Conference calls

This commit is contained in:
Isaac 2025-03-30 02:06:50 +04:00
parent 66c244f540
commit aaf52d4282
1585 changed files with 712275 additions and 1047 deletions

View File

@ -166,11 +166,11 @@ private final class EmbeddedBroadcastUploadImpl: BroadcastUploadImpl {
preferX264: false, preferX264: false,
logPath: "", logPath: "",
onMutedSpeechActivityDetected: { _ in }, onMutedSpeechActivityDetected: { _ in },
encryptionKey: nil,
isConference: false, isConference: false,
audioIsActiveByDefault: true, audioIsActiveByDefault: true,
isStream: false, isStream: false,
sharedAudioDevice: nil sharedAudioDevice: nil,
encryptionContext: nil
) )
self.callContext = callContext self.callContext = callContext
self.joinPayloadDisposable = (callContext.joinPayload self.joinPayloadDisposable = (callContext.joinPayload

View File

@ -23,7 +23,6 @@ swift_library(
"//submodules/PersistentStringHash:PersistentStringHash", "//submodules/PersistentStringHash:PersistentStringHash",
"//submodules/Utils/RangeSet", "//submodules/Utils/RangeSet",
"//submodules/Media/ConvertOpusToAAC", "//submodules/Media/ConvertOpusToAAC",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -926,7 +926,13 @@ private final class NotificationServiceHandler {
var localContactId: String? var localContactId: String?
} }
struct ConferenceCallData {
var id: Int64
var updates: String
}
var callData: CallData? var callData: CallData?
var conferenceCallData: ConferenceCallData?
if let messageIdString = payloadJson["msg_id"] as? String { if let messageIdString = payloadJson["msg_id"] as? String {
messageId = Int32(messageIdString) messageId = Int32(messageIdString)
@ -953,7 +959,24 @@ private final class NotificationServiceHandler {
} }
} }
if let callIdString = payloadJson["call_id"] as? String, let callAccessHashString = payloadJson["call_ah"] as? String, let peerId = peerId, let updates = payloadJson["updates"] as? String { if let locKey = payloadJson["loc-key"] as? String, (locKey == "CONF_CALL_REQUEST" || locKey == "CONF_CALL_MISSED"), let callIdString = payloadJson["call_id"] as? String {
if let callId = Int64(callIdString) {
if let updates = payloadJson["updates"] as? String {
var updateString = updates
updateString = updateString.replacingOccurrences(of: "-", with: "+")
updateString = updateString.replacingOccurrences(of: "_", with: "/")
while updateString.count % 4 != 0 {
updateString.append("=")
}
if let updateData = Data(base64Encoded: updateString) {
if let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) {
let _ = callUpdate
}
}
}
}
} else if let callIdString = payloadJson["call_id"] as? String, let callAccessHashString = payloadJson["call_ah"] as? String, let peerId = peerId, let updates = payloadJson["updates"] as? String {
if let callId = Int64(callIdString), let callAccessHash = Int64(callAccessHashString) { if let callId = Int64(callIdString), let callAccessHash = Int64(callAccessHashString) {
var peer: EnginePeer? var peer: EnginePeer?

View File

@ -955,11 +955,17 @@ public enum JoinSubjectScreenMode {
} }
public final class GroupCall { public final class GroupCall {
public let id: Int64
public let accessHash: Int64
public let slug: String
public let inviter: EnginePeer? public let inviter: EnginePeer?
public let members: [EnginePeer] public let members: [EnginePeer]
public let totalMemberCount: Int public let totalMemberCount: Int
public init(inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) { public init(id: Int64, accessHash: Int64, slug: String, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
self.id = id
self.accessHash = accessHash
self.slug = slug
self.inviter = inviter self.inviter = inviter
self.members = members self.members = members
self.totalMemberCount = totalMemberCount self.totalMemberCount = totalMemberCount

View File

@ -38,6 +38,7 @@ public final class OpenChatMessageParams {
public let openUrl: (String) -> Void public let openUrl: (String) -> Void
public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void
public let callPeer: (PeerId, Bool) -> Void public let callPeer: (PeerId, Bool) -> Void
public let openConferenceCall: (Message) -> Void
public let enqueueMessage: (EnqueueMessage) -> Void public let enqueueMessage: (EnqueueMessage) -> Void
public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
@ -70,6 +71,7 @@ public final class OpenChatMessageParams {
openUrl: @escaping (String) -> Void, openUrl: @escaping (String) -> Void,
openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void,
callPeer: @escaping (PeerId, Bool) -> Void, callPeer: @escaping (PeerId, Bool) -> Void,
openConferenceCall: @escaping (Message) -> Void,
enqueueMessage: @escaping (EnqueueMessage) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void,
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?,
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
@ -100,6 +102,7 @@ public final class OpenChatMessageParams {
self.openUrl = openUrl self.openUrl = openUrl
self.openPeer = openPeer self.openPeer = openPeer
self.callPeer = callPeer self.callPeer = callPeer
self.openConferenceCall = openConferenceCall
self.enqueueMessage = enqueueMessage self.enqueueMessage = enqueueMessage
self.sendSticker = sendSticker self.sendSticker = sendSticker
self.sendEmoji = sendEmoji self.sendEmoji = sendEmoji

View File

@ -422,6 +422,7 @@ public protocol PresentationGroupCall: AnyObject {
var accountContext: AccountContext { get } var accountContext: AccountContext { get }
var internalId: CallSessionInternalId { get } var internalId: CallSessionInternalId { get }
var peerId: EnginePeer.Id? { get } var peerId: EnginePeer.Id? { get }
var callId: Int64? { get }
var hasVideo: Bool { get } var hasVideo: Bool { get }
var hasScreencast: Bool { get } var hasScreencast: Bool { get }
@ -431,7 +432,6 @@ public protocol PresentationGroupCall: AnyObject {
var isStream: Bool { get } var isStream: Bool { get }
var isConference: Bool { get } var isConference: Bool { get }
var conferenceSource: CallSessionInternalId? { get } var conferenceSource: CallSessionInternalId? { get }
var encryptionKeyValue: Data? { get }
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get } var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
@ -447,6 +447,8 @@ public protocol PresentationGroupCall: AnyObject {
var isMuted: Signal<Bool, NoError> { get } var isMuted: Signal<Bool, NoError> { get }
var isNoiseSuppressionEnabled: Signal<Bool, NoError> { get } var isNoiseSuppressionEnabled: Signal<Bool, NoError> { get }
var e2eEncryptionKeyHash: Signal<Data?, NoError> { get }
var memberEvents: Signal<PresentationGroupCallMemberEvent, NoError> { get } var memberEvents: Signal<PresentationGroupCallMemberEvent, NoError> { get }
var reconnectedAsEvents: Signal<EnginePeer, NoError> { get } var reconnectedAsEvents: Signal<EnginePeer, NoError> { get }
@ -548,6 +550,10 @@ public enum PresentationCurrentCall: Equatable {
} }
} }
public enum JoinConferenceCallMode {
case joining
}
public protocol PresentationCallManager: AnyObject { public protocol PresentationCallManager: AnyObject {
var currentCallSignal: Signal<PresentationCall?, NoError> { get } var currentCallSignal: Signal<PresentationCall?, NoError> { get }
var currentGroupCallSignal: Signal<VideoChatCall?, NoError> { get } var currentGroupCallSignal: Signal<VideoChatCall?, NoError> { get }
@ -557,4 +563,11 @@ public protocol PresentationCallManager: AnyObject {
func requestCall(context: AccountContext, peerId: EnginePeer.Id, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult func requestCall(context: AccountContext, peerId: EnginePeer.Id, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
func joinGroupCall(context: AccountContext, peerId: EnginePeer.Id, invite: String?, requestJoinAsPeerId: ((@escaping (EnginePeer.Id?) -> Void) -> Void)?, initialCall: EngineGroupCallDescription, endCurrentIfAny: Bool) -> JoinGroupCallManagerResult func joinGroupCall(context: AccountContext, peerId: EnginePeer.Id, invite: String?, requestJoinAsPeerId: ((@escaping (EnginePeer.Id?) -> Void) -> Void)?, initialCall: EngineGroupCallDescription, endCurrentIfAny: Bool) -> JoinGroupCallManagerResult
func scheduleGroupCall(context: AccountContext, peerId: EnginePeer.Id, endCurrentIfAny: Bool, parentController: ViewController) -> RequestScheduleGroupCallResult func scheduleGroupCall(context: AccountContext, peerId: EnginePeer.Id, endCurrentIfAny: Bool, parentController: ViewController) -> RequestScheduleGroupCallResult
func joinConferenceCall(
accountContext: AccountContext,
initialCall: EngineGroupCallDescription,
reference: InternalGroupCallReference,
mode: JoinConferenceCallMode
)
} }

View File

@ -99,6 +99,7 @@ public final class BrowserBookmarksScreen: ViewController {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, presentGlobalOverlayController: { _, _ in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, longTap: { _, _ in }, longTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {

View File

@ -33,6 +33,7 @@ swift_library(
"//submodules/ItemListPeerActionItem", "//submodules/ItemListPeerActionItem",
"//submodules/InviteLinksUI", "//submodules/InviteLinksUI",
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/TelegramCallsUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -15,6 +15,7 @@ import ContextUI
import TelegramBaseController import TelegramBaseController
import InviteLinksUI import InviteLinksUI
import UndoUI import UndoUI
import TelegramCallsUI
public enum CallListControllerMode { public enum CallListControllerMode {
case tab case tab
@ -206,7 +207,32 @@ public final class CallListController: TelegramBaseController {
} }
private func createGroupCall() { private func createGroupCall() {
let controller = InviteLinkInviteController(context: self.context, updatedPresentationData: nil, mode: .groupCall(link: "https://t.me/call/+abbfbffll123", isRecentlyCreated: true), parentNavigationController: self.navigationController as? NavigationController, completed: { [weak self] result in let _ = (self.context.engine.calls.createConferenceCall()
|> deliverOnMainQueue).startStandalone(next: { [weak self] call in
guard let self else {
return
}
let openCall: () -> Void = { [weak self] in
guard let self else {
return
}
self.context.sharedContext.callManager?.joinConferenceCall(
accountContext: self.context,
initialCall: EngineGroupCallDescription(
id: call.callInfo.id,
accessHash: call.callInfo.accessHash,
title: call.callInfo.title,
scheduleTimestamp: nil,
subscribedToScheduled: false,
isStream: false
),
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
mode: .joining
)
}
let controller = InviteLinkInviteController(context: self.context, updatedPresentationData: nil, mode: .groupCall(link: call.link, isRecentlyCreated: true), parentNavigationController: self.navigationController as? NavigationController, completed: { [weak self] result in
guard let self else { guard let self else {
return return
} }
@ -217,14 +243,17 @@ public final class CallListController: TelegramBaseController {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .undo = action { if case .undo = action {
//TODO:release openCall()
} }
return false return false
}), in: .window(.root)) }), in: .window(.root))
case .openCall:
openCall()
} }
} }
}) })
self.present(controller, in: .window(.root), with: nil) self.present(controller, in: .window(.root), with: nil)
})
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {

View File

@ -117,13 +117,6 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
switch search { switch search {
case .recentPeers: case .recentPeers:
break break
/*items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
let _ = (context.engine.peers.removeRecentPeer(peerId: peerId)
|> deliverOnMainQueue).startStandalone(completed: {
f(.default)
})
})))
items.append(.separator)*/
case .recentSearch: case .recentSearch:
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId) let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId)
@ -539,9 +532,30 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
} else if case let .search(search) = source { } else if case let .search(search) = source {
switch search { switch search {
case .recentPeers, .search: case .recentPeers, .search:
var addedSeparator = false
if case let .recentPeers(isTopPeer) = search {
if isTopPeer {
if !items.isEmpty {
if !addedSeparator {
items.append(.separator)
addedSeparator = true
}
}
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
let _ = (context.engine.peers.removeRecentPeer(peerId: peerId)
|> deliverOnMainQueue).startStandalone(completed: {
f(.default)
})
})))
}
}
if peerGroup != nil { if peerGroup != nil {
if !items.isEmpty { if !items.isEmpty {
if !addedSeparator {
items.append(.separator) items.append(.separator)
addedSeparator = true
}
} }
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
if let chatListController = chatListController { if let chatListController = chatListController {

View File

@ -133,7 +133,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
peerSelected(peer, nil, false, .generic) peerSelected(peer, nil, false, .generic)
}, peerContextAction: { peer, node, gesture, location in }, peerContextAction: { peer, node, gesture, location in
if let peerContextAction = peerContextAction { if let peerContextAction = peerContextAction {
peerContextAction(peer, .recentPeers, node, gesture, location) peerContextAction(peer, .recentPeers(isTopPeer: true), node, gesture, location)
} else { } else {
gesture?.cancel() gesture?.cancel()
} }
@ -1409,7 +1409,7 @@ private struct ChatListSearchMessagesContext {
} }
public enum ChatListSearchContextActionSource { public enum ChatListSearchContextActionSource {
case recentPeers case recentPeers(isTopPeer: Bool)
case recentSearch case recentSearch
case recentApps case recentApps
case popularApps case popularApps
@ -3212,6 +3212,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
interaction.openUrl(url) interaction.openUrl(url)
}, openPeer: { _, _ in }, openPeer: { _, _ in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages |> map { message, a, b in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages |> map { message, a, b in
return (message.map { $0._asMessage() }, a, b) return (message.map { $0._asMessage() }, a, b)
@ -3406,6 +3407,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
interaction.openUrl(url) interaction.openUrl(url)
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource)) }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource))
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in }, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in

View File

@ -298,6 +298,9 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = invoice.title messageText = invoice.title
case let action as TelegramMediaAction: case let action as TelegramMediaAction:
switch action.action { switch action.action {
case .conferenceCall:
//TODO:localize
messageText = "Group call"
case let .phoneCall(_, discardReason, _, isVideo): case let .phoneCall(_, discardReason, _, isVideo):
hideAuthor = !isPeerGroup hideAuthor = !isPeerGroup
let incoming = message.flags.contains(.Incoming) let incoming = message.flags.contains(.Incoming)

View File

@ -162,6 +162,7 @@ public final class InviteLinkInviteController: ViewController {
public enum CompletionResult { public enum CompletionResult {
case linkCopied case linkCopied
case openCall
} }
private var animatedIn = false private var animatedIn = false

View File

@ -360,7 +360,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
} }
) )
//TODO:localize //TODO:localize
let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString("Be the first to join the call and add people from there. [Open Call >]()", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString("Be the first to join the call and add people from there. [Open Call >](open_call)", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = justCreatedCallTextAttributedString.string.range(of: ">"), let chevronImage { if let range = justCreatedCallTextAttributedString.string.range(of: ">"), let chevronImage {
justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string)) justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string))
} }
@ -575,6 +575,9 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
if strongSelf.justCreatedCallTextNode !== justCreatedCallTextNode { if strongSelf.justCreatedCallTextNode !== justCreatedCallTextNode {
strongSelf.justCreatedCallTextNode?.removeFromSupernode() strongSelf.justCreatedCallTextNode?.removeFromSupernode()
strongSelf.justCreatedCallTextNode = justCreatedCallTextNode strongSelf.justCreatedCallTextNode = justCreatedCallTextNode
//justCreatedCallTextNode.highlig
strongSelf.addSubnode(justCreatedCallTextNode) strongSelf.addSubnode(justCreatedCallTextNode)
} }
let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: shareButtonNode.frame.maxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height)) let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: shareButtonNode.frame.maxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height))

View File

@ -29,6 +29,7 @@ swift_library(
"//submodules/ComponentFlow", "//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent", "//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent",
"//submodules/Components/HierarchyTrackingLayer",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -19,6 +19,7 @@ import AvatarVideoNode
import ComponentFlow import ComponentFlow
import ComponentDisplayAdapters import ComponentDisplayAdapters
import StorySetIndicatorComponent import StorySetIndicatorComponent
import HierarchyTrackingLayer
private class PeerInfoAvatarListLoadingStripNode: ASImageNode { private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
private var currentInHierarchy = false private var currentInHierarchy = false
@ -225,6 +226,8 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
private var loadingProgressDisposable = MetaDisposable() private var loadingProgressDisposable = MetaDisposable()
private var hasProgress = false private var hasProgress = false
private let hierarchyTrackingLayer = HierarchyTrackingLayer()
public let isReady = Promise<Bool>() public let isReady = Promise<Bool>()
private var didSetReady: Bool = false private var didSetReady: Bool = false
@ -312,6 +315,20 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
}) })
} }
})) }))
self.hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] value in
guard let self else {
return
}
if value {
self.setupVideoPlayback()
} else {
self.videoNode?.removeFromSupernode()
self.videoNode = nil
self.videoContent = nil
}
}
self.layer.addSublayer(self.hierarchyTrackingLayer)
} }
deinit { deinit {
@ -364,6 +381,9 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
guard let videoContent = self.videoContent, let isCentral = self.isCentral, isCentral, self.videoNode == nil else { guard let videoContent = self.videoContent, let isCentral = self.isCentral, isCentral, self.videoNode == nil else {
return return
} }
if !self.hierarchyTrackingLayer.isInHierarchy {
return
}
let mediaManager = self.context.sharedContext.mediaManager let mediaManager = self.context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay)

View File

@ -219,3 +219,45 @@ public func restartIfError<T, E>(_ signal: Signal<T, E>) -> Signal<T, NoError> {
} }
} }
public enum RestartOrMapErrorCondition<E> {
case restart
case error(E)
}
public func restartOrMapError<T, E, E2>(condition: @escaping (E) -> RestartOrMapErrorCondition<E2>) -> (Signal<T, E>) -> Signal<T, E2> {
return { signal in
return Signal<T, E2> { subscriber in
let shouldRetry = Atomic(value: true)
let currentDisposable = MetaDisposable()
let start = recursiveFunction { recurse in
let currentShouldRetry = shouldRetry.with { value in
return value
}
if currentShouldRetry {
let disposable = signal.start(next: { next in
subscriber.putNext(next)
}, error: { error in
switch condition(error) {
case .restart:
recurse()
case let .error(e2):
subscriber.putError(e2)
}
}, completed: {
let _ = shouldRetry.swap(false)
subscriber.putCompletion()
})
currentDisposable.set(disposable)
}
}
start()
return ActionDisposable {
currentDisposable.dispose()
let _ = shouldRetry.swap(false)
}
}
}
}

View File

@ -52,6 +52,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1471112230] = { return $0.readInt32() } dict[-1471112230] = { return $0.readInt32() }
dict[570911930] = { return $0.readInt64() } dict[570911930] = { return $0.readInt64() }
dict[571523412] = { return $0.readDouble() } dict[571523412] = { return $0.readDouble() }
dict[0x0929C32F] = { return parseInt256($0) }
dict[-1255641564] = { return parseString($0) } dict[-1255641564] = { return parseString($0) }
dict[-1194283041] = { return Api.AccountDaysTTL.parse_accountDaysTTL($0) } dict[-1194283041] = { return Api.AccountDaysTTL.parse_accountDaysTTL($0) }
dict[-653423106] = { return Api.AttachMenuBot.parse_attachMenuBot($0) } dict[-653423106] = { return Api.AttachMenuBot.parse_attachMenuBot($0) }
@ -301,7 +302,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) }
dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) } dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) }
dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
dict[-839330845] = { return Api.GroupCall.parse_groupCall($0) } dict[-711498484] = { return Api.GroupCall.parse_groupCall($0) }
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
dict[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } dict[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) } dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) }
@ -381,6 +382,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) } dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) }
dict[-1945083841] = { return Api.InputGroupCall.parse_inputGroupCallInviteMessage($0) }
dict[-33127873] = { return Api.InputGroupCall.parse_inputGroupCallSlug($0) }
dict[887591921] = { return Api.InputInvoice.parse_inputInvoiceChatInviteSubscription($0) } dict[887591921] = { return Api.InputInvoice.parse_inputInvoiceChatInviteSubscription($0) }
dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) } dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) }
dict[-1734841331] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftCode($0) } dict[-1734841331] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftCode($0) }
@ -564,6 +567,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[51520707] = { return Api.MessageAction.parse_messageActionChatJoinedByLink($0) } dict[51520707] = { return Api.MessageAction.parse_messageActionChatJoinedByLink($0) }
dict[-339958837] = { return Api.MessageAction.parse_messageActionChatJoinedByRequest($0) } dict[-339958837] = { return Api.MessageAction.parse_messageActionChatJoinedByRequest($0) }
dict[-519864430] = { return Api.MessageAction.parse_messageActionChatMigrateTo($0) } dict[-519864430] = { return Api.MessageAction.parse_messageActionChatMigrateTo($0) }
dict[805187450] = { return Api.MessageAction.parse_messageActionConferenceCall($0) }
dict[-202219658] = { return Api.MessageAction.parse_messageActionContactSignUp($0) } dict[-202219658] = { return Api.MessageAction.parse_messageActionContactSignUp($0) }
dict[-85549226] = { return Api.MessageAction.parse_messageActionCustomAction($0) } dict[-85549226] = { return Api.MessageAction.parse_messageActionCustomAction($0) }
dict[-1230047312] = { return Api.MessageAction.parse_messageActionEmpty($0) } dict[-1230047312] = { return Api.MessageAction.parse_messageActionEmpty($0) }
@ -741,16 +745,16 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) } dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
dict[-193510921] = { return Api.PeerSettings.parse_peerSettings($0) } dict[-193510921] = { return Api.PeerSettings.parse_peerSettings($0) }
dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) } dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) }
dict[1000707084] = { return Api.PhoneCall.parse_phoneCall($0) } dict[810769141] = { return Api.PhoneCall.parse_phoneCall($0) }
dict[587035009] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
dict[-103656189] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) } dict[1355435489] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) }
dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) } dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) }
dict[1161174115] = { return Api.PhoneCall.parse_phoneCallRequested($0) } dict[347139340] = { return Api.PhoneCall.parse_phoneCallRequested($0) }
dict[-288085928] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } dict[-987599081] = { return Api.PhoneCall.parse_phoneCallWaiting($0) }
dict[-1344096199] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonAllowGroupCall($0) }
dict[-84416311] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonBusy($0) } dict[-84416311] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonBusy($0) }
dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) } dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) }
dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) } dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) }
dict[-1615072777] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMigrateConferenceCall($0) }
dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) } dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) }
dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) } dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
dict[-1665063993] = { return Api.PhoneConnection.parse_phoneConnection($0) } dict[-1665063993] = { return Api.PhoneConnection.parse_phoneConnection($0) }
@ -1061,6 +1065,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[422972864] = { return Api.Update.parse_updateFolderPeers($0) } dict[422972864] = { return Api.Update.parse_updateFolderPeers($0) }
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) } dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
dict[-1747565759] = { return Api.Update.parse_updateGroupCall($0) } dict[-1747565759] = { return Api.Update.parse_updateGroupCall($0) }
dict[-1535694705] = { return Api.Update.parse_updateGroupCallChainBlocks($0) }
dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) } dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) }
dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) } dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) }
dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) }

View File

@ -169,6 +169,8 @@ public extension Api {
public extension Api { public extension Api {
enum InputGroupCall: TypeConstructorDescription { enum InputGroupCall: TypeConstructorDescription {
case inputGroupCall(id: Int64, accessHash: Int64) case inputGroupCall(id: Int64, accessHash: Int64)
case inputGroupCallInviteMessage(msgId: Int32)
case inputGroupCallSlug(slug: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -179,6 +181,18 @@ public extension Api {
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false)
break break
case .inputGroupCallInviteMessage(let msgId):
if boxed {
buffer.appendInt32(-1945083841)
}
serializeInt32(msgId, buffer: buffer, boxed: false)
break
case .inputGroupCallSlug(let slug):
if boxed {
buffer.appendInt32(-33127873)
}
serializeString(slug, buffer: buffer, boxed: false)
break
} }
} }
@ -186,6 +200,10 @@ public extension Api {
switch self { switch self {
case .inputGroupCall(let id, let accessHash): case .inputGroupCall(let id, let accessHash):
return ("inputGroupCall", [("id", id as Any), ("accessHash", accessHash as Any)]) return ("inputGroupCall", [("id", id as Any), ("accessHash", accessHash as Any)])
case .inputGroupCallInviteMessage(let msgId):
return ("inputGroupCallInviteMessage", [("msgId", msgId as Any)])
case .inputGroupCallSlug(let slug):
return ("inputGroupCallSlug", [("slug", slug as Any)])
} }
} }
@ -203,6 +221,28 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_inputGroupCallInviteMessage(_ reader: BufferReader) -> InputGroupCall? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.InputGroupCall.inputGroupCallInviteMessage(msgId: _1!)
}
else {
return nil
}
}
public static func parse_inputGroupCallSlug(_ reader: BufferReader) -> InputGroupCall? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.InputGroupCall.inputGroupCallSlug(slug: _1!)
}
else {
return nil
}
}
} }
} }

View File

@ -349,6 +349,7 @@ public extension Api {
case messageActionChatJoinedByLink(inviterId: Int64) case messageActionChatJoinedByLink(inviterId: Int64)
case messageActionChatJoinedByRequest case messageActionChatJoinedByRequest
case messageActionChatMigrateTo(channelId: Int64) case messageActionChatMigrateTo(channelId: Int64)
case messageActionConferenceCall(flags: Int32, callId: Int64, duration: Int32?, otherParticipants: [Api.Peer]?)
case messageActionContactSignUp case messageActionContactSignUp
case messageActionCustomAction(message: String) case messageActionCustomAction(message: String)
case messageActionEmpty case messageActionEmpty
@ -479,6 +480,19 @@ public extension Api {
} }
serializeInt64(channelId, buffer: buffer, boxed: false) serializeInt64(channelId, buffer: buffer, boxed: false)
break break
case .messageActionConferenceCall(let flags, let callId, let duration, let otherParticipants):
if boxed {
buffer.appendInt32(805187450)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(callId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherParticipants!.count))
for item in otherParticipants! {
item.serialize(buffer, true)
}}
break
case .messageActionContactSignUp: case .messageActionContactSignUp:
if boxed { if boxed {
buffer.appendInt32(-202219658) buffer.appendInt32(-202219658)
@ -834,6 +848,8 @@ public extension Api {
return ("messageActionChatJoinedByRequest", []) return ("messageActionChatJoinedByRequest", [])
case .messageActionChatMigrateTo(let channelId): case .messageActionChatMigrateTo(let channelId):
return ("messageActionChatMigrateTo", [("channelId", channelId as Any)]) return ("messageActionChatMigrateTo", [("channelId", channelId as Any)])
case .messageActionConferenceCall(let flags, let callId, let duration, let otherParticipants):
return ("messageActionConferenceCall", [("flags", flags as Any), ("callId", callId as Any), ("duration", duration as Any), ("otherParticipants", otherParticipants as Any)])
case .messageActionContactSignUp: case .messageActionContactSignUp:
return ("messageActionContactSignUp", []) return ("messageActionContactSignUp", [])
case .messageActionCustomAction(let message): case .messageActionCustomAction(let message):
@ -1058,6 +1074,28 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_messageActionConferenceCall(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() }
var _4: [Api.Peer]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.MessageAction.messageActionConferenceCall(flags: _1!, callId: _2!, duration: _3, otherParticipants: _4)
}
else {
return nil
}
}
public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? { public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? {
return Api.MessageAction.messageActionContactSignUp return Api.MessageAction.messageActionContactSignUp
} }

View File

@ -1092,18 +1092,18 @@ public extension Api {
} }
public extension Api { public extension Api {
enum PhoneCall: TypeConstructorDescription { 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 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?)
case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol, 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?, conferenceCall: Api.InputGroupCall?) case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?)
case phoneCallEmpty(id: Int64) case phoneCallEmpty(id: Int64)
case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, protocol: Api.PhoneCallProtocol, conferenceCall: Api.InputGroupCall?) 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?, conferenceCall: Api.InputGroupCall?) case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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): 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):
if boxed { if boxed {
buffer.appendInt32(1000707084) buffer.appendInt32(810769141)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -1121,11 +1121,10 @@ public extension Api {
} }
serializeInt32(startDate, buffer: buffer, boxed: false) serializeInt32(startDate, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 7) != 0 {customParameters!.serialize(buffer, true)} if Int(flags) & Int(1 << 7) != 0 {customParameters!.serialize(buffer, true)}
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
break break
case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`, let conferenceCall): case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`):
if boxed { if boxed {
buffer.appendInt32(587035009) buffer.appendInt32(912311057)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -1135,17 +1134,15 @@ public extension Api {
serializeInt64(participantId, buffer: buffer, boxed: false) serializeInt64(participantId, buffer: buffer, boxed: false)
serializeBytes(gB, buffer: buffer, boxed: false) serializeBytes(gB, buffer: buffer, boxed: false)
`protocol`.serialize(buffer, true) `protocol`.serialize(buffer, true)
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
break break
case .phoneCallDiscarded(let flags, let id, let reason, let duration, let conferenceCall): case .phoneCallDiscarded(let flags, let id, let reason, let duration):
if boxed { if boxed {
buffer.appendInt32(-103656189) buffer.appendInt32(1355435489)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, 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 << 0) != 0 {reason!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} 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 break
case .phoneCallEmpty(let id): case .phoneCallEmpty(let id):
if boxed { if boxed {
@ -1153,9 +1150,9 @@ public extension Api {
} }
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
break break
case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`, let conferenceCall): case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`):
if boxed { if boxed {
buffer.appendInt32(1161174115) buffer.appendInt32(347139340)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -1165,11 +1162,10 @@ public extension Api {
serializeInt64(participantId, buffer: buffer, boxed: false) serializeInt64(participantId, buffer: buffer, boxed: false)
serializeBytes(gAHash, buffer: buffer, boxed: false) serializeBytes(gAHash, buffer: buffer, boxed: false)
`protocol`.serialize(buffer, true) `protocol`.serialize(buffer, true)
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
break break
case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate, let conferenceCall): case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate):
if boxed { if boxed {
buffer.appendInt32(-288085928) buffer.appendInt32(-987599081)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -1179,25 +1175,24 @@ public extension Api {
serializeInt64(participantId, buffer: buffer, boxed: false) serializeInt64(participantId, buffer: buffer, boxed: false)
`protocol`.serialize(buffer, true) `protocol`.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(receiveDate!, buffer: buffer, boxed: false)} 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 break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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): 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):
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)]) 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)])
case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`, let conferenceCall): 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), ("conferenceCall", conferenceCall as Any)]) 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, let conferenceCall): 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), ("conferenceCall", conferenceCall as Any)]) return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any)])
case .phoneCallEmpty(let id): case .phoneCallEmpty(let id):
return ("phoneCallEmpty", [("id", id as Any)]) return ("phoneCallEmpty", [("id", id as Any)])
case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`, let conferenceCall): 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), ("conferenceCall", conferenceCall as Any)]) 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, let conferenceCall): 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), ("conferenceCall", conferenceCall as Any)]) 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)])
} }
} }
@ -1232,10 +1227,6 @@ public extension Api {
if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() {
_12 = Api.parse(reader, signature: signature) as? Api.DataJSON _12 = Api.parse(reader, signature: signature) as? Api.DataJSON
} } } }
var _13: Api.InputGroupCall?
if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() {
_13 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1248,9 +1239,8 @@ public extension Api {
let _c10 = _10 != nil let _c10 = _10 != nil
let _c11 = _11 != nil let _c11 = _11 != nil
let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 8) == 0) || _13 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { return Api.PhoneCall.phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12)
return Api.PhoneCall.phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12, conferenceCall: _13)
} }
else { else {
return nil return nil
@ -1275,10 +1265,6 @@ public extension Api {
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol _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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1287,9 +1273,8 @@ public extension Api {
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = _7 != nil let _c7 = _7 != nil
let _c8 = _8 != nil let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
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!)
return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!, conferenceCall: _9)
} }
else { else {
return nil return nil
@ -1306,17 +1291,12 @@ public extension Api {
} } } }
var _4: Int32? var _4: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 {
if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4)
return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4, conferenceCall: _5)
} }
else { else {
return nil return nil
@ -1352,10 +1332,6 @@ public extension Api {
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol _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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1364,9 +1340,8 @@ public extension Api {
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = _7 != nil let _c7 = _7 != nil
let _c8 = _8 != nil let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
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!)
return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!, conferenceCall: _9)
} }
else { else {
return nil return nil
@ -1391,10 +1366,6 @@ public extension Api {
} }
var _8: Int32? var _8: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_8 = reader.readInt32() } 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1403,9 +1374,8 @@ public extension Api {
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = _7 != nil let _c7 = _7 != nil
let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
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)
return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8, conferenceCall: _9)
} }
else { else {
return nil return nil

View File

@ -1,19 +1,13 @@
public extension Api { public extension Api {
enum PhoneCallDiscardReason: TypeConstructorDescription { enum PhoneCallDiscardReason: TypeConstructorDescription {
case phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer)
case phoneCallDiscardReasonBusy case phoneCallDiscardReasonBusy
case phoneCallDiscardReasonDisconnect case phoneCallDiscardReasonDisconnect
case phoneCallDiscardReasonHangup case phoneCallDiscardReasonHangup
case phoneCallDiscardReasonMigrateConferenceCall(slug: String)
case phoneCallDiscardReasonMissed case phoneCallDiscardReasonMissed
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .phoneCallDiscardReasonAllowGroupCall(let encryptedKey):
if boxed {
buffer.appendInt32(-1344096199)
}
serializeBytes(encryptedKey, buffer: buffer, boxed: false)
break
case .phoneCallDiscardReasonBusy: case .phoneCallDiscardReasonBusy:
if boxed { if boxed {
buffer.appendInt32(-84416311) buffer.appendInt32(-84416311)
@ -31,6 +25,12 @@ public extension Api {
buffer.appendInt32(1471006352) buffer.appendInt32(1471006352)
} }
break
case .phoneCallDiscardReasonMigrateConferenceCall(let slug):
if boxed {
buffer.appendInt32(-1615072777)
}
serializeString(slug, buffer: buffer, boxed: false)
break break
case .phoneCallDiscardReasonMissed: case .phoneCallDiscardReasonMissed:
if boxed { if boxed {
@ -43,30 +43,19 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .phoneCallDiscardReasonAllowGroupCall(let encryptedKey):
return ("phoneCallDiscardReasonAllowGroupCall", [("encryptedKey", encryptedKey as Any)])
case .phoneCallDiscardReasonBusy: case .phoneCallDiscardReasonBusy:
return ("phoneCallDiscardReasonBusy", []) return ("phoneCallDiscardReasonBusy", [])
case .phoneCallDiscardReasonDisconnect: case .phoneCallDiscardReasonDisconnect:
return ("phoneCallDiscardReasonDisconnect", []) return ("phoneCallDiscardReasonDisconnect", [])
case .phoneCallDiscardReasonHangup: case .phoneCallDiscardReasonHangup:
return ("phoneCallDiscardReasonHangup", []) return ("phoneCallDiscardReasonHangup", [])
case .phoneCallDiscardReasonMigrateConferenceCall(let slug):
return ("phoneCallDiscardReasonMigrateConferenceCall", [("slug", slug as Any)])
case .phoneCallDiscardReasonMissed: case .phoneCallDiscardReasonMissed:
return ("phoneCallDiscardReasonMissed", []) return ("phoneCallDiscardReasonMissed", [])
} }
} }
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? { public static func parse_phoneCallDiscardReasonBusy(_ reader: BufferReader) -> PhoneCallDiscardReason? {
return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy
} }
@ -76,6 +65,17 @@ public extension Api {
public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? { public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? {
return Api.PhoneCallDiscardReason.phoneCallDiscardReasonHangup return Api.PhoneCallDiscardReason.phoneCallDiscardReasonHangup
} }
public static func parse_phoneCallDiscardReasonMigrateConferenceCall(_ reader: BufferReader) -> PhoneCallDiscardReason? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMigrateConferenceCall(slug: _1!)
}
else {
return nil
}
}
public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? { public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? {
return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed
} }

View File

@ -1048,6 +1048,7 @@ public extension Api {
case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32)
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32) case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
case updateGroupCall(flags: Int32, chatId: Int64?, call: Api.GroupCall) case updateGroupCall(flags: Int32, chatId: Int64?, call: Api.GroupCall)
case updateGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32)
case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) case updateGroupCallConnection(flags: Int32, params: Api.DataJSON)
case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32)
case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?)
@ -1738,6 +1739,19 @@ public extension Api {
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(chatId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(chatId!, buffer: buffer, boxed: false)}
call.serialize(buffer, true) call.serialize(buffer, true)
break break
case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset):
if boxed {
buffer.appendInt32(-1535694705)
}
call.serialize(buffer, true)
serializeInt32(subChainId, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocks.count))
for item in blocks {
serializeBytes(item, buffer: buffer, boxed: false)
}
serializeInt32(nextOffset, buffer: buffer, boxed: false)
break
case .updateGroupCallConnection(let flags, let params): case .updateGroupCallConnection(let flags, let params):
if boxed { if boxed {
buffer.appendInt32(192428418) buffer.appendInt32(192428418)
@ -2499,6 +2513,8 @@ public extension Api {
return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)]) return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)])
case .updateGroupCall(let flags, let chatId, let call): case .updateGroupCall(let flags, let chatId, let call):
return ("updateGroupCall", [("flags", flags as Any), ("chatId", chatId as Any), ("call", call as Any)]) return ("updateGroupCall", [("flags", flags as Any), ("chatId", chatId as Any), ("call", call as Any)])
case .updateGroupCallChainBlocks(let call, let subChainId, let blocks, let nextOffset):
return ("updateGroupCallChainBlocks", [("call", call as Any), ("subChainId", subChainId as Any), ("blocks", blocks as Any), ("nextOffset", nextOffset as Any)])
case .updateGroupCallConnection(let flags, let params): case .updateGroupCallConnection(let flags, let params):
return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)])
case .updateGroupCallParticipants(let call, let participants, let version): case .updateGroupCallParticipants(let call, let participants, let version):
@ -3939,6 +3955,30 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_updateGroupCallChainBlocks(_ reader: BufferReader) -> Update? {
var _1: Api.InputGroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
}
var _2: Int32?
_2 = reader.readInt32()
var _3: [Buffer]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self)
}
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.Update.updateGroupCallChainBlocks(call: _1!, subChainId: _2!, blocks: _3!, nextOffset: _4!)
}
else {
return nil
}
}
public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? { public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()

View File

@ -9855,16 +9855,15 @@ public extension Api.functions.phone {
} }
} }
public extension Api.functions.phone { public extension Api.functions.phone {
static func createConferenceCall(peer: Api.InputPhoneCall, keyFingerprint: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.PhoneCall>) { static func createConferenceCall(randomId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCall>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-540472917) buffer.appendInt32(-70320410)
peer.serialize(buffer, true) serializeInt32(randomId, buffer: buffer, boxed: false)
serializeInt64(keyFingerprint, buffer: buffer, boxed: false) return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in
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) let reader = BufferReader(buffer)
var result: Api.phone.PhoneCall? var result: Api.phone.GroupCall?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall
} }
return result return result
}) })
@ -9889,6 +9888,42 @@ public extension Api.functions.phone {
}) })
} }
} }
public extension Api.functions.phone {
static func declineConferenceCallInvite(msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1011325297)
serializeInt32(msgId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "phone.declineConferenceCallInvite", parameters: [("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.phone {
static func deleteConferenceCallParticipants(call: Api.InputGroupCall, ids: [Api.InputPeer], block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(585451463)
call.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(ids.count))
for item in ids {
item.serialize(buffer, true)
}
serializeBytes(block, buffer: buffer, boxed: false)
return (FunctionDescription(name: "phone.deleteConferenceCallParticipants", parameters: [("call", String(describing: call)), ("ids", String(describing: ids)), ("block", String(describing: block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.phone { public extension Api.functions.phone {
static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
@ -10009,6 +10044,24 @@ public extension Api.functions.phone {
}) })
} }
} }
public extension Api.functions.phone {
static func getGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-291534682)
call.serialize(buffer, true)
serializeInt32(subChainId, buffer: buffer, boxed: false)
serializeInt32(offset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "phone.getGroupCallChainBlocks", parameters: [("call", String(describing: call)), ("subChainId", String(describing: subChainId)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.phone { public extension Api.functions.phone {
static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.JoinAsPeers>) { static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.JoinAsPeers>) {
let buffer = Buffer() let buffer = Buffer()
@ -10082,6 +10135,22 @@ public extension Api.functions.phone {
}) })
} }
} }
public extension Api.functions.phone {
static func inviteConferenceCallParticipant(call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1050474478)
call.serialize(buffer, true)
userId.serialize(buffer, true)
return (FunctionDescription(name: "phone.inviteConferenceCallParticipant", parameters: [("call", String(describing: call)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.phone { public extension Api.functions.phone {
static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
@ -10103,16 +10172,18 @@ public extension Api.functions.phone {
} }
} }
public extension Api.functions.phone { public extension Api.functions.phone {
static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, keyFingerprint: Int64?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, publicKey: Int256?, block: Buffer?, inviteMsgId: Int32?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-702669325) buffer.appendInt32(-624854114)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true) call.serialize(buffer, true)
joinAs.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 << 1) != 0 {serializeString(inviteHash!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(keyFingerprint!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt256(publicKey!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {serializeBytes(block!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(inviteMsgId!, buffer: buffer, boxed: false)}
params.serialize(buffer, true) 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)), ("keyFingerprint", String(describing: keyFingerprint)), ("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)), ("publicKey", String(describing: publicKey)), ("block", String(describing: block)), ("inviteMsgId", String(describing: inviteMsgId)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -10185,16 +10256,15 @@ public extension Api.functions.phone {
} }
} }
public extension Api.functions.phone { public extension Api.functions.phone {
static func requestCall(flags: Int32, userId: Api.InputUser, conferenceCall: Api.InputGroupCall?, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.PhoneCall>) { static func requestCall(flags: Int32, userId: Api.InputUser, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.PhoneCall>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1497079796) buffer.appendInt32(1124046573)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
userId.serialize(buffer, true) userId.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {conferenceCall!.serialize(buffer, true)}
serializeInt32(randomId, buffer: buffer, boxed: false) serializeInt32(randomId, buffer: buffer, boxed: false)
serializeBytes(gAHash, buffer: buffer, boxed: false) serializeBytes(gAHash, buffer: buffer, boxed: false)
`protocol`.serialize(buffer, true) `protocol`.serialize(buffer, true)
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 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
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.phone.PhoneCall? var result: Api.phone.PhoneCall?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -10252,6 +10322,22 @@ public extension Api.functions.phone {
}) })
} }
} }
public extension Api.functions.phone {
static func sendConferenceCallBroadcast(call: Api.InputGroupCall, block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-965732096)
call.serialize(buffer, true)
serializeBytes(block, buffer: buffer, boxed: false)
return (FunctionDescription(name: "phone.sendConferenceCallBroadcast", parameters: [("call", String(describing: call)), ("block", String(describing: block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.phone { public extension Api.functions.phone {
static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -992,14 +992,14 @@ public extension Api {
} }
public extension Api { public extension Api {
enum GroupCall: TypeConstructorDescription { enum GroupCall: TypeConstructorDescription {
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32, conferenceFromCall: Int64?) case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32)
case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32) case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let conferenceFromCall): case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version):
if boxed { if boxed {
buffer.appendInt32(-839330845) buffer.appendInt32(-711498484)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -1012,7 +1012,6 @@ public extension Api {
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(unmutedVideoCount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(unmutedVideoCount!, buffer: buffer, boxed: false)}
serializeInt32(unmutedVideoLimit, buffer: buffer, boxed: false) serializeInt32(unmutedVideoLimit, buffer: buffer, boxed: false)
serializeInt32(version, buffer: buffer, boxed: false) serializeInt32(version, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 14) != 0 {serializeInt64(conferenceFromCall!, buffer: buffer, boxed: false)}
break break
case .groupCallDiscarded(let id, let accessHash, let duration): case .groupCallDiscarded(let id, let accessHash, let duration):
if boxed { if boxed {
@ -1027,8 +1026,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let conferenceFromCall): case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version):
return ("groupCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("participantsCount", participantsCount as Any), ("title", title as Any), ("streamDcId", streamDcId as Any), ("recordStartDate", recordStartDate as Any), ("scheduleDate", scheduleDate as Any), ("unmutedVideoCount", unmutedVideoCount as Any), ("unmutedVideoLimit", unmutedVideoLimit as Any), ("version", version as Any), ("conferenceFromCall", conferenceFromCall as Any)]) return ("groupCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("participantsCount", participantsCount as Any), ("title", title as Any), ("streamDcId", streamDcId as Any), ("recordStartDate", recordStartDate as Any), ("scheduleDate", scheduleDate as Any), ("unmutedVideoCount", unmutedVideoCount as Any), ("unmutedVideoLimit", unmutedVideoLimit as Any), ("version", version as Any)])
case .groupCallDiscarded(let id, let accessHash, let duration): case .groupCallDiscarded(let id, let accessHash, let duration):
return ("groupCallDiscarded", [("id", id as Any), ("accessHash", accessHash as Any), ("duration", duration as Any)]) return ("groupCallDiscarded", [("id", id as Any), ("accessHash", accessHash as Any), ("duration", duration as Any)])
} }
@ -1057,8 +1056,6 @@ public extension Api {
_10 = reader.readInt32() _10 = reader.readInt32()
var _11: Int32? var _11: Int32?
_11 = reader.readInt32() _11 = reader.readInt32()
var _12: Int64?
if Int(_1!) & Int(1 << 14) != 0 {_12 = reader.readInt64() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1070,9 +1067,8 @@ public extension Api {
let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil
let _c10 = _10 != nil let _c10 = _10 != nil
let _c11 = _11 != nil let _c11 = _11 != nil
let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!)
return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, conferenceFromCall: _12)
} }
else { else {
return nil return nil

View File

@ -3,13 +3,48 @@ import Foundation
public struct Int128 { public struct Int128 {
public var _0: Int64 public var _0: Int64
public var _1: Int64 public var _1: Int64
public init(_0: Int64, _1: Int64) {
self._0 = _0
self._1 = _1
}
} }
public struct Int256 { public struct Int256: Equatable, CustomStringConvertible {
public var _0: Int64 public var _0: Int64
public var _1: Int64 public var _1: Int64
public var _2: Int64 public var _2: Int64
public var _3: Int64 public var _3: Int64
public init(_0: Int64, _1: Int64, _2: Int64, _3: Int64) {
self._0 = _0
self._1 = _1
self._2 = _2
self._3 = _3
}
public var description: String {
var data = Data(count: 32)
data.withUnsafeMutableBytes { buffer in
if let baseAddress = buffer.baseAddress {
let int64Buffer = baseAddress.assumingMemoryBound(to: Int64.self)
int64Buffer[0] = self._0
int64Buffer[1] = self._1
int64Buffer[2] = self._2
int64Buffer[3] = self._3
}
}
let hexString = NSMutableString()
data.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
for i in 0 ..< data.count {
hexString.appendFormat("%02x", UInt(bytes.advanced(by: i).pointee))
}
}
return hexString as String
}
} }
func serializeInt32(_ value: Int32, buffer: Buffer, boxed: Bool) { func serializeInt32(_ value: Int32, buffer: Buffer, boxed: Bool) {

View File

@ -39,6 +39,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
}, openUrl: { _ in }, openUrl: { _ in
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, enqueueMessage: { message in }, enqueueMessage: { message in
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in
@ -469,7 +470,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
return return
} }
if groupCallPanelData.info.scheduleTimestamp != nil && !groupCallPanelData.info.subscribedToScheduled { if groupCallPanelData.info.scheduleTimestamp != nil && !groupCallPanelData.info.subscribedToScheduled {
let _ = self.context.engine.calls.toggleScheduledGroupCallSubscription(peerId: groupCallPanelData.peerId, callId: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, subscribe: true).startStandalone() let _ = self.context.engine.calls.toggleScheduledGroupCallSubscription(peerId: groupCallPanelData.peerId, reference: .id(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash), subscribe: true).startStandalone()
let controller = UndoOverlayController( let controller = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,

View File

@ -120,6 +120,7 @@ swift_library(
"//submodules/DirectMediaImageCache", "//submodules/DirectMediaImageCache",
"//submodules/FastBlur", "//submodules/FastBlur",
"//submodules/InviteLinksUI", "//submodules/InviteLinksUI",
"//third-party/td:TdBinding",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -233,7 +233,7 @@ public final class PresentationCallImpl: PresentationCall {
public let internalId: CallSessionInternalId public let internalId: CallSessionInternalId
public let peerId: EnginePeer.Id public let peerId: EnginePeer.Id
public let isOutgoing: Bool public let isOutgoing: Bool
private let isIncomingConference: Bool private let incomingConferenceSource: EngineMessage.Id?
public var isVideo: Bool public var isVideo: Bool
public var isVideoPossible: Bool public var isVideoPossible: Bool
private let enableStunMarking: Bool private let enableStunMarking: Bool
@ -354,6 +354,7 @@ public final class PresentationCallImpl: PresentationCall {
private var conferenceCallDisposable: Disposable? private var conferenceCallDisposable: Disposable?
private var upgradedToConferenceCompletions = Bag<(PresentationGroupCall) -> Void>() private var upgradedToConferenceCompletions = Bag<(PresentationGroupCall) -> Void>()
private var isAcceptingIncomingConference: Bool = false
private var waitForConferenceCallReadyDisposable: Disposable? private var waitForConferenceCallReadyDisposable: Disposable?
private let conferenceStatePromise = ValuePromise<PresentationCallConferenceState?>(nil) private let conferenceStatePromise = ValuePromise<PresentationCallConferenceState?>(nil)
public private(set) var conferenceStateValue: PresentationCallConferenceState? { public private(set) var conferenceStateValue: PresentationCallConferenceState? {
@ -386,7 +387,7 @@ public final class PresentationCallImpl: PresentationCall {
internalId: CallSessionInternalId, internalId: CallSessionInternalId,
peerId: EnginePeer.Id, peerId: EnginePeer.Id,
isOutgoing: Bool, isOutgoing: Bool,
isIncomingConference: Bool, incomingConferenceSource: EngineMessage.Id?,
peer: EnginePeer?, peer: EnginePeer?,
proxyServer: ProxyServerSettings?, proxyServer: ProxyServerSettings?,
auxiliaryServers: [CallAuxiliaryServer], auxiliaryServers: [CallAuxiliaryServer],
@ -421,7 +422,7 @@ public final class PresentationCallImpl: PresentationCall {
self.internalId = internalId self.internalId = internalId
self.peerId = peerId self.peerId = peerId
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.isIncomingConference = isIncomingConference self.incomingConferenceSource = incomingConferenceSource
self.isVideo = initialState?.type == .video self.isVideo = initialState?.type == .video
self.isVideoPossible = isVideoPossible self.isVideoPossible = isVideoPossible
self.enableStunMarking = enableStunMarking self.enableStunMarking = enableStunMarking
@ -731,8 +732,11 @@ public final class PresentationCallImpl: PresentationCall {
switch previous.state { switch previous.state {
case .active: case .active:
wasActive = true wasActive = true
case .terminated, .dropping: case let .terminated(_, reason, _):
if case .ended(.switchedToConference) = reason {
} else {
wasTerminated = true wasTerminated = true
}
default: default:
break break
} }
@ -858,22 +862,23 @@ public final class PresentationCallImpl: PresentationCall {
self.callWasActive = true self.callWasActive = true
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .dropping(reason): case let .dropping(reason):
if case .ended(.switchedToConference) = reason {
} else {
presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
}
case let .terminated(id, reason, options): 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) 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) presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
case let .active(_, _, keyVisualHash, _, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _): case .active(_, _, _, _, _, _, _, _), .switchedToConference:
self.callWasActive = true self.callWasActive = true
var isConference = false var isConference = false
if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state { if case .switchedToConference = sessionState.state {
isConference = conferenceCall != nil
} else if case .switchedToConference = sessionState.state {
isConference = true isConference = true
} }
if let callContextState = callContextState, !isConference { if let callContextState = callContextState, !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _) = sessionState.state {
switch callContextState.state { switch callContextState.state {
case .initializing: case .initializing:
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
@ -899,54 +904,67 @@ public final class PresentationCallImpl: PresentationCall {
} }
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
} }
} else if !isConference { } else if !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _) = sessionState.state {
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
} }
} }
var conferenceCallData: (key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)? var conferenceCallData: InternalGroupCallReference?
var conferenceFromCallId: CallId? if let incomingConferenceSource = self.incomingConferenceSource {
switch sessionState.state { if self.isAcceptingIncomingConference {
case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall, isIncomingConference): conferenceCallData = .message(id: incomingConferenceSource)
if let conferenceCall, !isIncomingConference {
conferenceFromCallId = id
conferenceCallData = (key, keyVisualHash, conferenceCall)
} }
case let .switchedToConference(key, keyVisualHash, conferenceCall): } else {
conferenceCallData = (key, keyVisualHash, conferenceCall) switch sessionState.state {
case let .switchedToConference(slug):
conferenceCallData = .link(slug: slug)
default: default:
break break
} }
}
if let (key, _, conferenceCall) = conferenceCallData { if let conferenceCallData {
if self.conferenceCallDisposable == nil { if self.conferenceCallDisposable == nil {
self.conferenceCallDisposable = EmptyDisposable let conferenceCallSignal = self.context.engine.calls.getCurrentGroupCall(reference: conferenceCallData)
self.conferenceCallDisposable = (conferenceCallSignal
#if DEBUG |> deliverOnMainQueue).startStrict(next: { [weak self] groupCall in
print("Switching to conference call with encryption key: \(key.base64EncodedString())") guard let self else {
#endif return
}
let keyPair: TelegramKeyPair? = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair()
guard let keyPair, let groupCall else {
self.updateSessionState(sessionState: CallSession(
id: self.internalId,
stableId: nil,
isOutgoing: false,
type: .audio,
state: .terminated(id: nil, reason: .error(.generic), options: CallTerminationOptions()),
isVideoPossible: true
),
callContextState: nil, reception: nil, audioSessionControl: self.audioSessionControl)
return
}
let conferenceCall = PresentationGroupCallImpl( let conferenceCall = PresentationGroupCallImpl(
accountContext: self.context, accountContext: self.context,
audioSession: self.audioSession, audioSession: self.audioSession,
callKitIntegration: self.callKitIntegration, callKitIntegration: self.callKitIntegration,
getDeviceAccessData: self.getDeviceAccessData, getDeviceAccessData: self.getDeviceAccessData,
initialCall: EngineGroupCallDescription( initialCall: (EngineGroupCallDescription(
id: conferenceCall.id, id: groupCall.info.id,
accessHash: conferenceCall.accessHash, accessHash: groupCall.info.accessHash,
title: nil, title: nil,
scheduleTimestamp: nil, scheduleTimestamp: nil,
subscribedToScheduled: false, subscribedToScheduled: false,
isStream: false isStream: false
), ), conferenceCallData),
internalId: CallSessionInternalId(), internalId: CallSessionInternalId(),
peerId: nil, peerId: nil,
isChannel: false, isChannel: false,
invite: nil, invite: nil,
joinAsPeerId: nil, joinAsPeerId: nil,
isStream: false, isStream: false,
encryptionKey: (key, 1), keyPair: keyPair,
conferenceFromCallId: conferenceFromCallId,
conferenceSourceId: self.internalId, conferenceSourceId: self.internalId,
isConference: true, isConference: true,
sharedAudioContext: self.sharedAudioContext sharedAudioContext: self.sharedAudioContext
@ -1045,6 +1063,20 @@ public final class PresentationCallImpl: PresentationCall {
f(conferenceCall) f(conferenceCall)
} }
}) })
}, error: { [weak self] _ in
guard let self else {
return
}
self.updateSessionState(sessionState: CallSession(
id: self.internalId,
stableId: nil,
isOutgoing: false,
type: .audio,
state: .terminated(id: nil, reason: .error(.generic), options: CallTerminationOptions()),
isVideoPossible: true
),
callContextState: nil, reception: nil, audioSessionControl: self.audioSessionControl)
})
} }
} }
@ -1053,7 +1085,7 @@ public final class PresentationCallImpl: PresentationCall {
if let _ = audioSessionControl { if let _ = audioSessionControl {
self.audioSessionShouldBeActive.set(true) self.audioSessionShouldBeActive.set(true)
} }
case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, _, _): case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P):
self.audioSessionShouldBeActive.set(true) self.audioSessionShouldBeActive.set(true)
if conferenceCallData != nil { if conferenceCallData != nil {
@ -1150,9 +1182,14 @@ public final class PresentationCallImpl: PresentationCall {
var terminating = false var terminating = false
if case .terminated = sessionState.state { if case .terminated = sessionState.state {
terminating = true terminating = true
} else if case .dropping = sessionState.state { } else if case let .dropping(reason) = sessionState.state {
switch reason {
case .ended(.switchedToConference):
break
default:
terminating = true terminating = true
} }
}
if terminating, !wasTerminated { if terminating, !wasTerminated {
if !self.didSetCanBeRemoved { if !self.didSetCanBeRemoved {
@ -1180,9 +1217,7 @@ public final class PresentationCallImpl: PresentationCall {
} }
var isConference = false var isConference = false
if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state { if case .switchedToConference = sessionState.state {
isConference = conferenceCall != nil
} else if case .switchedToConference = sessionState.state {
isConference = true isConference = true
} }
if self.conferenceCallImpl != nil { if self.conferenceCallImpl != nil {
@ -1191,7 +1226,7 @@ public final class PresentationCallImpl: PresentationCall {
if self.conferenceStateValue != nil { if self.conferenceStateValue != nil {
isConference = true isConference = true
} }
if self.isIncomingConference { if self.incomingConferenceSource != nil {
isConference = true isConference = true
} }
@ -1303,11 +1338,22 @@ public final class PresentationCallImpl: PresentationCall {
return return
} }
if value { if value {
if strongSelf.isIncomingConference { if strongSelf.incomingConferenceSource != nil {
strongSelf.conferenceStateValue = .preparing strongSelf.conferenceStateValue = .preparing
strongSelf.isAcceptingIncomingConference = true
strongSelf.updateSessionState(sessionState: CallSession(
id: strongSelf.internalId,
stableId: nil,
isOutgoing: false,
type: .audio,
state: .ringing,
isVideoPossible: true
),
callContextState: nil, reception: nil, audioSessionControl: strongSelf.audioSessionControl)
} else {
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
} }
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
if !fromCallKitAction { if !fromCallKitAction {
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId) strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
} }
@ -1316,11 +1362,22 @@ public final class PresentationCallImpl: PresentationCall {
} }
}) })
} else { } else {
if strongSelf.isIncomingConference { if strongSelf.incomingConferenceSource != nil {
strongSelf.conferenceStateValue = .preparing strongSelf.conferenceStateValue = .preparing
strongSelf.isAcceptingIncomingConference = true
strongSelf.updateSessionState(sessionState: CallSession(
id: strongSelf.internalId,
stableId: nil,
isOutgoing: false,
type: .audio,
state: .ringing,
isVideoPossible: true
),
callContextState: nil, reception: nil, audioSessionControl: strongSelf.audioSessionControl)
} else {
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
} }
strongSelf.callSessionManager.accept(internalId: strongSelf.internalId)
if !fromCallKitAction { if !fromCallKitAction {
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId) strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
} }
@ -1336,6 +1393,7 @@ public final class PresentationCallImpl: PresentationCall {
return .single(true) return .single(true)
} }
let debugLogValue = Promise<String?>() let debugLogValue = Promise<String?>()
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get()) self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get())
self.ongoingContext?.stop(debugLogValue: debugLogValue) self.ongoingContext?.stop(debugLogValue: debugLogValue)

View File

@ -348,7 +348,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
internalId: firstState.2.id, internalId: firstState.2.id,
peerId: firstState.2.peerId, peerId: firstState.2.peerId,
isOutgoing: false, isOutgoing: false,
isIncomingConference: firstState.2.isIncomingConference, incomingConferenceSource: firstState.2.conferenceSource,
peer: EnginePeer(firstState.1), peer: EnginePeer(firstState.1),
proxyServer: strongSelf.proxyServer, proxyServer: strongSelf.proxyServer,
auxiliaryServers: [], auxiliaryServers: [],
@ -571,7 +571,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|> mapToSignal { areVideoCallsAvailable -> Signal<CallSessionInternalId, NoError> in |> mapToSignal { areVideoCallsAvailable -> Signal<CallSessionInternalId, NoError> in
let isVideoPossible: Bool = areVideoCallsAvailable let isVideoPossible: Bool = areVideoCallsAvailable
return context.account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, conferenceCall: nil, internalId: internalId) return context.account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, internalId: internalId)
} }
return (combineLatest(queue: .mainQueue(), return (combineLatest(queue: .mainQueue(),
@ -616,7 +616,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
internalId: internalId, internalId: internalId,
peerId: peerId, peerId: peerId,
isOutgoing: true, isOutgoing: true,
isIncomingConference: false, incomingConferenceSource: nil,
peer: nil, peer: nil,
proxyServer: strongSelf.proxyServer, proxyServer: strongSelf.proxyServer,
auxiliaryServers: [], auxiliaryServers: [],
@ -847,8 +847,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
invite: nil, invite: nil,
joinAsPeerId: nil, joinAsPeerId: nil,
isStream: false, isStream: false,
encryptionKey: nil, keyPair: nil,
conferenceFromCallId: nil,
conferenceSourceId: nil, conferenceSourceId: nil,
isConference: false, isConference: false,
sharedAudioContext: nil sharedAudioContext: nil
@ -1067,19 +1066,53 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
audioSession: self.audioSession, audioSession: self.audioSession,
callKitIntegration: nil, callKitIntegration: nil,
getDeviceAccessData: self.getDeviceAccessData, getDeviceAccessData: self.getDeviceAccessData,
initialCall: initialCall, initialCall: (initialCall, .id(id: initialCall.id, accessHash: initialCall.accessHash)),
internalId: internalId, internalId: internalId,
peerId: peerId, peerId: peerId,
isChannel: isChannel, isChannel: isChannel,
invite: invite, invite: invite,
joinAsPeerId: joinAsPeerId, joinAsPeerId: joinAsPeerId,
isStream: initialCall.isStream ?? false, isStream: initialCall.isStream ?? false,
encryptionKey: nil, keyPair: nil,
conferenceFromCallId: nil,
conferenceSourceId: nil, conferenceSourceId: nil,
isConference: false, isConference: false,
sharedAudioContext: nil sharedAudioContext: nil
) )
self.updateCurrentGroupCall(.group(call)) self.updateCurrentGroupCall(.group(call))
} }
public func joinConferenceCall(
accountContext: AccountContext,
initialCall: EngineGroupCallDescription,
reference: InternalGroupCallReference,
mode: JoinConferenceCallMode
) {
let keyPair: TelegramKeyPair
switch mode {
case .joining:
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
return
}
keyPair = keyPairValue
}
let call = PresentationGroupCallImpl(
accountContext: accountContext,
audioSession: self.audioSession,
callKitIntegration: nil,
getDeviceAccessData: self.getDeviceAccessData,
initialCall: (initialCall, reference),
internalId: CallSessionInternalId(),
peerId: nil,
isChannel: false,
invite: nil,
joinAsPeerId: nil,
isStream: false,
keyPair: keyPair,
conferenceSourceId: nil,
isConference: true,
sharedAudioContext: nil
)
self.updateCurrentGroupCall(.group(call))
}
} }

View File

@ -17,6 +17,7 @@ import DeviceProximity
import UndoUI import UndoUI
import TemporaryCachedPeerDataManager import TemporaryCachedPeerDataManager
import CallsEmoji import CallsEmoji
import TdBinding
private extension GroupCallParticipantsContext.Participant { private extension GroupCallParticipantsContext.Participant {
var allSsrcs: Set<UInt32> { var allSsrcs: Set<UInt32> {
@ -94,7 +95,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
public init(account: Account, engine: TelegramEngine, peerId: PeerId?, isChannel: Bool, call: EngineGroupCallDescription) { public init(account: Account, engine: TelegramEngine, peerId: PeerId?, isChannel: Bool, call: EngineGroupCallDescription) {
self.panelDataPromise.set(.single(nil)) self.panelDataPromise.set(.single(nil))
let state = engine.calls.getGroupCallParticipants(callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: nil) let state = engine.calls.getGroupCallParticipants(reference: .id(id: call.id, accessHash: call.accessHash), offset: "", ssrcs: [], limit: 100, sortAscending: nil)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in |> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
return .single(nil) return .single(nil)
@ -118,7 +119,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
peerId: peerId, peerId: peerId,
myPeerId: account.peerId, myPeerId: account.peerId,
id: call.id, id: call.id,
accessHash: call.accessHash, reference: .id(id: call.id, accessHash: call.accessHash),
state: state, state: state,
previousServiceState: nil previousServiceState: nil
) )
@ -147,7 +148,21 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
return GroupCallPanelData( return GroupCallPanelData(
peerId: peerId, peerId: peerId,
isChannel: isChannel, isChannel: isChannel,
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, streamDcId: nil, title: state.title, scheduleTimestamp: state.scheduleTimestamp, subscribedToScheduled: state.subscribedToScheduled, recordingStartTimestamp: nil, sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit, isStream: state.isStream, upgradedPrivateCallId: state.upgradedPrivateCallId), info: GroupCallInfo(
id: call.id,
accessHash: call.accessHash,
participantCount: state.totalCount,
streamDcId: nil,
title: state.title,
scheduleTimestamp: state.scheduleTimestamp,
subscribedToScheduled: state.subscribedToScheduled,
recordingStartTimestamp: nil,
sortAscending: state.sortAscending,
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit,
isStream: state.isStream
),
topParticipants: topParticipants, topParticipants: topParticipants,
participantCount: state.totalCount, participantCount: state.totalCount,
activeSpeakers: activeSpeakers, activeSpeakers: activeSpeakers,
@ -537,11 +552,11 @@ private final class ScreencastInProcessIPCContext: ScreencastIPCContext {
preferX264: false, preferX264: false,
logPath: "", logPath: "",
onMutedSpeechActivityDetected: { _ in }, onMutedSpeechActivityDetected: { _ in },
encryptionKey: nil,
isConference: self.isConference, isConference: self.isConference,
audioIsActiveByDefault: true, audioIsActiveByDefault: true,
isStream: false, isStream: false,
sharedAudioDevice: nil sharedAudioDevice: nil,
encryptionContext: nil
) )
) )
self.screencastCallContext = screencastCallContext self.screencastCallContext = screencastCallContext
@ -613,10 +628,12 @@ private final class PendingConferenceInvitationContext {
private var didNotifyEnded: Bool = false private var didNotifyEnded: Bool = false
init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, encryptionKey: Data, peerId: PeerId, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) { init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, peerId: PeerId, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) {
self.callSessionManager = callSessionManager self.callSessionManager = callSessionManager
self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey)) preconditionFailure()
/*self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey))
|> deliverOnMainQueue).startStrict(next: { [weak self] internalId in |> deliverOnMainQueue).startStrict(next: { [weak self] internalId in
guard let self else { guard let self else {
return return
@ -642,7 +659,7 @@ private final class PendingConferenceInvitationContext {
break break
} }
}) })
}) })*/
} }
deinit { deinit {
@ -786,7 +803,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void) private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
private(set) var initialCall: EngineGroupCallDescription? private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?
public let internalId: CallSessionInternalId public let internalId: CallSessionInternalId
public let peerId: EnginePeer.Id? public let peerId: EnginePeer.Id?
private let isChannel: Bool private let isChannel: Bool
@ -795,10 +812,28 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var ignorePreviousJoinAsPeerId: (PeerId, UInt32)? private var ignorePreviousJoinAsPeerId: (PeerId, UInt32)?
private var reconnectingAsPeer: EnginePeer? private var reconnectingAsPeer: EnginePeer?
public private(set) var callId: Int64?
public private(set) var hasVideo: Bool public private(set) var hasVideo: Bool
public private(set) var hasScreencast: Bool public private(set) var hasScreencast: Bool
private let isVideoEnabled: Bool private let isVideoEnabled: Bool
private let keyPair: TelegramKeyPair?
private final class E2ECallState {
var call: TdCall?
var pendingIncomingBroadcastBlocks: [Data] = []
}
private let e2eCall = Atomic<E2ECallState>(value: E2ECallState())
private var e2ePoll0Offset: Int?
private var e2ePoll0Timer: Foundation.Timer?
private var e2ePoll0Disposable: Disposable?
private var e2ePoll1Offset: Int?
private var e2ePoll1Timer: Foundation.Timer?
private var e2ePoll1Disposable: Disposable?
private var temporaryJoinTimestamp: Int32 private var temporaryJoinTimestamp: Int32
private var temporaryActivityTimestamp: Double? private var temporaryActivityTimestamp: Double?
private var temporaryActivityRank: Int? private var temporaryActivityRank: Int?
@ -865,6 +900,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
private let isNoiseSuppressionEnabledDisposable = MetaDisposable() private let isNoiseSuppressionEnabledDisposable = MetaDisposable()
private let e2eEncryptionKeyHashValue = ValuePromise<Data?>(nil)
public var e2eEncryptionKeyHash: Signal<Data?, NoError> {
return self.e2eEncryptionKeyHashValue.get()
}
private var isVideoMuted: Bool = false private var isVideoMuted: Bool = false
private let isVideoMutedDisposable = MetaDisposable() private let isVideoMutedDisposable = MetaDisposable()
@ -1053,18 +1093,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var screencastStateDisposable: Disposable? private var screencastStateDisposable: Disposable?
public let isStream: Bool public let isStream: Bool
private let encryptionKey: (key: Data, fingerprint: Int64)?
private let sharedAudioContext: SharedCallAudioContext? private let sharedAudioContext: SharedCallAudioContext?
private let conferenceFromCallId: CallId?
public let isConference: Bool public let isConference: Bool
public var encryptionKeyValue: Data? {
if let key = self.encryptionKey?.key {
return dataForEmojiRawKey(key)
} else {
return nil
}
}
private let conferenceSourceId: CallSessionInternalId? private let conferenceSourceId: CallSessionInternalId?
public var conferenceSource: CallSessionInternalId? { public var conferenceSource: CallSessionInternalId? {
@ -1085,15 +1116,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
audioSession: ManagedAudioSession, audioSession: ManagedAudioSession,
callKitIntegration: CallKitIntegration?, callKitIntegration: CallKitIntegration?,
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
initialCall: EngineGroupCallDescription?, initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?,
internalId: CallSessionInternalId, internalId: CallSessionInternalId,
peerId: EnginePeer.Id?, peerId: EnginePeer.Id?,
isChannel: Bool, isChannel: Bool,
invite: String?, invite: String?,
joinAsPeerId: EnginePeer.Id?, joinAsPeerId: EnginePeer.Id?,
isStream: Bool, isStream: Bool,
encryptionKey: (key: Data, fingerprint: Int64)?, keyPair: TelegramKeyPair?,
conferenceFromCallId: CallId?,
conferenceSourceId: CallSessionInternalId?, conferenceSourceId: CallSessionInternalId?,
isConference: Bool, isConference: Bool,
sharedAudioContext: SharedCallAudioContext? sharedAudioContext: SharedCallAudioContext?
@ -1105,15 +1135,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.getDeviceAccessData = getDeviceAccessData self.getDeviceAccessData = getDeviceAccessData
self.initialCall = initialCall self.initialCall = initialCall
self.callId = initialCall?.description.id
self.internalId = internalId self.internalId = internalId
self.peerId = peerId self.peerId = peerId
self.isChannel = isChannel self.isChannel = isChannel
self.invite = invite self.invite = invite
self.joinAsPeerId = joinAsPeerId ?? accountContext.account.peerId self.joinAsPeerId = joinAsPeerId ?? accountContext.account.peerId
self.schedulePending = initialCall == nil self.schedulePending = initialCall == nil
self.isScheduled = initialCall == nil || initialCall?.scheduleTimestamp != nil self.isScheduled = initialCall == nil || initialCall?.description.scheduleTimestamp != nil
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false) self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.description.title, scheduleTimestamp: initialCall?.description.scheduleTimestamp, subscribedToScheduled: initialCall?.description.subscribedToScheduled ?? false)
self.statePromise = ValuePromise(self.stateValue) self.statePromise = ValuePromise(self.stateValue)
self.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) self.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
@ -1122,10 +1154,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.hasVideo = false self.hasVideo = false
self.hasScreencast = false self.hasScreencast = false
self.isStream = isStream self.isStream = isStream
self.conferenceFromCallId = conferenceFromCallId
self.conferenceSourceId = conferenceSourceId self.conferenceSourceId = conferenceSourceId
self.isConference = isConference self.isConference = isConference
self.encryptionKey = encryptionKey self.keyPair = keyPair
var sharedAudioContext = sharedAudioContext var sharedAudioContext = sharedAudioContext
if sharedAudioContext == nil { if sharedAudioContext == nil {
@ -1283,14 +1314,47 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
} }
} else if case .joined = participantUpdate.participationStatusChange {
} else if let ssrc = participantUpdate.ssrc, self.ssrcMapping[ssrc] == nil {
} }
} }
case let .call(isTerminated, _, _, _, _, _, _): case let .call(isTerminated, _, _, _, _, _, _):
if isTerminated { if isTerminated {
self.markAsCanBeRemoved() self.markAsCanBeRemoved()
} }
case let .conferenceChainBlocks(subChainId, blocks, nextOffset):
if let _ = self.keyPair {
var processBlock = true
let updateBaseOffset = nextOffset - blocks.count
if subChainId == 0 {
if let e2ePoll0Offset = self.e2ePoll0Offset {
if e2ePoll0Offset == updateBaseOffset {
self.e2ePoll0Offset = nextOffset
} else if e2ePoll0Offset < updateBaseOffset {
self.e2ePoll(subChainId: subChainId)
} else {
processBlock = false
}
} else {
processBlock = false
}
} else if subChainId == 1 {
if let e2ePoll1Offset = self.e2ePoll1Offset {
if e2ePoll1Offset == updateBaseOffset {
self.e2ePoll1Offset = nextOffset
} else if e2ePoll1Offset < updateBaseOffset {
self.e2ePoll(subChainId: subChainId)
} else {
processBlock = false
}
} else {
processBlock = false
}
} else {
processBlock = false
}
if processBlock {
self.addE2EBlocks(blocks: blocks, subChainId: subChainId)
}
}
} }
} }
} }
@ -1321,7 +1385,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}) })
if let initialCall = initialCall, let peerId, let temporaryParticipantsContext = (self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl)?.impl.syncWith({ impl in if let initialCall = initialCall, let peerId, let temporaryParticipantsContext = (self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl)?.impl.syncWith({ impl in
impl.get(account: accountContext.account, engine: accountContext.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(id: initialCall.id, accessHash: initialCall.accessHash, title: initialCall.title, scheduleTimestamp: initialCall.scheduleTimestamp, subscribedToScheduled: initialCall.subscribedToScheduled, isStream: initialCall.isStream)) impl.get(account: accountContext.account, engine: accountContext.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(id: initialCall.description.id, accessHash: initialCall.description.accessHash, title: initialCall.description.title, scheduleTimestamp: initialCall.description.scheduleTimestamp, subscribedToScheduled: initialCall.description.subscribedToScheduled, isStream: initialCall.description.isStream))
}) { }) {
self.switchToTemporaryParticipantsContext(sourceContext: temporaryParticipantsContext.context.participantsContext, oldMyPeerId: self.joinAsPeerId) self.switchToTemporaryParticipantsContext(sourceContext: temporaryParticipantsContext.context.participantsContext, oldMyPeerId: self.joinAsPeerId)
} else { } else {
@ -1447,6 +1511,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.peerUpdatesSubscription?.dispose() self.peerUpdatesSubscription?.dispose()
self.screencastStateDisposable?.dispose() self.screencastStateDisposable?.dispose()
self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate() self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate()
self.e2ePoll0Timer?.invalidate()
self.e2ePoll0Disposable?.dispose()
self.e2ePoll1Timer?.invalidate()
self.e2ePoll1Disposable?.dispose()
} }
private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) { private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) {
@ -1471,7 +1539,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
if let sourceContext = sourceContext, let initialState = sourceContext.immediateState { if let sourceContext = sourceContext, let initialState = sourceContext.immediateState {
let temporaryParticipantsContext = self.accountContext.engine.calls.groupCall(peerId: self.peerId, myPeerId: myPeerId, id: sourceContext.id, accessHash: sourceContext.accessHash, state: initialState, previousServiceState: sourceContext.serviceState) let temporaryParticipantsContext = self.accountContext.engine.calls.groupCall(peerId: self.peerId, myPeerId: myPeerId, id: sourceContext.id, reference: sourceContext.reference, state: initialState, previousServiceState: sourceContext.serviceState)
self.temporaryParticipantsContext = temporaryParticipantsContext self.temporaryParticipantsContext = temporaryParticipantsContext
self.participantsContextStateDisposable.set((combineLatest(queue: .mainQueue(), self.participantsContextStateDisposable.set((combineLatest(queue: .mainQueue(),
myPeerData, myPeerData,
@ -1702,7 +1770,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
peerId: self.peerId, peerId: self.peerId,
myPeerId: self.joinAsPeerId, myPeerId: self.joinAsPeerId,
id: callInfo.id, id: callInfo.id,
accessHash: callInfo.accessHash, reference: .id(id: callInfo.id, accessHash: callInfo.accessHash),
state: GroupCallParticipantsContext.State( state: GroupCallParticipantsContext.State(
participants: [], participants: [],
nextParticipantsFetchOffset: nil, nextParticipantsFetchOffset: nil,
@ -1718,7 +1786,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
isVideoEnabled: callInfo.isVideoEnabled, isVideoEnabled: callInfo.isVideoEnabled,
unmutedVideoLimit: callInfo.unmutedVideoLimit, unmutedVideoLimit: callInfo.unmutedVideoLimit,
isStream: callInfo.isStream, isStream: callInfo.isStream,
upgradedPrivateCallId: callInfo.upgradedPrivateCallId,
version: 0 version: 0
), ),
previousServiceState: nil previousServiceState: nil
@ -1817,7 +1884,21 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.stateValue.subscribedToScheduled = state.subscribedToScheduled self.stateValue.subscribedToScheduled = state.subscribedToScheduled
self.stateValue.scheduleTimestamp = self.isScheduledStarted ? nil : state.scheduleTimestamp self.stateValue.scheduleTimestamp = self.isScheduledStarted ? nil : state.scheduleTimestamp
if state.scheduleTimestamp == nil && !self.isScheduledStarted { if state.scheduleTimestamp == nil && !self.isScheduledStarted {
self.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true, defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, isVideoEnabled: callInfo.isVideoEnabled, unmutedVideoLimit: callInfo.unmutedVideoLimit, isStream: callInfo.isStream, upgradedPrivateCallId: callInfo.upgradedPrivateCallId)), audioSessionControl: self.audioSessionControl) self.updateSessionState(internalState: .active(GroupCallInfo(
id: callInfo.id,
accessHash: callInfo.accessHash,
participantCount: state.totalCount,
streamDcId: callInfo.streamDcId,
title: state.title,
scheduleTimestamp: nil,
subscribedToScheduled: false,
recordingStartTimestamp: nil,
sortAscending: true,
defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted,
isVideoEnabled: callInfo.isVideoEnabled,
unmutedVideoLimit: callInfo.unmutedVideoLimit,
isStream: callInfo.isStream
)), audioSessionControl: self.audioSessionControl)
} else { } else {
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo( self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
id: callInfo.id, id: callInfo.id,
@ -1832,8 +1913,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled, isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit, unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream, isStream: callInfo.isStream
upgradedPrivateCallId: callInfo.upgradedPrivateCallId
)))) ))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState( self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -1929,9 +2009,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
outgoingAudioBitrateKbit = Int32(value) outgoingAudioBitrateKbit = Int32(value)
} }
var encryptionKey: Data?
encryptionKey = self.encryptionKey?.key
let contextAudioSessionActive: Signal<Bool, NoError> let contextAudioSessionActive: Signal<Bool, NoError>
if self.sharedAudioContext != nil { if self.sharedAudioContext != nil {
contextAudioSessionActive = .single(true) contextAudioSessionActive = .single(true)
@ -1944,6 +2021,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
audioIsActiveByDefault = false audioIsActiveByDefault = false
} }
var encryptionContext: OngoingGroupCallEncryptionContext?
if self.isConference {
class OngoingGroupCallEncryptionContextImpl: OngoingGroupCallEncryptionContext {
private let e2eCall: Atomic<E2ECallState>
init(e2eCall: Atomic<E2ECallState>) {
self.e2eCall = e2eCall
}
func encrypt(message: Data) -> Data? {
return self.e2eCall.with({ $0.call?.encrypt(message) })
}
func decrypt(message: Data) -> Data? {
return self.e2eCall.with({ $0.call?.decrypt(message) })
}
}
encryptionContext = OngoingGroupCallEncryptionContextImpl(e2eCall: self.e2eCall)
}
genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: contextAudioSessionActive, video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: contextAudioSessionActive, video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in
let disposable = MetaDisposable() let disposable = MetaDisposable()
Queue.mainQueue().async { Queue.mainQueue().async {
@ -1969,7 +2066,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
self.onMutedSpeechActivityDetected?(value) self.onMutedSpeechActivityDetected?(value)
} }
}, encryptionKey: encryptionKey, isConference: self.isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: self.isStream, sharedAudioDevice: self.sharedAudioContext?.audioDevice)) }, isConference: self.isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: self.isStream, sharedAudioDevice: self.sharedAudioContext?.audioDevice, encryptionContext: encryptionContext))
} }
self.genericCallContext = genericCallContext self.genericCallContext = genericCallContext
@ -2085,17 +2182,50 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
peerAdminIds = .single([]) peerAdminIds = .single([])
} }
var generateE2EData: ((Data?) -> JoinGroupCallE2E?)?
if let keyPair = self.keyPair {
if let mappedKeyPair = TdKeyPair(keyId: keyPair.id, publicKey: keyPair.publicKey.data) {
let userId = self.joinAsPeerId.id._internalGetInt64Value()
generateE2EData = { block -> JoinGroupCallE2E? in
if let block {
guard let resultBlock = tdGenerateSelfAddBlock(mappedKeyPair, userId, block) else {
return nil
}
return JoinGroupCallE2E(
publicKey: keyPair.publicKey,
block: resultBlock
)
} else {
guard let resultBlock = tdGenerateZeroBlock(mappedKeyPair, userId) else {
return nil
}
return JoinGroupCallE2E(
publicKey: keyPair.publicKey,
block: resultBlock
)
}
}
}
}
let reference: InternalGroupCallReference
if let initialCall = self.initialCall {
reference = initialCall.reference
} else {
reference = .id(id: callInfo.id, accessHash: callInfo.accessHash)
}
self.currentLocalSsrc = ssrc self.currentLocalSsrc = ssrc
self.requestDisposable.set((self.accountContext.engine.calls.joinGroupCall( self.requestDisposable.set((self.accountContext.engine.calls.joinGroupCall(
peerId: self.peerId, peerId: self.peerId,
joinAs: self.joinAsPeerId, joinAs: self.joinAsPeerId,
callId: callInfo.id, callId: callInfo.id,
accessHash: callInfo.accessHash, reference: reference,
preferMuted: true, preferMuted: true,
joinPayload: joinPayload, joinPayload: joinPayload,
peerAdminIds: peerAdminIds, peerAdminIds: peerAdminIds,
inviteHash: self.invite, inviteHash: self.invite,
keyFingerprint: self.encryptionKey?.fingerprint generateE2E: generateE2EData
) )
|> deliverOnMainQueue).start(next: { [weak self] joinCallResult in |> deliverOnMainQueue).start(next: { [weak self] joinCallResult in
guard let self else { guard let self else {
@ -2153,6 +2283,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
self.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: self.audioSessionControl) self.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: self.audioSessionControl)
self.e2ePoll(subChainId: 0)
self.e2ePoll(subChainId: 1)
}, error: { [weak self] error in }, error: { [weak self] error in
guard let self else { guard let self else {
return return
@ -2394,11 +2527,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
serviceState = participantsContext.serviceState serviceState = participantsContext.serviceState
} }
let reference: InternalGroupCallReference
if let initialCall = self.initialCall {
reference = initialCall.reference
} else {
reference = .id(id: callInfo.id, accessHash: callInfo.accessHash)
}
let participantsContext = self.accountContext.engine.calls.groupCall( let participantsContext = self.accountContext.engine.calls.groupCall(
peerId: self.peerId, peerId: self.peerId,
myPeerId: self.joinAsPeerId, myPeerId: self.joinAsPeerId,
id: callInfo.id, id: callInfo.id,
accessHash: callInfo.accessHash, reference: reference,
state: initialState, state: initialState,
previousServiceState: serviceState previousServiceState: serviceState
) )
@ -2701,8 +2841,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled, isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit, unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream, isStream: callInfo.isStream
upgradedPrivateCallId: callInfo.upgradedPrivateCallId
)))) ))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState( self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -2759,6 +2898,117 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
private func addE2EBlocks(blocks: [Data], subChainId: Int) {
guard let initialCall = self.initialCall, let keyPair = self.keyPair else {
return
}
let (outBlocks, outEmoji) = self.e2eCall.with({ callState -> ([Data], Data) in
if let call = callState.call {
for block in blocks {
if subChainId == 0 {
call.applyBlock(block)
} else if subChainId == 1 {
call.applyBroadcastBlock(block)
}
}
return (call.takeOutgoingBroadcastBlocks(), call.emojiState())
} else {
if subChainId == 0 {
guard let block = blocks.last else {
return ([], Data())
}
guard let keyPair = TdKeyPair(keyId: keyPair.id, publicKey: keyPair.publicKey.data) else {
return ([], Data())
}
guard let call = TdCall.make(with: keyPair, latestBlock: block) else {
return ([], Data())
}
callState.call = call
for block in callState.pendingIncomingBroadcastBlocks {
call.applyBroadcastBlock(block)
}
callState.pendingIncomingBroadcastBlocks.removeAll()
return (call.takeOutgoingBroadcastBlocks(), call.emojiState())
} else if subChainId == 1 {
callState.pendingIncomingBroadcastBlocks.append(contentsOf: blocks)
return ([], Data())
} else {
return ([], Data())
}
}
})
self.e2eEncryptionKeyHashValue.set(outEmoji.isEmpty ? nil : outEmoji)
//TODO:release queue
for outBlock in outBlocks {
let _ = self.accountContext.engine.calls.sendConferenceCallBroadcast(callId: initialCall.description.id, accessHash: initialCall.description.accessHash, block: outBlock).startStandalone()
}
}
private func e2ePoll(subChainId: Int) {
guard let initialCall = self.initialCall else {
return
}
let offset: Int?
if subChainId == 0 {
offset = self.e2ePoll0Offset
self.e2ePoll0Disposable?.dispose()
} else if subChainId == 1 {
offset = self.e2ePoll1Offset
self.e2ePoll1Disposable?.dispose()
} else {
return
}
let disposable = (self.accountContext.engine.calls.pollConferenceCallBlockchain(reference: initialCall.reference, subChainId: subChainId, offset: offset ?? 0, limit: 10)
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
guard let self else {
return
}
var delayPoll = true
if let result {
if subChainId == 0 {
if self.e2ePoll0Offset != result.nextOffset {
self.e2ePoll0Offset = result.nextOffset
delayPoll = false
}
} else if subChainId == 1 {
if self.e2ePoll1Offset != result.nextOffset {
self.e2ePoll1Offset = result.nextOffset
delayPoll = false
}
}
self.addE2EBlocks(blocks: result.blocks, subChainId: subChainId)
}
if subChainId == 0 {
self.e2ePoll0Timer?.invalidate()
self.e2ePoll0Timer = Foundation.Timer.scheduledTimer(withTimeInterval: delayPoll ? 1.0 : 0.0, repeats: false, block: { [weak self] _ in
guard let self else {
return
}
self.e2ePoll(subChainId: 0)
})
} else if subChainId == 1 {
self.e2ePoll1Timer?.invalidate()
self.e2ePoll1Timer = Foundation.Timer.scheduledTimer(withTimeInterval: delayPoll ? 1.0 : 0.0, repeats: false, block: { [weak self] _ in
guard let self else {
return
}
self.e2ePoll(subChainId: 1)
})
}
})
if subChainId == 0 {
self.e2ePoll0Disposable = disposable
} else if subChainId == 1 {
self.e2ePoll1Disposable = disposable
}
}
private func activateIncomingAudioIfNeeded() { private func activateIncomingAudioIfNeeded() {
if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext { if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext {
groupCall.activateIncomingAudio() groupCall.activateIncomingAudio()
@ -2807,7 +3057,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
if !remainingSsrcs.isEmpty, let callInfo = self.internalState.callInfo { if !remainingSsrcs.isEmpty, let callInfo = self.internalState.callInfo {
return (self.accountContext.engine.calls.getGroupCallParticipants(callId: callInfo.id, accessHash: callInfo.accessHash, offset: "", ssrcs: Array(remainingSsrcs), limit: 100, sortAscending: callInfo.sortAscending) return (self.accountContext.engine.calls.getGroupCallParticipants(reference: .id(id: callInfo.id, accessHash: callInfo.accessHash), offset: "", ssrcs: Array(remainingSsrcs), limit: 100, sortAscending: callInfo.sortAscending)
|> deliverOnMainQueue).start(next: { state in |> deliverOnMainQueue).start(next: { state in
extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: state.participants, into: &result) extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: state.participants, into: &result)
@ -2913,7 +3163,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
self.markedAsCanBeRemoved = true self.markedAsCanBeRemoved = true
self.genericCallContext?.stop(account: self.account, reportCallId: self.conferenceFromCallId, debugLog: self.debugLog) self.genericCallContext?.stop(account: self.account, reportCallId: nil, debugLog: self.debugLog)
self.screencastIPCContext?.disableScreencast(account: self.account) self.screencastIPCContext?.disableScreencast(account: self.account)
self._canBeRemoved.set(.single(true)) self._canBeRemoved.set(.single(true))
@ -3615,7 +3865,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let context = self.accountContext let context = self.accountContext
let currentCall: Signal<GroupCallInfo?, CallError> let currentCall: Signal<GroupCallInfo?, CallError>
if let initialCall = self.initialCall { if let initialCall = self.initialCall {
currentCall = context.engine.calls.getCurrentGroupCall(callId: initialCall.id, accessHash: initialCall.accessHash) currentCall = context.engine.calls.getCurrentGroupCall(reference: initialCall.reference)
|> mapError { _ -> CallError in |> mapError { _ -> CallError in
return .generic return .generic
} }
@ -3623,7 +3873,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return summary?.info return summary?.info
} }
} else if case let .active(callInfo) = self.internalState { } else if case let .active(callInfo) = self.internalState {
currentCall = context.engine.calls.getCurrentGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash) currentCall = context.engine.calls.getCurrentGroupCall(reference: .id(id: callInfo.id, accessHash: callInfo.accessHash))
|> mapError { _ -> CallError in |> mapError { _ -> CallError in
return .generic return .generic
} }
@ -3662,7 +3912,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
if let value = value { if let value = value {
self.initialCall = EngineGroupCallDescription(id: value.id, accessHash: value.accessHash, title: value.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: value.isStream) var reference: InternalGroupCallReference = .id(id: value.id, accessHash: value.accessHash)
if let current = self.initialCall {
switch current.reference {
case .message, .link:
reference = current.reference
default:
break
}
}
self.initialCall = (EngineGroupCallDescription(id: value.id, accessHash: value.accessHash, title: value.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: value.isStream), reference)
self.callId = value.id
self.updateSessionState(internalState: .active(value), audioSessionControl: self.audioSessionControl) self.updateSessionState(internalState: .active(value), audioSessionControl: self.audioSessionControl)
} else { } else {
@ -3673,7 +3933,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
public func invitePeer(_ peerId: PeerId) -> Bool { public func invitePeer(_ peerId: PeerId) -> Bool {
if self.isConference { if self.isConference {
guard let initialCall = self.initialCall, let encryptionKey = self.encryptionKey else { guard let initialCall = self.initialCall else {
return false
}
//TODO:release
let _ = self.accountContext.engine.calls.inviteConferenceCallParticipant(callId: initialCall.description.id, accessHash: initialCall.description.accessHash, peerId: peerId).start()
return false
/*guard let initialCall = self.initialCall else {
return false return false
} }
if conferenceInvitationContexts[peerId] != nil { if conferenceInvitationContexts[peerId] != nil {
@ -3685,7 +3952,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let invitationContext = PendingConferenceInvitationContext( let invitationContext = PendingConferenceInvitationContext(
callSessionManager: self.accountContext.account.callSessionManager, callSessionManager: self.accountContext.account.callSessionManager,
groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash), groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash),
encryptionKey: encryptionKey.key,
peerId: peerId, peerId: peerId,
onStateUpdated: { state in onStateUpdated: { state in
onStateUpdated?(state) onStateUpdated?(state)
@ -3733,7 +3999,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
return false return false*/
} else { } else {
guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(where: { $0.id == peerId }) else { guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(where: { $0.id == peerId }) else {
return false return false
@ -3750,9 +4016,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
func setConferenceInvitedPeers(_ peerIds: [PeerId]) { func setConferenceInvitedPeers(_ peerIds: [PeerId]) {
self.invitedPeersValue = peerIds.map { //TODO:release
/*self.invitedPeersValue = peerIds.map {
PresentationGroupCallInvitedPeer(id: $0, state: .requesting) PresentationGroupCallInvitedPeer(id: $0, state: .requesting)
} }*/
} }
public func removedPeer(_ peerId: PeerId) { public func removedPeer(_ peerId: PeerId) {
@ -3771,6 +4038,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
public var inviteLinks: Signal<GroupCallInviteLinks?, NoError> { public var inviteLinks: Signal<GroupCallInviteLinks?, NoError> {
let engine = self.accountContext.engine let engine = self.accountContext.engine
let initialCall = self.initialCall
let isConference = self.isConference
return self.state return self.state
|> map { state -> PeerId in |> map { state -> PeerId in
@ -3788,7 +4057,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
|> mapToSignal { state in |> mapToSignal { state in
if let callInfo = state.callInfo { if let callInfo = state.callInfo {
return engine.calls.groupCallInviteLinks(callId: callInfo.id, accessHash: callInfo.accessHash) let reference: InternalGroupCallReference
if let initialCall = initialCall {
reference = initialCall.reference
} else {
reference = .id(id: callInfo.id, accessHash: callInfo.accessHash)
}
return engine.calls.groupCallInviteLinks(reference: reference, isConference: isConference)
} else { } else {
return .complete() return .complete()
} }
@ -3975,3 +4251,24 @@ private final class InProcessScreencastContext: ScreencastContext {
self.context.setJoinResponse(payload: clientParams) self.context.setJoinResponse(payload: clientParams)
} }
} }
public final class TelegramE2EEncryptionProviderImpl: TelegramE2EEncryptionProvider {
public static let shared = TelegramE2EEncryptionProviderImpl()
public func generateKeyPair() -> TelegramKeyPair? {
guard let keyPair = TdKeyPair.generate() else {
return nil
}
guard let publicKey = TelegramPublicKey(data: keyPair.publicKey) else {
return nil
}
return TelegramKeyPair(id: keyPair.keyId, publicKey: publicKey)
}
public func generateCallZeroBlock(keyPair: TelegramKeyPair, userId: Int64) -> Data? {
guard let keyPair = TdKeyPair(keyId: keyPair.id, publicKey: keyPair.publicKey.data) else {
return nil
}
return tdGenerateZeroBlock(keyPair, userId)
}
}

View File

@ -24,6 +24,7 @@ import TelegramAudio
import LegacyComponents import LegacyComponents
import TooltipUI import TooltipUI
import BlurredBackgroundComponent import BlurredBackgroundComponent
import CallsEmoji
extension VideoChatCall { extension VideoChatCall {
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
@ -262,6 +263,9 @@ final class VideoChatScreenComponent: Component {
var speakingParticipantPeers: [EnginePeer] = [] var speakingParticipantPeers: [EnginePeer] = []
var visibleParticipants: Set<EnginePeer.Id> = Set() var visibleParticipants: Set<EnginePeer.Id> = Set()
var encryptionKeyEmoji: [String]?
var encryptionKeyEmojiDisposable: Disposable?
let isPresentedValue = ValuePromise<Bool>(false, ignoreRepeated: true) let isPresentedValue = ValuePromise<Bool>(false, ignoreRepeated: true)
var applicationStateDisposable: Disposable? var applicationStateDisposable: Disposable?
@ -313,6 +317,7 @@ final class VideoChatScreenComponent: Component {
self.updateAvatarDisposable.dispose() self.updateAvatarDisposable.dispose()
self.inviteDisposable.dispose() self.inviteDisposable.dispose()
self.conferenceCallStateDisposable?.dispose() self.conferenceCallStateDisposable?.dispose()
self.encryptionKeyEmojiDisposable?.dispose()
} }
func animateIn() { func animateIn() {
@ -647,9 +652,6 @@ final class VideoChatScreenComponent: Component {
guard case let .group(groupCall) = self.currentCall else { guard case let .group(groupCall) = self.currentCall else {
return return
} }
guard let peerId = groupCall.peerId else {
return
}
let formatSendTitle: (String) -> String = { string in let formatSendTitle: (String) -> String = { string in
var string = string var string = string
@ -663,6 +665,7 @@ final class VideoChatScreenComponent: Component {
return string return string
} }
if let peerId = groupCall.peerId {
let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
@ -739,6 +742,58 @@ final class VideoChatScreenComponent: Component {
} }
environment.controller()?.present(shareController, in: .window(.root)) environment.controller()?.present(shareController, in: .window(.root))
}) })
} else if groupCall.isConference {
guard let environment = self.environment else {
return
}
let shareController = ShareController(context: groupCall.accountContext, subject: .url(inviteLinks.listenerLink), forceTheme: environment.theme, forcedActionTitle: environment.strings.VoiceChat_CopyInviteLink)
shareController.completed = { [weak self] peerIds in
guard let self, case let .group(groupCall) = self.currentCall else {
return
}
let _ = (groupCall.accountContext.engine.data.get(
EngineDataList(
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
)
)
|> deliverOnMainQueue).start(next: { [weak self] peerList in
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
return
}
let peers = peerList.compactMap { $0 }
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let text: String
var isSavedMessages = false
if peers.count == 1, let peer = peers.first {
isSavedMessages = peer.id == groupCall.accountContext.account.peerId
let peerName = peer.id == groupCall.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.VoiceChat_ForwardTooltip_Chat(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == groupCall.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == groupCall.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.VoiceChat_ForwardTooltip_TwoChats(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.VoiceChat_ForwardTooltip_ManyChats(peerName, "\(peers.count - 1)").string
} else {
text = ""
}
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: isSavedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})
}
shareController.actionCompleted = { [weak self] in
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
return
}
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}
environment.controller()?.present(shareController, in: .window(.root))
}
} }
private func onCameraPressed() { private func onCameraPressed() {
@ -1325,6 +1380,28 @@ final class VideoChatScreenComponent: Component {
} }
}) })
self.encryptionKeyEmojiDisposable?.dispose()
self.encryptionKeyEmojiDisposable = (groupCall.e2eEncryptionKeyHash
|> deliverOnMainQueue).startStrict(next: { [weak self] e2eEncryptionKeyHash in
guard let self else {
return
}
var encryptionKeyEmoji: [String]?
if let e2eEncryptionKeyHash, e2eEncryptionKeyHash.count >= 32 {
if let value = stringForEmojiHashOfData(e2eEncryptionKeyHash.prefix(32), 4) {
if !value.isEmpty {
encryptionKeyEmoji = value
}
}
}
if self.encryptionKeyEmoji != encryptionKeyEmoji {
self.encryptionKeyEmoji = encryptionKeyEmoji
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
}
})
self.conferenceCallStateDisposable?.dispose() self.conferenceCallStateDisposable?.dispose()
self.conferenceCallStateDisposable = nil self.conferenceCallStateDisposable = nil
@ -1961,7 +2038,7 @@ final class VideoChatScreenComponent: Component {
} }
var encryptionKeyFrame: CGRect? var encryptionKeyFrame: CGRect?
if component.initialCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug { if let encryptionKeyEmoji = self.encryptionKeyEmoji {
navigationHeight -= 2.0 navigationHeight -= 2.0
let encryptionKey: ComponentView<Empty> let encryptionKey: ComponentView<Empty>
var encryptionKeyTransition = transition var encryptionKeyTransition = transition
@ -1978,7 +2055,7 @@ final class VideoChatScreenComponent: Component {
component: AnyComponent(VideoChatEncryptionKeyComponent( component: AnyComponent(VideoChatEncryptionKeyComponent(
theme: environment.theme, theme: environment.theme,
strings: environment.strings, strings: environment.strings,
emoji: ["👌", "🧡", "🌹", "🤷"], emoji: encryptionKeyEmoji,
isExpanded: self.isEncryptionKeyExpanded, isExpanded: self.isEncryptionKeyExpanded,
tapAction: { [weak self] in tapAction: { [weak self] in
guard let self else { guard let self else {
@ -2283,10 +2360,17 @@ final class VideoChatScreenComponent: Component {
} }
if let encryptionKeyView = self.encryptionKey?.view, let encryptionKeyFrame { if let encryptionKeyView = self.encryptionKey?.view, let encryptionKeyFrame {
var encryptionKeyTransition = transition
if encryptionKeyView.superview == nil { if encryptionKeyView.superview == nil {
encryptionKeyTransition = encryptionKeyTransition.withAnimation(.none)
self.containerView.addSubview(encryptionKeyView) self.containerView.addSubview(encryptionKeyView)
ComponentTransition.immediate.setScale(view: encryptionKeyView, scale: 0.001)
encryptionKeyView.alpha = 0.0
} }
transition.setFrame(view: encryptionKeyView, frame: encryptionKeyFrame) encryptionKeyTransition.setPosition(view: encryptionKeyView, position: encryptionKeyFrame.center)
encryptionKeyTransition.setBounds(view: encryptionKeyView, bounds: CGRect(origin: CGPoint(), size: encryptionKeyFrame.size))
transition.setScale(view: encryptionKeyView, scale: 1.0)
alphaTransition.setAlpha(view: encryptionKeyView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) alphaTransition.setAlpha(view: encryptionKeyView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
if self.isEncryptionKeyExpanded { if self.isEncryptionKeyExpanded {

View File

@ -16,7 +16,7 @@ extension VideoChatScreenComponent.View {
return return
} }
if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug { /*if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else {
return return
} }
@ -39,7 +39,7 @@ extension VideoChatScreenComponent.View {
}) })
self.environment?.controller()?.present(controller, in: .window(.root), with: nil) self.environment?.controller()?.present(controller, in: .window(.root), with: nil)
return return
} }*/
if groupCall.isConference { if groupCall.isConference {
var disablePeerIds: [EnginePeer.Id] = [] var disablePeerIds: [EnginePeer.Id] = []

View File

@ -175,7 +175,7 @@ extension VideoChatScreenComponent.View {
} }
} }
if case let .group(groupCall) = currentCall, let encryptionKey = groupCall.encryptionKeyValue { /*if case let .group(groupCall) = currentCall, let encryptionKey = groupCall.encryptionKeyValue {
//TODO:localize //TODO:localize
let emojiKey = resolvedEmojiKey(data: encryptionKey) let emojiKey = resolvedEmojiKey(data: encryptionKey)
items.append(.action(ContextMenuActionItem(text: "Encryption Key", textLayout: .secondLineWithValue(emojiKey.joined(separator: "")), icon: { theme in items.append(.action(ContextMenuActionItem(text: "Encryption Key", textLayout: .secondLineWithValue(emojiKey.joined(separator: "")), icon: { theme in
@ -202,7 +202,7 @@ extension VideoChatScreenComponent.View {
environment.controller()?.present(alertController, in: .window(.root)) environment.controller()?.present(alertController, in: .window(.root))
}))) })))
items.append(.separator) items.append(.separator)
} }*/
if let (availableOutputs, currentOutput) = self.audioOutputState, availableOutputs.count > 1 { if let (availableOutputs, currentOutput) = self.audioOutputState, availableOutputs.count > 1 {
var currentOutputTitle = "" var currentOutputTitle = ""

View File

@ -77,7 +77,7 @@ public final class VoiceChatJoinScreen: ViewController {
if let call = call { if let call = call {
let peer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) let peer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> castError(GetCurrentGroupCallError.self) |> castError(GetCurrentGroupCallError.self)
return combineLatest(peer, context.engine.calls.getCurrentGroupCall(callId: call.id, accessHash: call.accessHash)) return combineLatest(peer, context.engine.calls.getCurrentGroupCall(reference: .id(id: call.id, accessHash: call.accessHash)))
|> map { peer, call -> (EnginePeer, GroupCallSummary)? in |> map { peer, call -> (EnginePeer, GroupCallSummary)? in
if let peer = peer, let call = call { if let peer = peer, let call = call {
return (peer, call) return (peer, call)

View File

@ -116,6 +116,7 @@ enum AccountStateMutationOperation {
case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?)
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32) case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
case UpdateGroupCall(peerId: PeerId?, call: Api.GroupCall) case UpdateGroupCall(peerId: PeerId?, call: Api.GroupCall)
case UpdateGroupCallChainBlocks(id: Int64, accessHash: Int64, subChainId: Int32, blocks: [Data], nextOffset: Int32)
case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?)
case UpdateAttachMenuBots case UpdateAttachMenuBots
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
@ -403,6 +404,10 @@ struct AccountMutableState {
self.addOperation(.UpdateGroupCall(peerId: peerId, call: call)) self.addOperation(.UpdateGroupCall(peerId: peerId, call: call))
} }
mutating func updateGroupCallChainBlocks(id: Int64, accessHash: Int64, subChainId: Int32, blocks: [Data], nextOffset: Int32) {
self.addOperation(.UpdateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks, nextOffset: nextOffset))
}
mutating func updateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) { mutating func updateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) {
self.addOperation(.UpdateAutoremoveTimeout(peer: peer, value: value)) self.addOperation(.UpdateAutoremoveTimeout(peer: peer, value: value))
} }
@ -709,7 +714,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) { mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery:
break break
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
for message in messages { for message in messages {
@ -858,6 +863,7 @@ struct AccountReplayedFinalState {
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let sentScheduledMessageIds: Set<MessageId> let sentScheduledMessageIds: Set<MessageId>
let reportMessageDelivery: Set<MessageId> let reportMessageDelivery: Set<MessageId>
let addedConferenceInvitationMessagesIds: [MessageId]
} }
struct AccountFinalStateEvents { struct AccountFinalStateEvents {
@ -889,12 +895,13 @@ struct AccountFinalStateEvents {
let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let reportMessageDelivery: Set<MessageId> let reportMessageDelivery: Set<MessageId>
let addedConferenceInvitationMessagesIds: [MessageId]
var isEmpty: Bool { var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty
} }
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set()) { init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) {
self.addedIncomingMessageIds = addedIncomingMessageIds self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds
@ -923,6 +930,7 @@ struct AccountFinalStateEvents {
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
self.sentScheduledMessageIds = sentScheduledMessageIds self.sentScheduledMessageIds = sentScheduledMessageIds
self.reportMessageDelivery = reportMessageDelivery self.reportMessageDelivery = reportMessageDelivery
self.addedConferenceInvitationMessagesIds = addedConferenceInvitationMessagesIds
} }
init(state: AccountReplayedFinalState) { init(state: AccountReplayedFinalState) {
@ -954,6 +962,7 @@ struct AccountFinalStateEvents {
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
self.sentScheduledMessageIds = state.sentScheduledMessageIds self.sentScheduledMessageIds = state.sentScheduledMessageIds
self.reportMessageDelivery = state.reportMessageDelivery self.reportMessageDelivery = state.reportMessageDelivery
self.addedConferenceInvitationMessagesIds = state.addedConferenceInvitationMessagesIds
} }
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -989,6 +998,37 @@ struct AccountFinalStateEvents {
var reportMessageDelivery = self.reportMessageDelivery var reportMessageDelivery = self.reportMessageDelivery
reportMessageDelivery.formUnion(other.reportMessageDelivery) reportMessageDelivery.formUnion(other.reportMessageDelivery)
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds, reportMessageDelivery: reportMessageDelivery) var addedConferenceInvitationMessagesIds = self.addedConferenceInvitationMessagesIds
addedConferenceInvitationMessagesIds.append(contentsOf: other.addedConferenceInvitationMessagesIds)
return AccountFinalStateEvents(
addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds,
addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents,
wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds,
deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds,
updatedTypingActivities: self.updatedTypingActivities,
updatedWebpages: self.updatedWebpages,
updatedCalls: self.updatedCalls + other.updatedCalls,
addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData,
updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants,
storyUpdates: self.storyUpdates + other.storyUpdates,
isContactUpdates: self.isContactUpdates + other.isContactUpdates,
displayAlerts: self.displayAlerts + other.displayAlerts,
dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews,
delayNotificatonsUntil: delayNotificatonsUntil,
updatedMaxMessageId: updatedMaxMessageId,
updatedQts: updatedQts,
externallyUpdatedPeerId: externallyUpdatedPeerId,
authorizationListUpdated: authorizationListUpdated,
updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates,uniquingKeysWith: { lhs, _ in lhs }),
updateConfig: updateConfig,
isPremiumUpdated: isPremiumUpdated,
updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }),
updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }),
updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }),
sentScheduledMessageIds: sentScheduledMessageIds,
reportMessageDelivery: reportMessageDelivery,
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds
)
} }
} }

View File

@ -262,6 +262,10 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
result.append(boostPeer.peerId) result.append(boostPeer.peerId)
case let .messageActionPaymentRefunded(_, peer, _, _, _, _): case let .messageActionPaymentRefunded(_, peer, _, _, _, _):
result.append(peer.peerId) result.append(peer.peerId)
case let .messageActionConferenceCall(_, _, _, otherParticipants):
if let otherParticipants {
result.append(contentsOf: otherParticipants.map(\.peerId))
}
} }
return result return result

View File

@ -80,6 +80,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
switch call { switch call {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: nil, duration: duration)) return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: nil, duration: duration))
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
} }
case let .messageActionInviteToGroupCall(call, userIds): case let .messageActionInviteToGroupCall(call, userIds):
switch call { switch call {
@ -87,6 +89,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .inviteToGroupPhoneCall(callId: id, accessHash: accessHash, peerIds: userIds.map { userId in return TelegramMediaAction(action: .inviteToGroupPhoneCall(callId: id, accessHash: accessHash, peerIds: userIds.map { userId in
PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
})) }))
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
} }
case let .messageActionSetMessagesTTL(_, period, autoSettingFrom): case let .messageActionSetMessagesTTL(_, period, autoSettingFrom):
return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: period, autoSettingSource: autoSettingFrom.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) })) return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: period, autoSettingSource: autoSettingFrom.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }))
@ -94,6 +98,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
switch call { switch call {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil)) return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil))
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
} }
case let .messageActionSetChatTheme(emoji): case let .messageActionSetChatTheme(emoji):
return TelegramMediaAction(action: .setChatTheme(emoji: emoji)) return TelegramMediaAction(action: .setChatTheme(emoji: emoji))
@ -195,6 +201,12 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars)) return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
case let .messageActionPaidMessagesPrice(stars): case let .messageActionPaidMessagesPrice(stars):
return TelegramMediaAction(action: .paidMessagesPriceEdited(stars: stars)) return TelegramMediaAction(action: .paidMessagesPriceEdited(stars: stars))
case let .messageActionConferenceCall(_, callId, duration, otherParticipants):
return TelegramMediaAction(action: .conferenceCall(
callId: callId,
duration: duration,
otherParticipants: otherParticipants.flatMap({ return $0.map(\.peerId) }) ?? []
))
} }
} }
@ -209,7 +221,7 @@ extension PhoneCallDiscardReason {
self = .hangup self = .hangup
case .phoneCallDiscardReasonMissed: case .phoneCallDiscardReasonMissed:
self = .missed self = .missed
case .phoneCallDiscardReasonAllowGroupCall: case .phoneCallDiscardReasonMigrateConferenceCall:
self = .hangup self = .hangup
} }
} }

View File

@ -1629,10 +1629,16 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
switch call { switch call {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
updatedState.updateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version) updatedState.updateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
break
} }
case let .updateGroupCall(_, channelId, call): case let .updateGroupCall(_, channelId, call):
updatedState.updateGroupCall(peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, call: call) updatedState.updateGroupCall(peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, call: call)
updatedState.updateGroupCall(peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value($0)) }, call: call) updatedState.updateGroupCall(peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value($0)) }, call: call)
case let .updateGroupCallChainBlocks(call, subChainId, blocks, nextOffset):
if case let .inputGroupCall(id, accessHash) = call {
updatedState.updateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks.map { $0.makeData() }, nextOffset: nextOffset)
}
case let .updatePeerHistoryTTL(_, peer, ttl): case let .updatePeerHistoryTTL(_, peer, ttl):
updatedState.updateAutoremoveTimeout(peer: peer, value: CachedPeerAutoremoveTimeout.Value(ttl)) updatedState.updateAutoremoveTimeout(peer: peer, value: CachedPeerAutoremoveTimeout.Value(ttl))
case let .updateLangPackTooLong(langCode): case let .updateLangPackTooLong(langCode):
@ -3322,7 +3328,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddQuickReplyMessages: OptimizeAddMessagesState? var currentAddQuickReplyMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -3505,6 +3511,8 @@ func replayFinalState(
var readInboxCloudMessageIds: [PeerId: Int32] = [:] var readInboxCloudMessageIds: [PeerId: Int32] = [:]
var addedOperationIncomingMessageIds: [MessageId] = [] var addedOperationIncomingMessageIds: [MessageId] = []
var addedConferenceInvitationMessagesIds: [MessageId] = []
for operation in finalState.state.operations { for operation in finalState.state.operations {
switch operation { switch operation {
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
@ -3526,6 +3534,10 @@ func replayFinalState(
recordPeerActivityTimestamp(peerId: authorId, timestamp: message.timestamp, into: &peerActivityTimestamps) recordPeerActivityTimestamp(peerId: authorId, timestamp: message.timestamp, into: &peerActivityTimestamps)
} }
} }
if id.namespace == Namespaces.Message.Cloud && id.peerId.namespace == Namespaces.Peer.CloudUser {
addedConferenceInvitationMessagesIds.append(id)
}
} }
if message.flags.contains(.WasScheduled) { if message.flags.contains(.WasScheduled) {
wasOperationScheduledMessageIds.append(id) wasOperationScheduledMessageIds.append(id)
@ -4505,7 +4517,7 @@ func replayFinalState(
} }
switch call { switch call {
case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _, _): case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _):
let isMuted = (flags & (1 << 1)) != 0 let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0 let canChange = (flags & (1 << 2)) != 0
let isVideoEnabled = (flags & (1 << 9)) != 0 let isVideoEnabled = (flags & (1 << 9)) != 0
@ -4544,6 +4556,11 @@ func replayFinalState(
}) })
} }
} }
case let .UpdateGroupCallChainBlocks(id, _, subChainId, blocks, nextOffset):
updatedGroupCallParticipants.append((
id,
.conferenceChainBlocks(subChainId: Int(subChainId), blocks: blocks, nextOffset: Int(nextOffset))
))
case let .UpdateAutoremoveTimeout(peer, autoremoveValue): case let .UpdateAutoremoveTimeout(peer, autoremoveValue):
let peerId = peer.peerId let peerId = peer.peerId
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
@ -5420,6 +5437,7 @@ func replayFinalState(
updatedStarsBalance: updatedStarsBalance, updatedStarsBalance: updatedStarsBalance,
updatedStarsRevenueStatus: updatedStarsRevenueStatus, updatedStarsRevenueStatus: updatedStarsRevenueStatus,
sentScheduledMessageIds: finalState.state.sentScheduledMessageIds, sentScheduledMessageIds: finalState.state.sentScheduledMessageIds,
reportMessageDelivery: reportMessageDelivery reportMessageDelivery: reportMessageDelivery,
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds
) )
} }

View File

@ -1145,6 +1145,9 @@ public final class AccountStateManager {
if !events.reportMessageDelivery.isEmpty { if !events.reportMessageDelivery.isEmpty {
strongSelf.reportMessageDeliveryDisposable.add(_internal_reportMessageDelivery(postbox: strongSelf.postbox, network: strongSelf.network, messageIds: Array(events.reportMessageDelivery), fromPushNotification: false).start()) strongSelf.reportMessageDeliveryDisposable.add(_internal_reportMessageDelivery(postbox: strongSelf.postbox, network: strongSelf.network, messageIds: Array(events.reportMessageDelivery), fromPushNotification: false).start())
} }
if !events.addedConferenceInvitationMessagesIds.isEmpty {
strongSelf.callSessionManager?.addConferenceInvitationMessages(ids: events.addedConferenceInvitationMessagesIds)
}
if !events.isContactUpdates.isEmpty { if !events.isContactUpdates.isEmpty {
strongSelf.addIsContactUpdates(events.isContactUpdates) strongSelf.addIsContactUpdates(events.isContactUpdates)
} }
@ -2203,7 +2206,7 @@ public final class AccountStateManager {
switch update { switch update {
case let .updatePhoneCall(phoneCall): case let .updatePhoneCall(phoneCall):
switch phoneCall { switch phoneCall {
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, _, _, conferenceCall): case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, _, _):
guard let peer = peers.first(where: { $0.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)) }) else { guard let peer = peers.first(where: { $0.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)) }) else {
return nil return nil
} }
@ -2213,7 +2216,7 @@ public final class AccountStateManager {
timestamp: date, timestamp: date,
peer: EnginePeer(peer), peer: EnginePeer(peer),
isVideo: (flags & (1 << 6)) != 0, isVideo: (flags & (1 << 6)) != 0,
isConference: conferenceCall != nil isConference: false
) )
default: default:
break break

View File

@ -4,7 +4,6 @@ import MtProtoKit
import SwiftSignalKit import SwiftSignalKit
import TelegramApi import TelegramApi
private let minLayer: Int32 = 65 private let minLayer: Int32 = 65
public enum CallSessionError: Equatable { public enum CallSessionError: Equatable {
@ -15,11 +14,11 @@ public enum CallSessionError: Equatable {
case disconnected case disconnected
} }
public enum CallSessionEndedType { public enum CallSessionEndedType: Equatable {
case hungUp case hungUp
case busy case busy
case missed case missed
case switchedToConference case switchedToConference(slug: String)
} }
public enum CallSessionTerminationReason: Equatable { public enum CallSessionTerminationReason: Equatable {
@ -65,41 +64,47 @@ public struct GroupCallReference: Equatable {
} }
extension GroupCallReference { extension GroupCallReference {
init(_ apiGroupCall: Api.InputGroupCall) { init?(_ apiGroupCall: Api.InputGroupCall) {
switch apiGroupCall { switch apiGroupCall {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
self.init(id: id, accessHash: accessHash) self.init(id: id, accessHash: accessHash)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
} }
} }
var apiInputGroupCall: Api.InputGroupCall {
return .inputGroupCall(id: self.id, accessHash: self.accessHash)
}
} }
enum CallSessionInternalState { enum CallSessionInternalState {
case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String], conferenceCall: GroupCallReference?) case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String])
case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, conferenceCall: GroupCallReference?, disposable: Disposable) case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, disposable: Disposable)
case awaitingConfirmation(id: Int64, accessHash: Int64, gAHash: Data, b: Data, config: SecretChatEncryptionConfig) case awaitingConfirmation(id: Int64, accessHash: Int64, gAHash: Data, b: Data, config: SecretChatEncryptionConfig)
case requesting(a: Data, conferenceCall: GroupCallReference?, disposable: Disposable) case requesting(a: Data, disposable: Disposable)
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?, conferenceCall: GroupCallReference?) 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, conferenceCall: GroupCallReference?, disposable: Disposable) case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, 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?, willSwitchToConference: Bool) case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference) case switchedToConference(slug: String)
case dropping(reason: CallSessionTerminationReason, disposable: Disposable) case dropping(reason: CallSessionTerminationReason, disposable: Disposable)
case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool) case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool)
var stableId: Int64? { var stableId: Int64? {
switch self { switch self {
case let .ringing(id, _, _, _, _, _): case let .ringing(id, _, _, _, _):
return id return id
case let .accepting(id, _, _, _, _, _): case let .accepting(id, _, _, _, _):
return id return id
case let .awaitingConfirmation(id, _, _, _, _): case let .awaitingConfirmation(id, _, _, _, _):
return id return id
case .requesting: case .requesting:
return nil return nil
case let .requested(id, _, _, _, _, _, _): case let .requested(id, _, _, _, _, _):
return id return id
case let .confirming(id, _, _, _, _, _, _): case let .confirming(id, _, _, _, _, _):
return id return id
case let .active(id, _, _, _, _, _, _, _, _, _, _, _, _): case let .active(id, _, _, _, _, _, _, _, _, _, _):
return id return id
case .switchedToConference: case .switchedToConference:
return nil return nil
@ -138,7 +143,8 @@ public struct CallSessionRingingState: Equatable {
public let peerId: PeerId public let peerId: PeerId
public let isVideo: Bool public let isVideo: Bool
public let isVideoPossible: Bool public let isVideoPossible: Bool
public let isIncomingConference: Bool public let conferenceSource: MessageId?
public let otherParticipants: [EnginePeer]
} }
public enum DropCallReason { public enum DropCallReason {
@ -166,9 +172,9 @@ public struct CallTerminationOptions: OptionSet {
public enum CallSessionState { public enum CallSessionState {
case ringing case ringing
case accepting case accepting
case requesting(ringing: Bool, conferenceCall: GroupCallReference?) case requesting(ringing: Bool)
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool) case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference) case switchedToConference(slug: String)
case dropping(reason: CallSessionTerminationReason) case dropping(reason: CallSessionTerminationReason)
case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions) case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions)
@ -178,17 +184,20 @@ public enum CallSessionState {
self = .ringing self = .ringing
case .accepting, .awaitingConfirmation: case .accepting, .awaitingConfirmation:
self = .accepting self = .accepting
case let .requesting(_, conferenceCall, _): case .requesting:
self = .requesting(ringing: false, conferenceCall: conferenceCall) self = .requesting(ringing: false)
case let .confirming(_, _, _, _, _, conferenceCall, _): case .confirming:
self = .requesting(ringing: true, conferenceCall: conferenceCall) self = .requesting(ringing: true)
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp, conferenceCall): case let .requested(_, _, _, _, _, remoteConfirmationTimestamp):
self = .requesting(ringing: remoteConfirmationTimestamp != nil, conferenceCall: conferenceCall) self = .requesting(ringing: remoteConfirmationTimestamp != nil)
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall, willSwitchToConference): case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P):
self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: willSwitchToConference) self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
case let .dropping(reason, _): case let .dropping(reason, _):
self = .dropping(reason: reason) self = .dropping(reason: reason)
case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs): case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs):
if case let .ended(endedReason) = reason, case let .switchedToConference(slug) = endedReason {
self = .switchedToConference(slug: slug)
} else {
var options = CallTerminationOptions() var options = CallTerminationOptions()
if reportRating { if reportRating {
options.insert(.reportRating) options.insert(.reportRating)
@ -203,8 +212,9 @@ public enum CallSessionState {
callId = nil callId = nil
} }
self = .terminated(id: callId, reason: reason, options: options) self = .terminated(id: callId, reason: reason, options: options)
case let .switchedToConference(key, keyVisualHash, conferenceCall): }
self = .switchedToConference(key: key, keyVisualHash: keyVisualHash, conferenceCall: conferenceCall) case let .switchedToConference(slug):
self = .switchedToConference(slug: slug)
} }
} }
} }
@ -222,7 +232,7 @@ public struct CallSession {
public let state: CallSessionState public let state: CallSessionState
public let isVideoPossible: Bool public let isVideoPossible: Bool
init( public init(
id: CallSessionInternalId, id: CallSessionInternalId,
stableId: Int64?, stableId: Int64?,
isOutgoing: Bool, isOutgoing: Bool,
@ -336,17 +346,15 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.
private final class CallSessionContext { private final class CallSessionContext {
let peerId: PeerId let peerId: PeerId
let isOutgoing: Bool let isOutgoing: Bool
let isIncomingConference: Bool
var type: CallSession.CallType var type: CallSession.CallType
var isVideoPossible: Bool var isVideoPossible: Bool
let pendingConference: (conference: GroupCallReference, encryptionKey: Data)?
var state: CallSessionInternalState var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>() let subscribers = Bag<(CallSession) -> Void>()
var signalingReceiver: (([Data]) -> Void)? var signalingReceiver: (([Data]) -> Void)?
let signalingDisposables = DisposableSet() let signalingDisposables = DisposableSet()
var createConferenceCallDisposable: Disposable?
let acknowledgeIncomingCallDisposable = MetaDisposable() let acknowledgeIncomingCallDisposable = MetaDisposable()
var createConferenceCallDisposable: Disposable?
var isEmpty: Bool { var isEmpty: Bool {
if case .terminated = self.state { if case .terminated = self.state {
@ -356,13 +364,11 @@ private final class CallSessionContext {
} }
} }
init(peerId: PeerId, isOutgoing: Bool, isIncomingConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) { init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, isVideoPossible: Bool, state: CallSessionInternalState) {
self.peerId = peerId self.peerId = peerId
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.isIncomingConference = isIncomingConference
self.type = type self.type = type
self.isVideoPossible = isVideoPossible self.isVideoPossible = isVideoPossible
self.pendingConference = pendingConference
self.state = state self.state = state
} }
@ -373,6 +379,73 @@ private final class CallSessionContext {
} }
} }
private final class IncomingConferenceInvitationContext {
enum State: Equatable {
case pending
case ringing(callId: Int64, otherParticipants: [EnginePeer])
case stopped
}
private let queue: Queue
private var disposable: Disposable?
let internalId: CallSessionInternalId
private(set) var state: State = .pending
init(queue: Queue, postbox: Postbox, messageId: MessageId, updated: @escaping () -> Void) {
self.queue = queue
self.internalId = CallSessionInternalId()
let key = PostboxViewKey.messages(Set([messageId]))
self.disposable = (postbox.combinedView(keys: [key])
|> map { view -> Message? in
guard let view = view.views[key] as? MessagesView else {
return nil
}
return view.messages[messageId]
}
|> deliverOn(self.queue)).startStrict(next: { [weak self] message in
guard let self = self else {
return
}
let state: State
if let message = message {
var foundAction: TelegramMediaAction?
for media in message.media {
if let action = media as? TelegramMediaAction {
foundAction = action
break
}
}
if let action = foundAction, case let .conferenceCall(callId, duration, otherParticipants) = action.action {
if duration != nil {
state = .stopped
} else {
state = .ringing(callId: callId, otherParticipants: otherParticipants.compactMap { id -> EnginePeer? in
return message.peers[id].flatMap(EnginePeer.init)
})
}
} else {
state = .stopped
}
} else {
state = .stopped
}
if self.state != state {
self.state = state
updated()
}
})
}
deinit {
self.disposable?.dispose()
}
}
private func selectVersionOnAccept(localVersions: [CallSessionManagerImplementationVersion], remoteVersions: [String]) -> [String]? { private func selectVersionOnAccept(localVersions: [CallSessionManagerImplementationVersion], remoteVersions: [String]) -> [String]? {
let filteredVersions = localVersions.map({ $0.version }).filter(remoteVersions.contains) let filteredVersions = localVersions.map({ $0.version }).filter(remoteVersions.contains)
if filteredVersions.isEmpty { if filteredVersions.isEmpty {
@ -406,9 +479,12 @@ private final class CallSessionManagerContext {
private var futureContextSubscribers: [CallSessionInternalId: Bag<(CallSession) -> Void>] = [:] private var futureContextSubscribers: [CallSessionInternalId: Bag<(CallSession) -> Void>] = [:]
private var contextIdByStableId: [CallSessionStableId: CallSessionInternalId] = [:] private var contextIdByStableId: [CallSessionStableId: CallSessionInternalId] = [:]
private var incomingConferenceInvitationContexts: [MessageId: IncomingConferenceInvitationContext] = [:]
private var enqueuedSignalingData: [Int64: [Data]] = [:] private var enqueuedSignalingData: [Int64: [Data]] = [:]
private let disposables = DisposableSet() private let disposables = DisposableSet()
private let rejectConferenceInvitationDisposables = DisposableSet()
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId, maxLayer: Int32, versions: [CallSessionManagerImplementationVersion], addUpdates: @escaping (Api.Updates) -> Void) { init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId, maxLayer: Int32, versions: [CallSessionManagerImplementationVersion], addUpdates: @escaping (Api.Updates) -> Void) {
self.queue = queue self.queue = queue
@ -423,6 +499,7 @@ private final class CallSessionManagerContext {
deinit { deinit {
assert(self.queue.isCurrent()) assert(self.queue.isCurrent())
self.disposables.dispose() self.disposables.dispose()
self.rejectConferenceInvitationDisposables.dispose()
} }
func updateVersions(versions: [CallSessionManagerImplementationVersion]) { func updateVersions(versions: [CallSessionManagerImplementationVersion]) {
@ -556,7 +633,20 @@ private final class CallSessionManagerContext {
peerId: context.peerId, peerId: context.peerId,
isVideo: context.type == .video, isVideo: context.type == .video,
isVideoPossible: context.isVideoPossible, isVideoPossible: context.isVideoPossible,
isIncomingConference: context.isIncomingConference conferenceSource: nil,
otherParticipants: []
))
}
}
for (id, context) in self.incomingConferenceInvitationContexts {
if case let .ringing(_, otherParticipants) = context.state {
ringingContexts.append(CallSessionRingingState(
id: context.internalId,
peerId: id.peerId,
isVideo: false,
isVideoPossible: true,
conferenceSource: id,
otherParticipants: otherParticipants
)) ))
} }
} }
@ -584,7 +674,7 @@ private final class CallSessionManagerContext {
} }
} }
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool, conferenceCall: GroupCallReference?) -> CallSessionInternalId? { private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool) -> CallSessionInternalId? {
if self.contextIdByStableId[stableId] != nil { if self.contextIdByStableId[stableId] != nil {
return nil return nil
} }
@ -594,13 +684,10 @@ private final class CallSessionManagerContext {
let b = Data(bytesNoCopy: bBytes, count: 256, deallocator: .free) let b = Data(bytesNoCopy: bBytes, count: 256, deallocator: .free)
if randomStatus == 0 { if randomStatus == 0 {
var isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) }) let isVideoPossible = true
//#if DEBUG
isVideoPossible = true
//#endif
let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId) let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId)
let context = CallSessionContext(peerId: peerId, isOutgoing: false, isIncomingConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall)) 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))
self.contexts[internalId] = context self.contexts[internalId] = context
let queue = self.queue let queue = self.queue
@ -632,12 +719,36 @@ private final class CallSessionManagerContext {
} }
func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) { func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) {
for (id, context) in self.incomingConferenceInvitationContexts {
if context.internalId == internalId {
self.incomingConferenceInvitationContexts.removeValue(forKey: id)
self.ringingStatesUpdated()
let addUpdates = self.addUpdates
let rejectSignal = self.network.request(Api.functions.phone.declineConferenceCallInvite(msgId: id.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates {
addUpdates(updates)
}
return .complete()
}
self.rejectConferenceInvitationDisposables.add(rejectSignal.startStrict())
return
}
}
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
var dropData: (CallSessionStableId, Int64, DropCallSessionReason)? var dropData: (CallSessionStableId, Int64, DropCallSessionReason)?
var wasRinging = false var wasRinging = false
let isVideo = context.type == .video let isVideo = context.type == .video
switch context.state { switch context.state {
case let .ringing(id, accessHash, _, _, _, _): case let .ringing(id, accessHash, _, _, _):
wasRinging = true wasRinging = true
let internalReason: DropCallSessionReason let internalReason: DropCallSessionReason
switch reason { switch reason {
@ -651,10 +762,10 @@ private final class CallSessionManagerContext {
internalReason = .missed internalReason = .missed
} }
dropData = (id, accessHash, internalReason) dropData = (id, accessHash, internalReason)
case let .accepting(id, accessHash, _, _, _, disposable): case let .accepting(id, accessHash, _, _, disposable):
dropData = (id, accessHash, .abort) dropData = (id, accessHash, .abort)
disposable.dispose() disposable.dispose()
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _, _): case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _):
let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp) let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp)
let internalReason: DropCallSessionReason let internalReason: DropCallSessionReason
switch reason { switch reason {
@ -672,10 +783,10 @@ private final class CallSessionManagerContext {
break break
case let .awaitingConfirmation(id, accessHash, _, _, _): case let .awaitingConfirmation(id, accessHash, _, _, _):
dropData = (id, accessHash, .abort) dropData = (id, accessHash, .abort)
case let .confirming(id, accessHash, _, _, _, _, disposable): case let .confirming(id, accessHash, _, _, _, disposable):
disposable.dispose() disposable.dispose()
dropData = (id, accessHash, .abort) dropData = (id, accessHash, .abort)
case let .requested(id, accessHash, _, _, _, _, _): case let .requested(id, accessHash, _, _, _, _):
let internalReason: DropCallSessionReason let internalReason: DropCallSessionReason
switch reason { switch reason {
case .busy, .hangUp: case .busy, .hangUp:
@ -686,7 +797,7 @@ private final class CallSessionManagerContext {
internalReason = .missed internalReason = .missed
} }
dropData = (id, accessHash, internalReason) dropData = (id, accessHash, internalReason)
case let .requesting(_, _, disposable): case let .requesting(_, disposable):
disposable.dispose() disposable.dispose()
context.state = .terminated(id: nil, accessHash: nil, reason: .ended(.hungUp), reportRating: false, sendDebugLogs: false) context.state = .terminated(id: nil, accessHash: nil, reason: .ended(.hungUp), reportRating: false, sendDebugLogs: false)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
@ -709,8 +820,8 @@ private final class CallSessionManagerContext {
mappedReason = .ended(.hungUp) mappedReason = .ended(.hungUp)
case .missed: case .missed:
mappedReason = .ended(.missed) mappedReason = .ended(.missed)
case .switchToConference: case let .switchToConference(slug):
mappedReason = .ended(.switchedToConference) mappedReason = .ended(.switchedToConference(slug: slug))
} }
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason) 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 |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
@ -751,12 +862,12 @@ private final class CallSessionManagerContext {
} }
} }
func dropToConference(internalId: CallSessionInternalId, encryptedGroupKey: Data) { func dropToConference(internalId: CallSessionInternalId, slug: String) {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
var dropData: (CallSessionStableId, Int64)? var dropData: (CallSessionStableId, Int64)?
let isVideo = context.type == .video let isVideo = context.type == .video
switch context.state { switch context.state {
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _): case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
dropData = (id, accessHash) dropData = (id, accessHash)
default: default:
break break
@ -764,11 +875,11 @@ private final class CallSessionManagerContext {
if let (id, accessHash) = dropData { if let (id, accessHash) = dropData {
self.contextIdByStableId.removeValue(forKey: id) self.contextIdByStableId.removeValue(forKey: id)
context.state = .dropping(reason: .ended(.switchedToConference), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey)) context.state = .dropping(reason: .ended(.switchedToConference(slug: slug)), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(slug: slug))
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
if let strongSelf = self { if let strongSelf = self {
if let context = strongSelf.contexts[internalId] { if let context = strongSelf.contexts[internalId] {
context.state = .terminated(id: id, accessHash: accessHash, reason: .ended(.hungUp), reportRating: reportRating, sendDebugLogs: sendDebugLogs) context.state = .switchedToConference(slug: slug)
strongSelf.contextUpdated(internalId: internalId) strongSelf.contextUpdated(internalId: internalId)
if context.isEmpty { if context.isEmpty {
strongSelf.contexts.removeValue(forKey: internalId) strongSelf.contexts.removeValue(forKey: internalId)
@ -793,9 +904,9 @@ private final class CallSessionManagerContext {
func accept(internalId: CallSessionInternalId) { func accept(internalId: CallSessionInternalId) {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
switch context.state { switch context.state {
case let .ringing(id, accessHash, gAHash, b, _, conferenceCall): case let .ringing(id, accessHash, gAHash, b, _):
let acceptVersions = self.versions.map({ $0.version }) let acceptVersions = self.versions.map({ $0.version })
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 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
if let strongSelf = self, let context = strongSelf.contexts[internalId] { if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .accepting = context.state { if case .accepting = context.state {
switch result { switch result {
@ -806,9 +917,9 @@ private final class CallSessionManagerContext {
case let .waiting(config): case let .waiting(config):
context.state = .awaitingConfirmation(id: id, accessHash: accessHash, gAHash: gAHash, b: b, config: config) context.state = .awaitingConfirmation(id: id, accessHash: accessHash, gAHash: gAHash, b: b, config: config)
strongSelf.contextUpdated(internalId: internalId) strongSelf.contextUpdated(internalId: internalId)
case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall): case let .call(config, gA, timestamp, connections, maxLayer, version, customParameters, allowsP2P):
if let (key, keyId, keyVisualHash) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) { if let (key, keyId, keyVisualHash) = strongSelf.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gA) {
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall, willSwitchToConference: context.isIncomingConference) context.state = .active(id: id, accessHash: accessHash, beginTimestamp: timestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
strongSelf.contextUpdated(internalId: internalId) strongSelf.contextUpdated(internalId: internalId)
} else { } else {
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@ -829,7 +940,7 @@ private final class CallSessionManagerContext {
func sendSignalingData(internalId: CallSessionInternalId, data: Data) { func sendSignalingData(internalId: CallSessionInternalId, data: Data) {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
switch context.state { switch context.state {
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _): case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
context.signalingDisposables.add(self.network.request(Api.functions.phone.sendSignalingData(peer: .inputPhoneCall(id: id, accessHash: accessHash), data: Buffer(data: data))).start()) context.signalingDisposables.add(self.network.request(Api.functions.phone.sendSignalingData(peer: .inputPhoneCall(id: id, accessHash: accessHash), data: Buffer(data: data))).start())
default: default:
break break
@ -845,33 +956,29 @@ private final class CallSessionManagerContext {
var idAndAccessHash: (id: Int64, accessHash: Int64)? var idAndAccessHash: (id: Int64, accessHash: Int64)?
switch context.state { switch context.state {
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _): case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
if conferenceCall != nil {
return
}
idAndAccessHash = (id, accessHash) idAndAccessHash = (id, accessHash)
default: default:
break break
} }
if let (id, accessHash) = idAndAccessHash { if idAndAccessHash != nil {
context.createConferenceCallDisposable = (createConferenceCall(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, callId: CallId(id: id, accessHash: accessHash)) context.createConferenceCallDisposable = (_internal_createConferenceCall(
postbox: self.postbox,
network: self.network,
accountPeerId: self.accountPeerId
)
|> deliverOn(self.queue)).startStrict(next: { [weak self] result in |> deliverOn(self.queue)).startStrict(next: { [weak self] result in
guard let self else { guard let self else {
return return
} }
guard let context = self.contexts[internalId] else {
self.dropToConference(internalId: internalId, slug: result.slug)
}, error: { [weak self] error in
guard let self else {
return return
} }
if let result { self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
switch context.state {
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: result, willSwitchToConference: false)
self.contextUpdated(internalId: internalId)
default:
break
}
}
}) })
} }
} }
@ -889,7 +996,7 @@ private final class CallSessionManagerContext {
switch call { switch call {
case .phoneCallEmpty: case .phoneCallEmpty:
break break
case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol, conferenceCall): case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol):
let remoteVersions: [String] let remoteVersions: [String]
switch remoteProtocol { switch remoteProtocol {
case let .phoneCallProtocol(_, _, _, versions): case let .phoneCallProtocol(_, _, _, versions):
@ -903,7 +1010,7 @@ private final class CallSessionManagerContext {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
switch context.state { switch context.state {
case let .requested(_, accessHash, a, gA, config, _, _): case let .requested(_, accessHash, a, gA, config, _):
let p = config.p.makeData() let p = config.p.makeData()
if !MTCheckIsSafeGAOrB(self.network.encryptionProvider, gA, p) { if !MTCheckIsSafeGAOrB(self.network.encryptionProvider, gA, p) {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@ -928,14 +1035,10 @@ private final class CallSessionManagerContext {
let keyVisualHash = MTSha256(key + gA) let keyVisualHash = MTSha256(key + gA)
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 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
if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state { if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state {
if let updatedCall = updatedCall { if let updatedCall = updatedCall {
strongSelf.updateSession(updatedCall, completion: { _ in }) strongSelf.updateSession(updatedCall, completion: { _ in })
if let pendingConference = context.pendingConference {
strongSelf.dropToConference(internalId: internalId, encryptedGroupKey: pendingConference.encryptionKey)
}
} else { } else {
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
} }
@ -949,8 +1052,7 @@ private final class CallSessionManagerContext {
assertionFailure() assertionFailure()
} }
} }
case let .phoneCallDiscarded(flags, id, reason, _, conferenceCall): case let .phoneCallDiscarded(flags, id, reason, _):
let _ = conferenceCall
let reportRating = (flags & (1 << 2)) != 0 let reportRating = (flags & (1 << 2)) != 0
let sendDebugLogs = (flags & (1 << 3)) != 0 let sendDebugLogs = (flags & (1 << 3)) != 0
if let internalId = self.contextIdByStableId[id] { if let internalId = self.contextIdByStableId[id] {
@ -963,47 +1065,39 @@ private final class CallSessionManagerContext {
case .phoneCallDiscardReasonDisconnect: case .phoneCallDiscardReasonDisconnect:
parsedReason = .error(.disconnected) parsedReason = .error(.disconnected)
case .phoneCallDiscardReasonHangup: case .phoneCallDiscardReasonHangup:
if context.pendingConference != nil {
parsedReason = .ended(.switchedToConference)
} else {
parsedReason = .ended(.hungUp) parsedReason = .ended(.hungUp)
}
case .phoneCallDiscardReasonMissed: case .phoneCallDiscardReasonMissed:
parsedReason = .ended(.missed) parsedReason = .ended(.missed)
case .phoneCallDiscardReasonAllowGroupCall: case let .phoneCallDiscardReasonMigrateConferenceCall(slug):
parsedReason = .ended(.switchedToConference) parsedReason = .ended(.switchedToConference(slug: slug))
} }
} else { } else {
parsedReason = .ended(.hungUp) parsedReason = .ended(.hungUp)
} }
switch context.state { switch context.state {
case let .accepting(id, accessHash, _, _, _, disposable): case let .accepting(id, accessHash, _, _, disposable):
disposable.dispose() disposable.dispose()
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _): case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
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) context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
}
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .awaitingConfirmation(id, accessHash, _, _, _): case let .awaitingConfirmation(id, accessHash, _, _, _):
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .requested(id, accessHash, _, _, _, _, _): case let .requested(id, accessHash, _, _, _, _):
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .confirming(id, accessHash, _, _, _, _, disposable): case let .confirming(id, accessHash, _, _, _, disposable):
disposable.dispose() disposable.dispose()
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .requesting(_, _, disposable): case let .requesting(_, disposable):
disposable.dispose() disposable.dispose()
context.state = .terminated(id: nil, accessHash: nil, reason: parsedReason, reportRating: false, sendDebugLogs: false) context.state = .terminated(id: nil, accessHash: nil, reason: parsedReason, reportRating: false, sendDebugLogs: false)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .ringing(id, accessHash, _, _, _, _): case let .ringing(id, accessHash, _, _, _):
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
self.ringingStatesUpdated() self.ringingStatesUpdated()
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
@ -1014,15 +1108,15 @@ private final class CallSessionManagerContext {
//assertionFailure() //assertionFailure()
} }
} }
case let .phoneCall(flags, id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connections, startDate, customParameters, conferenceCall): case let .phoneCall(flags, id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connections, startDate, customParameters):
let allowsP2P = (flags & (1 << 5)) != 0 let allowsP2P = (flags & (1 << 5)) != 0
if let internalId = self.contextIdByStableId[id] { if let internalId = self.contextIdByStableId[id] {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
switch context.state { switch context.state {
case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference: case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
break break
case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, _, _): case let .active(id, accessHash, beginTimestamp, key, keyId, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference) context.state = .active(id: id, accessHash: accessHash, beginTimestamp: beginTimestamp, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
case let .awaitingConfirmation(_, accessHash, gAHash, b, config): case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) { if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
@ -1041,7 +1135,7 @@ private final class CallSessionManagerContext {
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) }) let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
context.isVideoPossible = isVideoPossible context.isVideoPossible = isVideoPossible
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference) context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
} else { } else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@ -1053,7 +1147,7 @@ private final class CallSessionManagerContext {
} else { } else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) 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 { switch callProtocol {
case let .phoneCallProtocol(_, _, maxLayer, versions): case let .phoneCallProtocol(_, _, maxLayer, versions):
if !versions.isEmpty { if !versions.isEmpty {
@ -1068,7 +1162,7 @@ private final class CallSessionManagerContext {
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) }) let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
context.isVideoPossible = isVideoPossible context.isVideoPossible = isVideoPossible
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), willSwitchToConference: context.isIncomingConference) context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
} else { } else {
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
@ -1079,7 +1173,7 @@ private final class CallSessionManagerContext {
assertionFailure() assertionFailure()
} }
} }
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol, conferenceCall): case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol):
let isVideo = (flags & (1 << 6)) != 0 let isVideo = (flags & (1 << 6)) != 0
let versions: [String] let versions: [String]
switch requestedProtocol { switch requestedProtocol {
@ -1087,7 +1181,7 @@ private final class CallSessionManagerContext {
versions = libraryVersions versions = libraryVersions
} }
if self.contextIdByStableId[id] == nil { 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, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) 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)
if let internalId = internalId { if let internalId = internalId {
var resultRingingStateValue: CallSessionRingingState? var resultRingingStateValue: CallSessionRingingState?
for ringingState in self.ringingStatesValue() { for ringingState in self.ringingStatesValue() {
@ -1104,13 +1198,13 @@ private final class CallSessionManagerContext {
} }
} }
} }
case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate, conferenceCall): case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate):
if let internalId = self.contextIdByStableId[id] { if let internalId = self.contextIdByStableId[id] {
if let context = self.contexts[internalId] { if let context = self.contexts[internalId] {
switch context.state { 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 { if let receiveDate = receiveDate, remoteConfirmationTimestamp == nil {
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate)
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
} }
default: default:
@ -1145,6 +1239,29 @@ private final class CallSessionManagerContext {
} }
} }
func addConferenceInvitationMessages(ids: [MessageId]) {
for id in ids {
if self.incomingConferenceInvitationContexts[id] == nil {
let context = IncomingConferenceInvitationContext(queue: self.queue, postbox: self.postbox, messageId: id, updated: { [weak self] in
guard let self else {
return
}
if let context = self.incomingConferenceInvitationContexts[id] {
switch context.state {
case .pending:
break
case .ringing:
self.ringingStatesUpdated()
case .stopped:
self.incomingConferenceInvitationContexts.removeValue(forKey: id)
}
}
})
self.incomingConferenceInvitationContexts[id] = context
}
}
}
private func makeSessionEncryptionKey(config: SecretChatEncryptionConfig, gAHash: Data, b: Data, gA: Data) -> (key: Data, keyId: Int64, keyVisualHash: Data)? { private func makeSessionEncryptionKey(config: SecretChatEncryptionConfig, gAHash: Data, b: Data, gA: Data) -> (key: Data, keyId: Int64, keyVisualHash: Data)? {
var key = MTExp(self.network.encryptionProvider, gA, b, config.p.makeData())! var key = MTExp(self.network.encryptionProvider, gA, b, config.p.makeData())!
@ -1173,17 +1290,17 @@ private final class CallSessionManagerContext {
return (key, keyId, keyVisualHash) return (key, keyId, keyVisualHash)
} }
func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool, conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?) -> CallSessionInternalId? { func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool) -> CallSessionInternalId? {
let aBytes = malloc(256)! let aBytes = malloc(256)!
let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self)) let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free) let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
if randomStatus == 0 { if randomStatus == 0 {
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isIncomingConference: false, 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 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
if let strongSelf = self, let context = strongSelf.contexts[internalId] { if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .requesting = context.state { if case .requesting = context.state {
switch result { switch result {
case let .success(id, accessHash, config, gA, remoteConfirmationTimestamp): case let .success(id, accessHash, config, gA, remoteConfirmationTimestamp):
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp, conferenceCall: conferenceCall?.conference) context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp)
strongSelf.contextIdByStableId[id] = internalId strongSelf.contextIdByStableId[id] = internalId
strongSelf.contextUpdated(internalId: internalId) strongSelf.contextUpdated(internalId: internalId)
strongSelf.deliverCallSignalingData(id: id) strongSelf.deliverCallSignalingData(id: id)
@ -1254,6 +1371,12 @@ public final class CallSessionManager {
} }
} }
func addConferenceInvitationMessages(ids: [MessageId]) {
self.withContext { context in
context.addConferenceInvitationMessages(ids: ids)
}
}
public func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) { public func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) {
self.withContext { context in self.withContext { context in
context.drop(internalId: internalId, reason: reason, debugLog: debugLog) context.drop(internalId: internalId, reason: reason, debugLog: debugLog)
@ -1278,12 +1401,12 @@ public final class CallSessionManager {
} }
} }
public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> { public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal<CallSessionInternalId, NoError> {
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self?.withContext { context in self?.withContext { context in
if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo, conferenceCall: conferenceCall) { if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo) {
subscriber.putNext(internalId) subscriber.putNext(internalId)
subscriber.putCompletion() subscriber.putCompletion()
} }
@ -1354,7 +1477,7 @@ public final class CallSessionManager {
private enum AcceptedCall { private enum AcceptedCall {
case waiting(config: SecretChatEncryptionConfig) case waiting(config: SecretChatEncryptionConfig)
case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?) case call(config: SecretChatEncryptionConfig, gA: Data, timestamp: Int32, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
} }
private enum AcceptCallResult { private enum AcceptCallResult {
@ -1394,7 +1517,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
return .failed return .failed
case .phoneCallWaiting: case .phoneCallWaiting:
return .success(.waiting(config: config)) return .success(.waiting(config: config))
case let .phoneCall(flags, id, _, _, _, _, gAOrB, _, callProtocol, connections, startDate, customParameters, conferenceCall): case let .phoneCall(flags, id, _, _, _, _, gAOrB, _, callProtocol, connections, startDate, customParameters):
if id == stableId { if id == stableId {
switch callProtocol{ switch callProtocol{
case let .phoneCallProtocol(_, _, maxLayer, versions): case let .phoneCallProtocol(_, _, maxLayer, versions):
@ -1407,7 +1530,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
customParametersValue = data customParametersValue = data
} }
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: (flags & (1 << 5)) != 0, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))) return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, version: versions[0], customParameters: customParametersValue, allowsP2P: (flags & (1 << 5)) != 0))
} else { } else {
return .failed return .failed
} }
@ -1430,7 +1553,7 @@ private enum RequestCallSessionResult {
case failed(CallSessionError) case failed(CallSessionError)
} }
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool, conferenceCall: GroupCallReference?) -> Signal<RequestCallSessionResult, NoError> { private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool) -> Signal<RequestCallSessionResult, NoError> {
return validatedEncryptionConfig(postbox: postbox, network: network) return validatedEncryptionConfig(postbox: postbox, network: network)
|> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in |> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in
return postbox.transaction { transaction -> Signal<RequestCallSessionResult, NoError> in return postbox.transaction { transaction -> Signal<RequestCallSessionResult, NoError> in
@ -1450,18 +1573,15 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer
if isVideo { if isVideo {
callFlags |= 1 << 0 callFlags |= 1 << 0
} }
if conferenceCall != nil {
callFlags |= 1 << 1
}
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))) 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)))
|> map { result -> RequestCallSessionResult in |> map { result -> RequestCallSessionResult in
switch result { switch result {
case let .phoneCall(phoneCall, _): case let .phoneCall(phoneCall, _):
switch 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) 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) return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate)
default: default:
return .failed(.generic) return .failed(.generic)
@ -1514,7 +1634,7 @@ private enum DropCallSessionReason {
case busy case busy
case disconnect case disconnect
case missed case missed
case switchToConference(encryptedGroupKey: Data) case switchToConference(slug: String)
} }
private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, isVideo: Bool, reason: DropCallSessionReason) -> Signal<(Bool, Bool), NoError> { private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, isVideo: Bool, reason: DropCallSessionReason) -> Signal<(Bool, Bool), NoError> {
@ -1532,8 +1652,8 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
mappedReason = .phoneCallDiscardReasonDisconnect mappedReason = .phoneCallDiscardReasonDisconnect
case .missed: case .missed:
mappedReason = .phoneCallDiscardReasonMissed mappedReason = .phoneCallDiscardReasonMissed
case let .switchToConference(encryptedGroupKey): case let .switchToConference(slug):
mappedReason = .phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer(data: encryptedGroupKey)) mappedReason = .phoneCallDiscardReasonMigrateConferenceCall(slug: slug)
} }
var callFlags: Int32 = 0 var callFlags: Int32 = 0
@ -1556,7 +1676,7 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
switch update { switch update {
case .updatePhoneCall(let phoneCall): case .updatePhoneCall(let phoneCall):
switch phoneCall { switch phoneCall {
case let .phoneCallDiscarded(flags, _, _, _, _): case let .phoneCallDiscarded(flags, _, _, _):
reportRating = (flags & (1 << 2)) != 0 reportRating = (flags & (1 << 2)) != 0
sendDebugLogs = (flags & (1 << 3)) != 0 sendDebugLogs = (flags & (1 << 3)) != 0
default: default:
@ -1578,34 +1698,3 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
} }
} }
private func createConferenceCall(postbox: Postbox, network: Network, accountPeerId: PeerId, callId: CallId) -> Signal<GroupCallReference?, NoError> {
return network.request(Api.functions.phone.createConferenceCall(peer: .inputPhoneCall(id: callId.id, accessHash: callId.accessHash), keyFingerprint: 1))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<GroupCallReference?, NoError> in
guard let result else {
return .single(nil)
}
return postbox.transaction { transaction -> GroupCallReference? in
switch result {
case let .phoneCall(phoneCall, users):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
switch phoneCall {
case .phoneCallEmpty, .phoneCallRequested, .phoneCallAccepted, .phoneCallDiscarded:
return nil
case .phoneCallWaiting:
return nil
case let .phoneCall(_, _, _, _, _, _, _, _, _, _, _, _, conferenceCall):
if let conferenceCall {
return GroupCallReference(conferenceCall)
} else {
return nil
}
}
}
}
}
}

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 201 return 202
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -134,6 +134,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?) case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
case paidMessagesRefunded(count: Int32, stars: Int64) case paidMessagesRefunded(count: Int32, stars: Int64)
case paidMessagesPriceEdited(stars: Int64) case paidMessagesPriceEdited(stars: Int64)
case conferenceCall(callId: Int64, duration: Int32?, otherParticipants: [PeerId])
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -262,6 +263,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0)) self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
case 47: case 47:
self = .paidMessagesPriceEdited(stars: decoder.decodeInt64ForKey("stars", orElse: 0)) self = .paidMessagesPriceEdited(stars: decoder.decodeInt64ForKey("stars", orElse: 0))
case 48:
self = .conferenceCall(callId: decoder.decodeInt64ForKey("cid", orElse: 0), duration: decoder.decodeOptionalInt32ForKey("dur"), otherParticipants: decoder.decodeInt64ArrayForKey("part").map(PeerId.init))
default: default:
self = .unknown self = .unknown
} }
@ -639,6 +642,15 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .paidMessagesPriceEdited(stars): case let .paidMessagesPriceEdited(stars):
encoder.encodeInt32(47, forKey: "_rawValue") encoder.encodeInt32(47, forKey: "_rawValue")
encoder.encodeInt64(stars, forKey: "stars") encoder.encodeInt64(stars, forKey: "stars")
case let .conferenceCall(callId, duration, otherParticipants):
encoder.encodeInt32(48, forKey: "_rawValue")
encoder.encodeInt64(callId, forKey: "cid")
if let duration {
encoder.encodeInt32(duration, forKey: "dur")
} else {
encoder.encodeNil(forKey: "dur")
}
encoder.encodeInt64Array(otherParticipants.map({ $0.toInt64() }), forKey: "part")
} }
} }

View File

@ -3,6 +3,85 @@ import SwiftSignalKit
import TelegramApi import TelegramApi
import MtProtoKit import MtProtoKit
public final class TelegramKeyPair: Equatable {
public let id: Int64
public let publicKey: TelegramPublicKey
public init(id: Int64, publicKey: TelegramPublicKey) {
self.id = id
self.publicKey = publicKey
}
public static func ==(lhs: TelegramKeyPair, rhs: TelegramKeyPair) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.publicKey != rhs.publicKey {
return false
}
return true
}
}
public final class TelegramPublicKey: Equatable {
let value: Int256
init(value: Int256) {
self.value = value
}
public static func ==(lhs: TelegramPublicKey, rhs: TelegramPublicKey) -> Bool {
return lhs.value == rhs.value
}
}
public extension TelegramPublicKey {
convenience init?(data: Data) {
guard data.count == 32 else {
return nil
}
var int256 = Int256(
_0: 0,
_1: 0,
_2: 0,
_3: 0
)
data.withUnsafeBytes { buffer in
if let baseAddress = buffer.baseAddress {
let int64Buffer = baseAddress.assumingMemoryBound(to: Int64.self)
int256._0 = int64Buffer[0]
int256._1 = int64Buffer[1]
int256._2 = int64Buffer[2]
int256._3 = int64Buffer[3]
}
}
self.init(value: int256)
assert(self.data == data)
}
var data: Data {
var data = Data(count: 32)
data.withUnsafeMutableBytes { buffer in
if let baseAddress = buffer.baseAddress {
let int64Buffer = baseAddress.assumingMemoryBound(to: Int64.self)
int64Buffer[0] = self.value._0
int64Buffer[1] = self.value._1
int64Buffer[2] = self.value._2
int64Buffer[3] = self.value._3
}
}
return data
}
}
public protocol TelegramE2EEncryptionProvider: AnyObject {
func generateKeyPair() -> TelegramKeyPair?
func generateCallZeroBlock(keyPair: TelegramKeyPair, userId: Int64) -> Data?
}
public struct GroupCallInfo: Equatable { public struct GroupCallInfo: Equatable {
public var id: Int64 public var id: Int64
public var accessHash: Int64 public var accessHash: Int64
@ -17,7 +96,6 @@ public struct GroupCallInfo: Equatable {
public var isVideoEnabled: Bool public var isVideoEnabled: Bool
public var unmutedVideoLimit: Int public var unmutedVideoLimit: Int
public var isStream: Bool public var isStream: Bool
public var upgradedPrivateCallId: Int64?
public init( public init(
id: Int64, id: Int64,
@ -32,8 +110,7 @@ public struct GroupCallInfo: Equatable {
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
isVideoEnabled: Bool, isVideoEnabled: Bool,
unmutedVideoLimit: Int, unmutedVideoLimit: Int,
isStream: Bool, isStream: Bool
upgradedPrivateCallId: Int64?
) { ) {
self.id = id self.id = id
self.accessHash = accessHash self.accessHash = accessHash
@ -48,7 +125,6 @@ public struct GroupCallInfo: Equatable {
self.isVideoEnabled = isVideoEnabled self.isVideoEnabled = isVideoEnabled
self.unmutedVideoLimit = unmutedVideoLimit self.unmutedVideoLimit = unmutedVideoLimit
self.isStream = isStream self.isStream = isStream
self.upgradedPrivateCallId = upgradedPrivateCallId
} }
} }
@ -60,7 +136,7 @@ public struct GroupCallSummary: Equatable {
extension GroupCallInfo { extension GroupCallInfo {
init?(_ call: Api.GroupCall) { init?(_ call: Api.GroupCall) {
switch call { switch call {
case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, conferenceFromCall): case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _):
self.init( self.init(
id: id, id: id,
accessHash: accessHash, accessHash: accessHash,
@ -74,8 +150,7 @@ extension GroupCallInfo {
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0), defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
isVideoEnabled: (flags & (1 << 9)) != 0, isVideoEnabled: (flags & (1 << 9)) != 0,
unmutedVideoLimit: Int(unmutedVideoLimit), unmutedVideoLimit: Int(unmutedVideoLimit),
isStream: (flags & (1 << 12)) != 0, isStream: (flags & (1 << 12)) != 0
upgradedPrivateCallId: conferenceFromCall
) )
case .groupCallDiscarded: case .groupCallDiscarded:
return nil return nil
@ -87,9 +162,43 @@ public enum GetCurrentGroupCallError {
case generic case generic
} }
func _internal_getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> { public enum InternalGroupCallReference: Equatable {
case id(id: Int64, accessHash: Int64)
case link(slug: String)
case message(id: MessageId)
}
extension InternalGroupCallReference {
var apiInputGroupCall: Api.InputGroupCall {
switch self {
case let .id(id, accessHash):
return .inputGroupCall(id: id, accessHash: accessHash)
case let .link(slug):
return .inputGroupCallSlug(slug: slug)
case let .message(id):
return .inputGroupCallInviteMessage(msgId: id.id)
}
}
}
func _internal_getCurrentGroupCall(account: Account, reference: InternalGroupCallReference, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
let accountPeerId = account.peerId let accountPeerId = account.peerId
return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 4)) let inputCall: Api.InputGroupCall
switch reference {
case let .id(id, accessHash):
inputCall = .inputGroupCall(id: id, accessHash: accessHash)
case let .link(slug):
inputCall = .inputGroupCallSlug(slug: slug)
case let .message(id):
if id.peerId.namespace != Namespaces.Peer.CloudUser {
return .fail(.generic)
}
if id.namespace != Namespaces.Message.Cloud {
return .fail(.generic)
}
inputCall = .inputGroupCallInviteMessage(msgId: id.id)
}
return account.network.request(Api.functions.phone.getGroupCall(call: inputCall, limit: 4))
|> mapError { _ -> GetCurrentGroupCallError in |> mapError { _ -> GetCurrentGroupCallError in
return .generic return .generic
} }
@ -247,7 +356,7 @@ public enum ToggleScheduledGroupCallSubscriptionError {
case generic case generic
} }
func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> { func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: PeerId, reference: InternalGroupCallReference, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> {
return account.postbox.transaction { transaction -> Void in return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let activeCall = cachedData.activeCall { if let cachedData = cachedData as? CachedChannelData, let activeCall = cachedData.activeCall {
@ -261,7 +370,7 @@ func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: Pe
} }
|> castError(ToggleScheduledGroupCallSubscriptionError.self) |> castError(ToggleScheduledGroupCallSubscriptionError.self)
|> mapToSignal { _ -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> in |> mapToSignal { _ -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> in
return account.network.request(Api.functions.phone.toggleGroupCallStartSubscription(call: .inputGroupCall(id: callId, accessHash: accessHash), subscribed: subscribe ? .boolTrue : .boolFalse)) return account.network.request(Api.functions.phone.toggleGroupCallStartSubscription(call: reference.apiInputGroupCall, subscribed: subscribe ? .boolTrue : .boolFalse))
|> mapError { error -> ToggleScheduledGroupCallSubscriptionError in |> mapError { error -> ToggleScheduledGroupCallSubscriptionError in
return .generic return .generic
} }
@ -342,24 +451,24 @@ public enum GetGroupCallParticipantsError {
case generic case generic
} }
func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> { func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
let accountPeerId = account.peerId let accountPeerId = account.peerId
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool, Int64?), GetGroupCallParticipantsError> let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError>
sortAscendingValue = _internal_getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash) sortAscendingValue = _internal_getCurrentGroupCall(account: account, reference: reference)
|> mapError { _ -> GetGroupCallParticipantsError in |> mapError { _ -> GetGroupCallParticipantsError in
return .generic return .generic
} }
|> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool, Int64?), GetGroupCallParticipantsError> in |> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError> in
guard let result = result else { guard let result = result else {
return .fail(.generic) return .fail(.generic)
} }
return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream, result.info.upgradedPrivateCallId)) return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream))
} }
return combineLatest( return combineLatest(
account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: [], sources: ssrcs.map { Int32(bitPattern: $0) }, offset: offset, limit: limit)) account.network.request(Api.functions.phone.getGroupParticipants(call: reference.apiInputGroupCall, ids: [], sources: ssrcs.map { Int32(bitPattern: $0) }, offset: offset, limit: limit))
|> mapError { _ -> GetGroupCallParticipantsError in |> mapError { _ -> GetGroupCallParticipantsError in
return .generic return .generic
}, },
@ -372,7 +481,7 @@ func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessH
let version: Int32 let version: Int32
let nextParticipantsFetchOffset: String? let nextParticipantsFetchOffset: String?
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream, upgradedPrivateCallId) = sortAscendingAndScheduleTimestamp let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp
switch result { switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion): case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
@ -408,7 +517,6 @@ func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessH
isVideoEnabled: isVideoEnabled, isVideoEnabled: isVideoEnabled,
unmutedVideoLimit: unmutedVideoLimit, unmutedVideoLimit: unmutedVideoLimit,
isStream: isStream, isStream: isStream,
upgradedPrivateCallId: upgradedPrivateCallId,
version: version version: version
) )
} }
@ -435,7 +543,39 @@ public struct JoinGroupCallResult {
public var jsonParams: String 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, keyFingerprint: Int64?) -> Signal<JoinGroupCallResult, JoinGroupCallError> { public class JoinGroupCallE2E {
public let publicKey: TelegramPublicKey
public let block: Data
public init(publicKey: TelegramPublicKey, block: Data) {
self.publicKey = publicKey
self.block = block
}
}
func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
enum InternalJoinError {
case error(JoinGroupCallError)
case restart
}
var e2eData: Signal<JoinGroupCallE2E?, NoError> = .single(nil)
if let generateE2E {
e2eData = _internal_pollConferenceCallBlockchain(network: account.network, reference: reference, subChainId: 0, offset: -1, limit: 1)
|> map { result -> JoinGroupCallE2E? in
guard let result else {
return nil
}
guard let block = result.blocks.last else {
return generateE2E(nil)
}
return generateE2E(block)
}
}
let signal: Signal<JoinGroupCallResult, InternalJoinError> = e2eData
|> castError(InternalJoinError.self)
|> mapToSignal { e2eData -> Signal<JoinGroupCallResult, InternalJoinError> in
return account.postbox.transaction { transaction -> Api.InputPeer? in return account.postbox.transaction { transaction -> Api.InputPeer? in
if let joinAs = joinAs { if let joinAs = joinAs {
return transaction.getPeer(joinAs).flatMap(apiInputPeer) return transaction.getPeer(joinAs).flatMap(apiInputPeer)
@ -443,10 +583,10 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
return .inputPeerSelf return .inputPeerSelf
} }
} }
|> castError(JoinGroupCallError.self) |> castError(InternalJoinError.self)
|> mapToSignal { inputJoinAs in |> mapToSignal { inputJoinAs -> Signal<JoinGroupCallResult, InternalJoinError> in
guard let inputJoinAs = inputJoinAs else { guard let inputJoinAs = inputJoinAs else {
return .fail(.generic) return .fail(.error(.generic))
} }
var flags: Int32 = 0 var flags: Int32 = 0
@ -457,16 +597,16 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
if let _ = inviteHash { if let _ = inviteHash {
flags |= (1 << 1) flags |= (1 << 1)
} }
if keyFingerprint != nil { if e2eData != nil {
flags |= (1 << 3) 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))) let joinRequest = account.network.request(Api.functions.phone.joinGroupCall(flags: flags, call: reference.apiInputGroupCall, joinAs: inputJoinAs, inviteHash: inviteHash, publicKey: e2eData?.publicKey.value, block: (e2eData?.block).flatMap({ Buffer.init(data: $0) }), inviteMsgId: nil, params: .dataJSON(data: joinPayload)))
|> `catch` { error -> Signal<Api.Updates, JoinGroupCallError> in |> `catch` { error -> Signal<Api.Updates, InternalJoinError> in
if error.errorDescription == "GROUPCALL_ANONYMOUS_FORBIDDEN" { if error.errorDescription == "GROUPCALL_ANONYMOUS_FORBIDDEN" {
return .fail(.anonymousNotAllowed) return .fail(.error(.anonymousNotAllowed))
} else if error.errorDescription == "GROUPCALL_PARTICIPANTS_TOO_MUCH" { } else if error.errorDescription == "GROUPCALL_PARTICIPANTS_TOO_MUCH" {
return .fail(.tooManyParticipants) return .fail(.error(.tooManyParticipants))
} else if error.errorDescription == "JOIN_AS_PEER_INVALID" { } else if error.errorDescription == "JOIN_AS_PEER_INVALID" {
if let peerId { if let peerId {
let _ = (account.postbox.transaction { transaction -> Void in let _ = (account.postbox.transaction { transaction -> Void in
@ -482,9 +622,9 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}).start() }).start()
} }
return .fail(.invalidJoinAsPeer) return .fail(.error(.invalidJoinAsPeer))
} else if error.errorDescription == "GROUPCALL_INVALID" { } else if error.errorDescription == "GROUPCALL_INVALID" {
return account.postbox.transaction { transaction -> Signal<Api.Updates, JoinGroupCallError> in return account.postbox.transaction { transaction -> Signal<Api.Updates, InternalJoinError> in
if let peerId { if let peerId {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedGroupData { if let current = current as? CachedGroupData {
@ -500,35 +640,37 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}) })
} }
return .fail(.generic) return .fail(.error(.generic))
} }
|> castError(JoinGroupCallError.self) |> castError(InternalJoinError.self)
|> switchToLatest |> switchToLatest
} else if error.errorDescription.hasPrefix("CONF_WRITE_CHAIN_INVALID") {
return .fail(.restart)
} else { } else {
return .fail(.generic) return .fail(.error(.generic))
} }
} }
let getParticipantsRequest = _internal_getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: true) let getParticipantsRequest = _internal_getGroupCallParticipants(account: account, reference: reference, offset: "", ssrcs: [], limit: 100, sortAscending: true)
|> mapError { _ -> JoinGroupCallError in |> mapError { _ -> InternalJoinError in
return .generic return .error(.generic)
} }
return combineLatest( return combineLatest(
joinRequest, joinRequest,
getParticipantsRequest getParticipantsRequest
) )
|> mapToSignal { updates, participantsState -> Signal<JoinGroupCallResult, JoinGroupCallError> in |> mapToSignal { updates, participantsState -> Signal<JoinGroupCallResult, InternalJoinError> in
let peer = account.postbox.transaction { transaction -> Peer? in let peer = account.postbox.transaction { transaction -> Peer? in
return peerId.flatMap(transaction.getPeer) return peerId.flatMap(transaction.getPeer)
} }
|> castError(JoinGroupCallError.self) |> castError(InternalJoinError.self)
return combineLatest( return combineLatest(
peerAdminIds |> castError(JoinGroupCallError.self) |> take(1), peerAdminIds |> castError(InternalJoinError.self) |> take(1),
peer peer
) )
|> mapToSignal { peerAdminIds, peer -> Signal<JoinGroupCallResult, JoinGroupCallError> in |> mapToSignal { peerAdminIds, peer -> Signal<JoinGroupCallResult, InternalJoinError> in
var state = participantsState var state = participantsState
if let peer { if let peer {
if let channel = peer as? TelegramChannel { if let channel = peer as? TelegramChannel {
@ -552,7 +694,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
maybeParsedCall = GroupCallInfo(call) maybeParsedCall = GroupCallInfo(call)
switch call { switch call {
case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _): case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _):
let isMuted = (flags & (1 << 1)) != 0 let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0 let canChange = (flags & (1 << 2)) != 0
let isVideoEnabled = (flags & (1 << 9)) != 0 let isVideoEnabled = (flags & (1 << 9)) != 0
@ -576,7 +718,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
} }
guard let parsedCall = maybeParsedCall, let parsedClientParams = maybeParsedClientParams else { guard let parsedCall = maybeParsedCall, let parsedClientParams = maybeParsedClientParams else {
return .fail(.generic) return .fail(.error(.generic))
} }
state.sortAscending = parsedCall.sortAscending state.sortAscending = parsedCall.sortAscending
@ -673,10 +815,93 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
jsonParams: parsedClientParams jsonParams: parsedClientParams
) )
} }
|> castError(JoinGroupCallError.self) |> castError(InternalJoinError.self)
} }
} }
} }
}
return signal |> restartOrMapError { error in
switch error {
case .restart:
return .restart
case let .error(e):
return .error(e)
}
}
}
func _internal_inviteConferenceCallParticipant(account: Account, callId: Int64, accessHash: Int64, peerId: EnginePeer.Id) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
guard let inputPeer else {
return .complete()
}
return account.network.request(Api.functions.phone.inviteConferenceCallParticipant(call: .inputGroupCall(id: callId, accessHash: accessHash), userId: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
if let result {
account.stateManager.addUpdates(result)
}
return .complete()
}
}
}
public enum RemoveGroupCallBlockchainParticipantError {
case generic
case pollBlocksAndRetry
}
func _internal_removeGroupCallBlockchainParticipants(account: Account, callId: Int64, accessHash: Int64, block: @escaping ([EnginePeer.Id]) -> Data?) -> Signal<Never, RemoveGroupCallBlockchainParticipantError> {
/*let blockSignal = _internal_getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", ssrcs: [], limit: 1000, sortAscending: nil)
|> mapError { _ -> RemoveGroupCallBlockchainParticipantError in
return .generic
}
|> map { result -> Data? in
return block(result.participants.map(\.peer.id))
}
let signal: Signal<Never, RemoveGroupCallBlockchainParticipantError> = blockSignal
|> mapToSignal { block -> Signal<Never, RemoveGroupCallBlockchainParticipantError> in
guard let block else {
return .complete()
}
return
}
account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(participantId).flatMap(apiInputPeer)
}
|> castError(RemoveGroupCallBlockchainParticipantError.self)
|> mapToSignal { inputPeer -> Signal<Never, RemoveGroupCallBlockchainParticipantError> in
guard let inputPeer else {
return .fail(.generic)
}
return account.network.request(Api.functions.phone.deleteConferenceCallParticipant(call: .inputGroupCall(id: callId, accessHash: accessHash), peer: inputPeer, block: Buffer(data: block)))
|> mapError { error -> RemoveGroupCallBlockchainParticipantError in
if error.errorDescription.hasPrefix("CONF_WRITE_CHAIN_INVALID") {
return .pollBlocksAndRetry
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Never, RemoveGroupCallBlockchainParticipantError> in
account.stateManager.addUpdates(result)
return .complete()
}
}
return signal*/
//TODO:release
return .complete()
} }
public struct JoinGroupCallAsScreencastResult { public struct JoinGroupCallAsScreencastResult {
@ -1037,7 +1262,6 @@ public final class GroupCallParticipantsContext {
public var isVideoEnabled: Bool public var isVideoEnabled: Bool
public var unmutedVideoLimit: Int public var unmutedVideoLimit: Int
public var isStream: Bool public var isStream: Bool
public var upgradedPrivateCallId: Int64?
public var version: Int32 public var version: Int32
public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) { public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) {
@ -1073,7 +1297,6 @@ public final class GroupCallParticipantsContext {
isVideoEnabled: Bool, isVideoEnabled: Bool,
unmutedVideoLimit: Int, unmutedVideoLimit: Int,
isStream: Bool, isStream: Bool,
upgradedPrivateCallId: Int64?,
version: Int32 version: Int32
) { ) {
self.participants = participants self.participants = participants
@ -1090,7 +1313,6 @@ public final class GroupCallParticipantsContext {
self.isVideoEnabled = isVideoEnabled self.isVideoEnabled = isVideoEnabled
self.unmutedVideoLimit = unmutedVideoLimit self.unmutedVideoLimit = unmutedVideoLimit
self.isStream = isStream self.isStream = isStream
self.upgradedPrivateCallId = upgradedPrivateCallId
self.version = version self.version = version
} }
} }
@ -1197,6 +1419,7 @@ public final class GroupCallParticipantsContext {
case state(update: StateUpdate) case state(update: StateUpdate)
case call(isTerminated: Bool, defaultParticipantsAreMuted: State.DefaultParticipantsAreMuted, title: String?, recordingStartTimestamp: Int32?, scheduleTimestamp: Int32?, isVideoEnabled: Bool, participantCount: Int?) case call(isTerminated: Bool, defaultParticipantsAreMuted: State.DefaultParticipantsAreMuted, title: String?, recordingStartTimestamp: Int32?, scheduleTimestamp: Int32?, isVideoEnabled: Bool, participantCount: Int?)
case conferenceChainBlocks(subChainId: Int, blocks: [Data], nextOffset: Int)
} }
public final class MemberEvent { public final class MemberEvent {
@ -1215,7 +1438,7 @@ public final class GroupCallParticipantsContext {
private let peerId: PeerId? private let peerId: PeerId?
public let myPeerId: PeerId public let myPeerId: PeerId
public let id: Int64 public let id: Int64
public let accessHash: Int64 public let reference: InternalGroupCallReference
private var hasReceivedSpeakingParticipantsReport: Bool = false private var hasReceivedSpeakingParticipantsReport: Bool = false
@ -1323,12 +1546,12 @@ public final class GroupCallParticipantsContext {
public private(set) var serviceState: ServiceState public private(set) var serviceState: ServiceState
init(account: Account, peerId: PeerId?, myPeerId: PeerId, id: Int64, accessHash: Int64, state: State, previousServiceState: ServiceState?) { init(account: Account, peerId: PeerId?, myPeerId: PeerId, id: Int64, reference: InternalGroupCallReference, state: State, previousServiceState: ServiceState?) {
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.myPeerId = myPeerId self.myPeerId = myPeerId
self.id = id self.id = id
self.accessHash = accessHash self.reference = reference
self.stateValue = InternalState(state: state, overlayState: OverlayState()) self.stateValue = InternalState(state: state, overlayState: OverlayState())
self.statePromise = ValuePromise<InternalState>(self.stateValue) self.statePromise = ValuePromise<InternalState>(self.stateValue)
self.serviceState = previousServiceState ?? ServiceState() self.serviceState = previousServiceState ?? ServiceState()
@ -1407,7 +1630,6 @@ public final class GroupCallParticipantsContext {
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled, isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit, unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
isStream: strongSelf.stateValue.state.isStream, isStream: strongSelf.stateValue.state.isStream,
upgradedPrivateCallId: strongSelf.stateValue.state.upgradedPrivateCallId,
version: strongSelf.stateValue.state.version version: strongSelf.stateValue.state.version
), ),
overlayState: strongSelf.stateValue.overlayState overlayState: strongSelf.stateValue.overlayState
@ -1560,7 +1782,6 @@ public final class GroupCallParticipantsContext {
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled, isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit, unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
isStream: strongSelf.stateValue.state.isStream, isStream: strongSelf.stateValue.state.isStream,
upgradedPrivateCallId: strongSelf.stateValue.state.upgradedPrivateCallId,
version: strongSelf.stateValue.state.version version: strongSelf.stateValue.state.version
), ),
overlayState: strongSelf.stateValue.overlayState overlayState: strongSelf.stateValue.overlayState
@ -1608,7 +1829,7 @@ public final class GroupCallParticipantsContext {
Logger.shared.log("GroupCallParticipantsContext", "will request ssrcs=\(ssrcs)") Logger.shared.log("GroupCallParticipantsContext", "will request ssrcs=\(ssrcs)")
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: Array(ssrcs), limit: 100, sortAscending: true) self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: "", ssrcs: Array(ssrcs), limit: 100, sortAscending: true)
|> deliverOnMainQueue).start(next: { [weak self] state in |> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -1784,7 +2005,6 @@ public final class GroupCallParticipantsContext {
let isVideoEnabled = strongSelf.stateValue.state.isVideoEnabled let isVideoEnabled = strongSelf.stateValue.state.isVideoEnabled
let isStream = strongSelf.stateValue.state.isStream let isStream = strongSelf.stateValue.state.isStream
let unmutedVideoLimit = strongSelf.stateValue.state.unmutedVideoLimit let unmutedVideoLimit = strongSelf.stateValue.state.unmutedVideoLimit
let upgradedPrivateCallId = strongSelf.stateValue.state.upgradedPrivateCallId
updatedParticipants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: strongSelf.stateValue.state.sortAscending) }) updatedParticipants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: strongSelf.stateValue.state.sortAscending) })
@ -1804,7 +2024,6 @@ public final class GroupCallParticipantsContext {
isVideoEnabled: isVideoEnabled, isVideoEnabled: isVideoEnabled,
unmutedVideoLimit: unmutedVideoLimit, unmutedVideoLimit: unmutedVideoLimit,
isStream: isStream, isStream: isStream,
upgradedPrivateCallId: upgradedPrivateCallId,
version: update.version version: update.version
), ),
overlayState: updatedOverlayState overlayState: updatedOverlayState
@ -1824,7 +2043,7 @@ public final class GroupCallParticipantsContext {
self.updateQueue.removeAll() self.updateQueue.removeAll()
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending) self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: "", ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending)
|> deliverOnMainQueue).start(next: { [weak self] state in |> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -1877,7 +2096,7 @@ public final class GroupCallParticipantsContext {
let account = self.account let account = self.account
let id = self.id let id = self.id
let accessHash = self.accessHash let reference = self.reference
let myPeerId = self.myPeerId let myPeerId = self.myPeerId
let signal: Signal<Api.Updates?, NoError> = self.account.postbox.transaction { transaction -> Api.InputPeer? in let signal: Signal<Api.Updates?, NoError> = self.account.postbox.transaction { transaction -> Api.InputPeer? in
@ -1907,7 +2126,7 @@ public final class GroupCallParticipantsContext {
raiseHandApi = nil raiseHandApi = nil
} }
return account.network.request(Api.functions.phone.editGroupCallParticipant(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), participant: inputPeer, muted: muted, volume: volume, raiseHand: raiseHandApi, videoStopped: nil, videoPaused: nil, presentationPaused: nil)) return account.network.request(Api.functions.phone.editGroupCallParticipant(flags: flags, call: reference.apiInputGroupCall, participant: inputPeer, muted: muted, volume: volume, raiseHand: raiseHandApi, videoStopped: nil, videoPaused: nil, presentationPaused: nil))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)
@ -1931,6 +2150,8 @@ public final class GroupCallParticipantsContext {
if updateCallId != id { if updateCallId != id {
continue loop continue loop
} }
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
continue loop
} }
stateUpdates.append(.state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId]))) stateUpdates.append(.state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId])))
default: default:
@ -1964,7 +2185,7 @@ public final class GroupCallParticipantsContext {
let account = self.account let account = self.account
let id = self.id let id = self.id
let accessHash = self.accessHash let reference = self.reference
let signal: Signal<Api.Updates?, NoError> = self.account.postbox.transaction { transaction -> Api.InputPeer? in let signal: Signal<Api.Updates?, NoError> = self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
@ -1993,7 +2214,7 @@ public final class GroupCallParticipantsContext {
flags |= 1 << 5 flags |= 1 << 5
} }
return account.network.request(Api.functions.phone.editGroupCallParticipant(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), participant: inputPeer, muted: nil, volume: nil, raiseHand: nil, videoStopped: videoMuted, videoPaused: videoPaused, presentationPaused: presentationPaused)) return account.network.request(Api.functions.phone.editGroupCallParticipant(flags: flags, call: reference.apiInputGroupCall, participant: inputPeer, muted: nil, volume: nil, raiseHand: nil, videoStopped: videoMuted, videoPaused: videoPaused, presentationPaused: presentationPaused))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)
@ -2017,6 +2238,8 @@ public final class GroupCallParticipantsContext {
if updateCallId != id { if updateCallId != id {
continue loop continue loop
} }
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
continue loop
} }
stateUpdates.append(.state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId]))) stateUpdates.append(.state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId])))
default: default:
@ -2053,7 +2276,7 @@ public final class GroupCallParticipantsContext {
videoPortrait = videoOrientation ? .boolTrue : .boolFalse videoPortrait = videoOrientation ? .boolTrue : .boolFalse
} }
self.updateShouldBeRecordingDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallRecord(flags: flags, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), title: title, videoPortrait: videoPortrait)) self.updateShouldBeRecordingDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallRecord(flags: flags, call: self.reference.apiInputGroupCall, title: title, videoPortrait: videoPortrait))
|> deliverOnMainQueue).start(next: { [weak self] updates in |> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2068,7 +2291,7 @@ public final class GroupCallParticipantsContext {
} }
self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted
self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), joinMuted: isMuted ? .boolTrue : .boolFalse)) self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: self.reference.apiInputGroupCall, joinMuted: isMuted ? .boolTrue : .boolFalse))
|> deliverOnMainQueue).start(next: { [weak self] updates in |> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2078,7 +2301,7 @@ public final class GroupCallParticipantsContext {
} }
public func resetInviteLinks() { public func resetInviteLinks() {
self.resetInviteLinksDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), joinMuted: nil)) self.resetInviteLinksDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: self.reference.apiInputGroupCall, joinMuted: nil))
|> deliverOnMainQueue).start(next: { [weak self] updates in |> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2096,7 +2319,7 @@ public final class GroupCallParticipantsContext {
} }
self.stateValue.state.subscribedToScheduled = subscribe self.stateValue.state.subscribedToScheduled = subscribe
self.subscribeDisposable.set(_internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, callId: self.id, accessHash: self.accessHash, subscribe: subscribe).start()) self.subscribeDisposable.set(_internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, reference: self.reference, subscribe: subscribe).start())
} }
public func loadMore(token: String) { public func loadMore(token: String) {
@ -2109,7 +2332,7 @@ public final class GroupCallParticipantsContext {
} }
self.isLoadingMore = true self.isLoadingMore = true
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: token, ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending) self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: token, ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending)
|> deliverOnMainQueue).start(next: { [weak self] state in |> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2236,8 +2459,8 @@ public struct GroupCallInviteLinks {
} }
} }
func _internal_groupCallInviteLinks(account: Account, callId: Int64, accessHash: Int64) -> Signal<GroupCallInviteLinks?, NoError> { func _internal_groupCallInviteLinks(account: Account, reference: InternalGroupCallReference, isConference: Bool) -> Signal<GroupCallInviteLinks?, NoError> {
let call = Api.InputGroupCall.inputGroupCall(id: callId, accessHash: accessHash) let call = reference.apiInputGroupCall
let listenerInvite: Signal<String?, NoError> = account.network.request(Api.functions.phone.exportGroupCallInvite(flags: 0, call: call)) let listenerInvite: Signal<String?, NoError> = account.network.request(Api.functions.phone.exportGroupCallInvite(flags: 0, call: call))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.phone.ExportedGroupCallInvite?, NoError> in |> `catch` { _ -> Signal<Api.phone.ExportedGroupCallInvite?, NoError> in
@ -2262,8 +2485,19 @@ func _internal_groupCallInviteLinks(account: Account, callId: Int64, accessHash:
return .single(nil) return .single(nil)
} }
if isConference {
return speakerInvite
|> map { speakerLink -> GroupCallInviteLinks? in
guard let speakerLink = speakerLink else {
return nil
}
return GroupCallInviteLinks(listenerLink: speakerLink, speakerLink: speakerLink)
}
}
return combineLatest(listenerInvite, speakerInvite) return combineLatest(listenerInvite, speakerInvite)
|> map { listenerLink, speakerLink in |> map { listenerLink, speakerLink in
if let listenerLink = listenerLink { if let listenerLink = listenerLink {
return GroupCallInviteLinks(listenerLink: listenerLink, speakerLink: speakerLink) return GroupCallInviteLinks(listenerLink: listenerLink, speakerLink: speakerLink)
} else { } else {
@ -2689,3 +2923,118 @@ func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, r
} }
} }
} }
public enum CreateConferenceCallError {
case generic
}
public final class EngineCreatedGroupCall {
public let slug: String
public let link: String
public let callInfo: GroupCallInfo
public init(slug: String, link: String, callInfo: GroupCallInfo) {
self.slug = slug
self.link = link
self.callInfo = callInfo
}
}
func _internal_createConferenceCall(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
return network.request(Api.functions.phone.createConferenceCall(randomId: Int32.random(in: Int32.min ... Int32.max)))
|> mapError { _ -> CreateConferenceCallError in
return .generic
}
|> mapToSignal { result in
switch result {
case let .groupCall(call, participants, _, chats, users):
return postbox.transaction { transaction -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> in
guard let info = GroupCallInfo(call) else {
return .fail(.generic)
}
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let parsedParticipants = participants.compactMap { GroupCallParticipantsContext.Participant($0, transaction: transaction) }
let _ = parsedParticipants
let speakerInvite: Signal<EngineCreatedGroupCall, CreateConferenceCallError> = network.request(Api.functions.phone.exportGroupCallInvite(flags: 1 << 0, call: .inputGroupCall(id: info.id, accessHash: info.accessHash)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.phone.ExportedGroupCallInvite?, NoError> in
return .single(nil)
}
|> castError(CreateConferenceCallError.self)
|> mapToSignal { result -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> in
if let result, case let .exportedGroupCallInvite(link) = result {
let slug = link.components(separatedBy: "/").last ?? link
return .single(EngineCreatedGroupCall(
slug: slug,
link: link,
callInfo: info
))
}
return .fail(.generic)
}
return speakerInvite
}
|> mapError { _ -> CreateConferenceCallError in
}
|> switchToLatest
}
}
}
public enum ConfirmAddConferenceParticipantError {
case generic
}
func _internal_pollConferenceCallBlockchain(network: Network, reference: InternalGroupCallReference, subChainId: Int, offset: Int, limit: Int) -> Signal<(blocks: [Data], nextOffset: Int)?, NoError> {
return network.request(Api.functions.phone.getGroupCallChainBlocks(call: reference.apiInputGroupCall, subChainId: Int32(subChainId), offset: Int32(offset), limit: Int32(limit)))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> map { result -> (blocks: [Data], nextOffset: Int)? in
guard let result = result else {
return nil
}
var blocks: [Data] = []
var nextOffset: Int?
for update in result.allUpdates {
switch update {
case let .updateGroupCallChainBlocks(_, updateSubChainId, updateBlocks, updateNextOffset):
if updateSubChainId == Int32(subChainId) {
blocks.append(contentsOf: updateBlocks.map { $0.makeData() })
nextOffset = Int(updateNextOffset)
}
default:
break
}
}
guard let nextOffset = nextOffset else {
return nil
}
return (blocks: blocks, nextOffset: nextOffset)
}
}
func _internal_sendConferenceCallBroadcast(account: Account, callId: Int64, accessHash: Int64, block: Data) -> Signal<Never, NoError> {
return account.network.request(Api.functions.phone.sendConferenceCallBroadcast(call: .inputGroupCall(id: callId, accessHash: accessHash), block: Buffer(data: block)))
|> retry(retryOnError: { _ in
return true
}, delayIncrement: 0.1, maxDelay: 1.0, maxRetries: 5, onQueue: Queue.concurrentDefaultQueue())
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
account.stateManager.addUpdates(result)
return .complete()
}
}

View File

@ -33,8 +33,8 @@ public extension TelegramEngine {
return _internal_saveCompleteCallDebugLog(account: self.account, callId: callId, logPath: logPath) return _internal_saveCompleteCallDebugLog(account: self.account, callId: callId, logPath: logPath)
} }
public func getCurrentGroupCall(callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> { public func getCurrentGroupCall(reference: InternalGroupCallReference, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
return _internal_getCurrentGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId) return _internal_getCurrentGroupCall(account: self.account, reference: reference, peerId: peerId)
} }
public func createGroupCall(peerId: PeerId, title: String?, scheduleDate: Int32?, isExternalStream: Bool) -> Signal<GroupCallInfo, CreateGroupCallError> { public func createGroupCall(peerId: PeerId, title: String?, scheduleDate: Int32?, isExternalStream: Bool) -> Signal<GroupCallInfo, CreateGroupCallError> {
@ -45,20 +45,20 @@ public extension TelegramEngine {
return _internal_startScheduledGroupCall(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash) return _internal_startScheduledGroupCall(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash)
} }
public func toggleScheduledGroupCallSubscription(peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> { public func toggleScheduledGroupCallSubscription(peerId: PeerId, reference: InternalGroupCallReference, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> {
return _internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash, subscribe: subscribe) return _internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, reference: reference, subscribe: subscribe)
} }
public func updateGroupCallJoinAsPeer(peerId: PeerId, joinAs: PeerId) -> Signal<Never, UpdateGroupCallJoinAsPeerError> { public func updateGroupCallJoinAsPeer(peerId: PeerId, joinAs: PeerId) -> Signal<Never, UpdateGroupCallJoinAsPeerError> {
return _internal_updateGroupCallJoinAsPeer(account: self.account, peerId: peerId, joinAs: joinAs) return _internal_updateGroupCallJoinAsPeer(account: self.account, peerId: peerId, joinAs: joinAs)
} }
public func getGroupCallParticipants(callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> { public func getGroupCallParticipants(reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
return _internal_getGroupCallParticipants(account: self.account, callId: callId, accessHash: accessHash, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending) return _internal_getGroupCallParticipants(account: self.account, reference: reference, 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, keyFingerprint: Int64?) -> Signal<JoinGroupCallResult, JoinGroupCallError> { public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, accessHash: accessHash, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, keyFingerprint: keyFingerprint) return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, reference: reference, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E)
} }
public func joinGroupCallAsScreencast(callId: Int64, accessHash: Int64, joinPayload: String) -> Signal<JoinGroupCallAsScreencastResult, JoinGroupCallError> { public func joinGroupCallAsScreencast(callId: Int64, accessHash: Int64, joinPayload: String) -> Signal<JoinGroupCallAsScreencastResult, JoinGroupCallError> {
@ -85,17 +85,33 @@ public extension TelegramEngine {
return _internal_inviteToGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId) return _internal_inviteToGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId)
} }
public func groupCallInviteLinks(callId: Int64, accessHash: Int64) -> Signal<GroupCallInviteLinks?, NoError> { public func groupCallInviteLinks(reference: InternalGroupCallReference, isConference: Bool) -> Signal<GroupCallInviteLinks?, NoError> {
return _internal_groupCallInviteLinks(account: self.account, callId: callId, accessHash: accessHash) return _internal_groupCallInviteLinks(account: self.account, reference: reference, isConference: isConference)
} }
public func editGroupCallTitle(callId: Int64, accessHash: Int64, title: String) -> Signal<Never, EditGroupCallTitleError> { public func editGroupCallTitle(callId: Int64, accessHash: Int64, title: String) -> Signal<Never, EditGroupCallTitleError> {
return _internal_editGroupCallTitle(account: self.account, callId: callId, accessHash: accessHash, title: title) return _internal_editGroupCallTitle(account: self.account, callId: callId, accessHash: accessHash, title: title)
} }
/*public func groupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<[FoundPeer], NoError> { public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
return _internal_groupCallDisplayAsAvailablePeers(network: self.account.network, postbox: self.account.postbox, peerId: peerId) return _internal_createConferenceCall(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
}*/ }
public func pollConferenceCallBlockchain(reference: InternalGroupCallReference, subChainId: Int, offset: Int, limit: Int) -> Signal<(blocks: [Data], nextOffset: Int)?, NoError> {
return _internal_pollConferenceCallBlockchain(network: self.account.network, reference: reference, subChainId: subChainId, offset: offset, limit: limit)
}
public func sendConferenceCallBroadcast(callId: Int64, accessHash: Int64, block: Data) -> Signal<Never, NoError> {
return _internal_sendConferenceCallBroadcast(account: self.account, callId: callId, accessHash: accessHash, block: block)
}
public func inviteConferenceCallParticipant(callId: Int64, accessHash: Int64, peerId: EnginePeer.Id) -> Signal<Never, NoError> {
return _internal_inviteConferenceCallParticipant(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId)
}
public func removeGroupCallBlockchainParticipant(callId: Int64, accessHash: Int64, block: @escaping ([EnginePeer.Id]) -> Data?) -> Signal<Never, RemoveGroupCallBlockchainParticipantError> {
return _internal_removeGroupCallBlockchainParticipants(account: self.account, callId: callId, accessHash: accessHash, block: block)
}
public func clearCachedGroupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<Never, NoError> { public func clearCachedGroupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<Never, NoError> {
return _internal_clearCachedGroupCallDisplayAsAvailablePeers(account: self.account, peerId: peerId) return _internal_clearCachedGroupCallDisplayAsAvailablePeers(account: self.account, peerId: peerId)
@ -124,8 +140,8 @@ public extension TelegramEngine {
return _internal_getVideoBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds, channelId: channelId, quality: quality) return _internal_getVideoBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds, channelId: channelId, quality: quality)
} }
public func groupCall(peerId: PeerId?, myPeerId: PeerId, id: Int64, accessHash: Int64, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?) -> GroupCallParticipantsContext { public func groupCall(peerId: PeerId?, myPeerId: PeerId, id: Int64, reference: InternalGroupCallReference, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?) -> GroupCallParticipantsContext {
return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, accessHash: accessHash, state: state, previousServiceState: previousServiceState) return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, reference: reference, state: state, previousServiceState: previousServiceState)
} }
public func serverTime() -> Signal<Int64, NoError> { public func serverTime() -> Signal<Int64, NoError> {

View File

@ -143,11 +143,15 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
} }
public final class JoinCallLinkInformation { public final class JoinCallLinkInformation {
public let id: Int64
public let accessHash: Int64
public let inviter: EnginePeer? public let inviter: EnginePeer?
public let members: [EnginePeer] public let members: [EnginePeer]
public let totalMemberCount: Int public let totalMemberCount: Int
public init(inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) { public init(id: Int64, accessHash: Int64, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
self.id = id
self.accessHash = accessHash
self.inviter = inviter self.inviter = inviter
self.members = members self.members = members
self.totalMemberCount = totalMemberCount self.totalMemberCount = totalMemberCount
@ -155,29 +159,41 @@ public final class JoinCallLinkInformation {
} }
func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> { func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
//TODO:release return _internal_getCurrentGroupCall(account: account, reference: .link(slug: hash))
|> mapError { error -> JoinLinkInfoError in
let invite: Signal<Api.ChatInvite?, JoinLinkInfoError> = account.network.request(Api.functions.messages.checkChatInvite(hash: hash), automaticFloodWait: false) switch error {
|> map(Optional.init) case .generic:
|> `catch` { error -> Signal<Api.ChatInvite?, JoinLinkInfoError> in return .generic
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.flood)
} else {
return .single(nil)
} }
} }
|> mapToSignal { call -> Signal<JoinCallLinkInformation, JoinLinkInfoError> in
return invite guard let call = call else {
|> mapToSignal { result -> Signal<JoinCallLinkInformation, JoinLinkInfoError> in
if let result {
switch result {
case let .chatInvite(_, _, _, _, participantsCount, participants, _, _, _, _):
return .single(JoinCallLinkInformation(inviter: nil, members: participants?.map({ EnginePeer(TelegramUser(user: $0)) }) ?? [], totalMemberCount: Int(participantsCount)))
default:
return .fail(.generic) return .fail(.generic)
} }
} else { var members: [EnginePeer] = []
return .fail(.generic) for participant in call.topParticipants {
members.append(EnginePeer(participant.peer))
} }
return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
}
}
func _internal_joinCallInvitationInformation(account: Account, messageId: MessageId) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
return _internal_getCurrentGroupCall(account: account, reference: .message(id: messageId))
|> mapError { error -> JoinLinkInfoError in
switch error {
case .generic:
return .generic
}
}
|> mapToSignal { call -> Signal<JoinCallLinkInformation, JoinLinkInfoError> in
guard let call = call else {
return .fail(.generic)
}
var members: [EnginePeer] = []
for participant in call.topParticipants {
members.append(EnginePeer(participant.peer))
}
return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
} }
} }

View File

@ -842,6 +842,10 @@ public extension TelegramEngine {
return _internal_joinCallLinkInformation(hash, account: self.account) return _internal_joinCallLinkInformation(hash, account: self.account)
} }
public func joinCallInvitationInformation(messageId: EngineMessage.Id) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
return _internal_joinCallInvitationInformation(account: self.account, messageId: messageId)
}
public func updatePeerTitle(peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> { public func updatePeerTitle(peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
return _internal_updatePeerTitle(account: self.account, peerId: peerId, title: title) return _internal_updatePeerTitle(account: self.account, peerId: peerId, title: title)
} }

View File

@ -534,6 +534,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
switch inputCall { switch inputCall {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream) updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
break
} }
} }
@ -787,6 +789,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
switch inputCall { switch inputCall {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream) updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
break
} }
} }

View File

@ -616,6 +616,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} }
} }
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case .conferenceCall:
//TODO:localize
let titleString = "Group call"
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .groupPhoneCall(_, _, scheduleDate, duration): case let .groupPhoneCall(_, _, scheduleDate, duration):
if let scheduleDate = scheduleDate { if let scheduleDate = scheduleDate {
if message.author?.id.namespace == Namespaces.Peer.CloudChannel { if message.author?.id.namespace == Namespaces.Peer.CloudChannel {

View File

@ -175,7 +175,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
break break
case .ignore: case .ignore:
return .fail return .fail
case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom: case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom:
return .waitForSingleTap return .waitForSingleTap
} }
} }

View File

@ -151,6 +151,7 @@ public struct ChatMessageBubbleContentTapAction {
case wallpaper case wallpaper
case theme case theme
case call(peerId: PeerId, isVideo: Bool) case call(peerId: PeerId, isVideo: Bool)
case conferenceCall(message: Message)
case openMessage case openMessage
case timecode(Double, String) case timecode(Double, String)
case tooltip(String, ASDisplayNode?, CGRect?) case tooltip(String, ASDisplayNode?, CGRect?)

View File

@ -213,6 +213,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
isAction = true isAction = true
if case .phoneCall = action.action { if case .phoneCall = action.action {
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
} else if case .conferenceCall = action.action {
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
} else if case .giftPremium = action.action { } else if case .giftPremium = action.action {
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
} else if case .giftStars = action.action { } else if case .giftStars = action.action {
@ -1251,7 +1253,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
break break
case .ignore: case .ignore:
return .fail return .fail
case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom: case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom:
return .waitForSingleTap return .waitForSingleTap
} }
} }
@ -1347,7 +1349,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return false return false
} }
else if let media = media as? TelegramMediaAction { else if let media = media as? TelegramMediaAction {
if case .phoneCall(_, _, _, _) = media.action { if case .phoneCall = media.action {
} else if case .conferenceCall = media.action {
} else { } else {
return false return false
} }
@ -2723,6 +2726,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
switch action.action { switch action.action {
case .phoneCall: case .phoneCall:
break break
case .conferenceCall:
break
default: default:
centerAligned = true centerAligned = true
} }
@ -5034,6 +5039,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return .optionalAction({ return .optionalAction({
self.item?.controllerInteraction.callPeer(peerId, isVideo) self.item?.controllerInteraction.callPeer(peerId, isVideo)
}) })
case let .conferenceCall(message):
return .optionalAction({
self.item?.controllerInteraction.openConferenceCall(message)
})
case .openMessage: case .openMessage:
if let item = self.item { if let item = self.item {
if let type = self.backgroundNode.type, case .none = type { if let type = self.backgroundNode.type, case .none = type {
@ -5221,6 +5230,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
break break
case .call: case .call:
break break
case .conferenceCall:
break
case .openMessage: case .openMessage:
break break
case let .timecode(timecode, text): case let .timecode(timecode, text):
@ -5516,6 +5527,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
for media in message.media { for media in message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
if case .phoneCall = action.action { if case .phoneCall = action.action {
} else if case .conferenceCall = action.action {
} else { } else {
canHaveSelection = false canHaveSelection = false
break break

View File

@ -123,6 +123,12 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
break break
} else if let action = media as? TelegramMediaAction, case let .conferenceCall(_, duration, _) = action.action {
isVideo = false
callDuration = duration
//TODO:localize
titleString = "Group Call"
break
} }
} }
@ -254,6 +260,9 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action { if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action {
isVideo = isVideoValue isVideo = isVideoValue
} else if let action = media as? TelegramMediaAction, case .conferenceCall = action.action {
item.controllerInteraction.openConferenceCall(item.message)
return
} }
} }
item.controllerInteraction.callPeer(item.message.id.peerId, isVideo) item.controllerInteraction.callPeer(item.message.id.peerId, isVideo)
@ -268,6 +277,8 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action { if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action {
isVideo = isVideoValue isVideo = isVideoValue
} else if let action = media as? TelegramMediaAction, case .conferenceCall = action.action {
return ChatMessageBubbleContentTapAction(content: .conferenceCall(message: item.message))
} }
} }
return ChatMessageBubbleContentTapAction(content: .call(peerId: item.message.id.peerId, isVideo: isVideo)) return ChatMessageBubbleContentTapAction(content: .call(peerId: item.message.id.peerId, isVideo: isVideo))

View File

@ -1603,8 +1603,18 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
} }
} else { } else {
onlyFullSizeVideoThumbnail = isSendingUpdated onlyFullSizeVideoThumbnail = isSendingUpdated
let codecConfiguration = HLSCodecConfiguration(context: context)
updateImageSignal = { synchronousLoad, _ in updateImageSignal = { synchronousLoad, _ in
return mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true) let videoReference: FileMediaReference = .message(message: MessageReference(message), media: file)
var hlsFiles: [(playlist: TelegramMediaFile, video: TelegramMediaFile)] = []
if let qualitySet = HLSQualitySet(baseFile: videoReference, codecConfiguration: codecConfiguration) {
for key in qualitySet.playlistFiles.keys.sorted() {
if let playlist = qualitySet.playlistFiles[key], let file = qualitySet.qualityFiles[key] {
hlsFiles.append((playlist.media, file.media))
}
}
}
return mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: videoReference, hlsFiles: hlsFiles, onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true)
} }
updateBlurredImageSignal = { synchronousLoad, _ in updateBlurredImageSignal = { synchronousLoad, _ in
return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: true) return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: true)

View File

@ -590,6 +590,7 @@ public final class ChatMessageAccessibilityData {
} }
else if let media = media as? TelegramMediaAction { else if let media = media as? TelegramMediaAction {
if case .phoneCall = media.action { if case .phoneCall = media.action {
} else if case .conferenceCall = media.action {
} else { } else {
canReply = false canReply = false
} }

View File

@ -264,6 +264,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openPeer(peer: EnginePeer(peer)) self?.openPeer(peer: EnginePeer(peer))
}, callPeer: { peerId, isVideo in }, callPeer: { peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, openConferenceCall: { message in
self?.controllerInteraction?.openConferenceCall(message)
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in
if let strongSelf = self { if let strongSelf = self {
@ -372,7 +374,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return self?.getNavigationController() return self?.getNavigationController()
}, chatControllerNode: { [weak self] in }, chatControllerNode: { [weak self] in
return self return self
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { [weak self] action, params in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, longTap: { [weak self] action, params in
if let strongSelf = self { if let strongSelf = self {
switch action { switch action {
case let .url(url): case let .url(url):
@ -1383,7 +1386,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
let _ = (signal let _ = (signal
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in |> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall( navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
inviter: resolvedCallLink.inviter, members: resolvedCallLink.members, totalMemberCount: resolvedCallLink.totalMemberCount id: resolvedCallLink.id,
accessHash: resolvedCallLink.accessHash,
slug: link,
inviter: resolvedCallLink.inviter,
members: resolvedCallLink.members,
totalMemberCount: resolvedCallLink.totalMemberCount
)))) ))))
}) })
case let .localization(identifier): case let .localization(identifier):

View File

@ -428,7 +428,8 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, canSendMessages: { }, canSendMessages: {

View File

@ -208,6 +208,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let chatControllerNode: () -> ASDisplayNode? public let chatControllerNode: () -> ASDisplayNode?
public let presentGlobalOverlayController: (ViewController, Any?) -> Void public let presentGlobalOverlayController: (ViewController, Any?) -> Void
public let callPeer: (PeerId, Bool) -> Void public let callPeer: (PeerId, Bool) -> Void
public let openConferenceCall: (Message) -> Void
public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void
public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void
public let openSearch: () -> Void public let openSearch: () -> Void
@ -367,6 +368,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
chatControllerNode: @escaping () -> ASDisplayNode?, chatControllerNode: @escaping () -> ASDisplayNode?,
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
callPeer: @escaping (PeerId, Bool) -> Void, callPeer: @escaping (PeerId, Bool) -> Void,
openConferenceCall: @escaping (Message) -> Void,
longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void,
openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void, openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void,
openSearch: @escaping () -> Void, openSearch: @escaping () -> Void,
@ -482,6 +484,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
self.chatControllerNode = chatControllerNode self.chatControllerNode = chatControllerNode
self.presentGlobalOverlayController = presentGlobalOverlayController self.presentGlobalOverlayController = presentGlobalOverlayController
self.callPeer = callPeer self.callPeer = callPeer
self.openConferenceCall = openConferenceCall
self.longTap = longTap self.longTap = longTap
self.openCheckoutOrReceipt = openCheckoutOrReceipt self.openCheckoutOrReceipt = openCheckoutOrReceipt
self.openSearch = openSearch self.openSearch = openSearch

View File

@ -394,7 +394,20 @@ private final class JoinSubjectScreenComponent: Component {
self.environment?.controller()?.dismiss() self.environment?.controller()?.dismiss()
}) })
case let .groupCall(groupCall): case let .groupCall(groupCall):
let _ = groupCall component.context.sharedContext.callManager?.joinConferenceCall(
accountContext: component.context,
initialCall: EngineGroupCallDescription(
id: groupCall.id,
accessHash: groupCall.accessHash,
title: nil,
scheduleTimestamp: nil,
subscribedToScheduled: false,
isStream: false
),
reference: .link(slug: groupCall.slug),
mode: .joining
)
self.environment?.controller()?.dismiss() self.environment?.controller()?.dismiss()
} }
} }

View File

@ -157,6 +157,7 @@ swift_library(
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
"//submodules/TelegramUI/Components/BatchVideoRendering", "//submodules/TelegramUI/Components/BatchVideoRendering",
"//submodules/TelegramUI/Components/GifVideoLayer", "//submodules/TelegramUI/Components/GifVideoLayer",
"//submodules/Components/HierarchyTrackingLayer",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -17,7 +17,7 @@ import Postbox
import TelegramCore import TelegramCore
import EmojiStatusComponent import EmojiStatusComponent
import GalleryUI import GalleryUI
import HierarchyTrackingLayer
final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let context: AccountContext let context: AccountContext
@ -32,6 +32,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
private var videoContent: NativeVideoContent? private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double? private var videoStartTimestamp: Double?
private let hierarchyTrackingLayer = HierarchyTrackingLayer()
var isExpanded: Bool = false var isExpanded: Bool = false
var canAttachVideo: Bool = true { var canAttachVideo: Bool = true {
didSet { didSet {
@ -78,6 +80,21 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
tapGestureRecognizer.isEnabled = true tapGestureRecognizer.isEnabled = true
strongSelf.contextAction?(strongSelf.containerNode, gesture) strongSelf.contextAction?(strongSelf.containerNode, gesture)
} }
self.hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] value in
guard let self else {
return
}
if value {
self.updateFromParams()
} else {
self.videoNode?.removeFromSupernode()
self.videoNode = nil
self.videoContent = nil
}
}
self.layer.addSublayer(self.hierarchyTrackingLayer)
} }
deinit { deinit {
@ -190,8 +207,51 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
} }
} }
private struct Params {
let peer: Peer?
let threadId: Int64?
let threadInfo: EngineMessageHistoryThread.Info?
let item: PeerInfoAvatarListItem?
let theme: PresentationTheme
let avatarSize: CGFloat
let isExpanded: Bool
let isSettings: Bool
init(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) {
self.peer = peer
self.threadId = threadId
self.threadInfo = threadInfo
self.item = item
self.theme = theme
self.avatarSize = avatarSize
self.isExpanded = isExpanded
self.isSettings = isSettings
}
}
var removedPhotoResourceIds = Set<String>() var removedPhotoResourceIds = Set<String>()
private var params: Params?
private func updateFromParams() {
guard let params = self.params else {
return
}
self.update(
peer: params.peer,
threadId: params.threadId,
threadInfo: params.threadInfo,
item: params.item,
theme: params.theme,
avatarSize: params.avatarSize,
isExpanded: params.isExpanded,
isSettings: params.isSettings
)
}
func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) {
self.params = Params(peer: peer, threadId: threadId, threadInfo: threadInfo, item: item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: isSettings)
if let peer = peer { if let peer = peer {
let previousItem = self.item let previousItem = self.item
var item = item var item = item
@ -345,6 +405,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if videoContent.id != self.videoContent?.id { if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode() self.videoNode?.removeFromSupernode()
if self.hierarchyTrackingLayer.isInHierarchy {
let mediaManager = self.context.sharedContext.mediaManager let mediaManager = self.context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false videoNode.isUserInteractionEnabled = false
@ -392,6 +453,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.avatarNode.contentNode.addSubnode(videoNode) self.avatarNode.contentNode.addSubnode(videoNode)
} }
}
} else { } else {
if let markupNode = self.markupNode { if let markupNode = self.markupNode {
self.markupNode = nil self.markupNode = nil

View File

@ -196,9 +196,11 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
self.videoContent = nil self.videoContent = nil
self.videoNode = nil self.videoNode = nil
DispatchQueue.main.async {
videoNode.removeFromSupernode() videoNode.removeFromSupernode()
} }
} }
}
} else if let videoNode = self.videoNode { } else if let videoNode = self.videoNode {
self.videoStartTimestamp = nil self.videoStartTimestamp = nil
self.videoContent = nil self.videoContent = nil

View File

@ -3617,6 +3617,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, chatControllerNode: { }, chatControllerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, longTap: { [weak self] content, _ in }, longTap: { [weak self] content, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -5430,7 +5431,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, openPeer: { [weak self] peer, navigation in }, openPeer: { [weak self] peer, navigation in
self?.openPeer(peerId: peer.id, navigation: navigation) self?.openPeer(peerId: peer.id, navigation: navigation)
}, callPeer: { peerId, isVideo in }, callPeer: { peerId, isVideo in
//self?.controllerInteraction?.callPeer(peerId) }, openConferenceCall: { _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in
if let strongSelf = self { if let strongSelf = self {

View File

@ -2793,7 +2793,7 @@ final class StorageUsageScreenComponent: Component {
let _ = self let _ = self
}, },
callPeer: { _, _ in callPeer: { _, _ in
//self?.controllerInteraction?.callPeer(peerId) }, openConferenceCall: { _ in
}, },
enqueueMessage: { _ in enqueueMessage: { _ in
}, },

View File

@ -307,6 +307,8 @@ extension ChatControllerImpl {
switch action.action { switch action.action {
case .phoneCall: case .phoneCall:
break break
case .conferenceCall:
break
default: default:
hideReactionPanelTail = true hideReactionPanelTail = true
} }

View File

@ -177,6 +177,7 @@ extension ChatControllerImpl {
contextController?.present(c, in: .current) contextController?.present(c, in: .current)
} }
let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .singleMessage(message.id))) let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .singleMessage(message.id)))
} }
} }

View File

@ -133,6 +133,7 @@ import NotificationPeerExceptionController
import AdsReportScreen import AdsReportScreen
import AdUI import AdUI
import ChatMessagePaymentAlertController import ChatMessagePaymentAlertController
import TelegramCallsUI
public enum ChatControllerPeekActions { public enum ChatControllerPeekActions {
case standard case standard
@ -1366,6 +1367,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil) self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
}, callPeer: { [weak self] peerId, isVideo in }, callPeer: { [weak self] peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo) self?.controllerInteraction?.callPeer(peerId, isVideo)
}, openConferenceCall: { [weak self] message in
self?.controllerInteraction?.openConferenceCall(message)
}, enqueueMessage: { [weak self] message in }, enqueueMessage: { [weak self] message in
self?.sendMessages([message]) self?.sendMessages([message])
}, sendSticker: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] fileReference, sourceNode, sourceRect in }, sendSticker: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] fileReference, sourceNode, sourceRect in
@ -2861,6 +2864,50 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
}) })
} }
}, openConferenceCall: { [weak self] message in
guard let self else {
return
}
var action: TelegramMediaAction?
for media in message.media {
if let media = media as? TelegramMediaAction {
action = media
break
}
}
guard case let .conferenceCall(callId, duration, _) = action?.action else {
return
}
if duration != nil {
return
}
if let currentGroupCallController = self.context.sharedContext as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == callId {
self.context.sharedContext.navigateToCurrentCall()
return
}
let signal = self.context.engine.peers.joinCallInvitationInformation(messageId: message.id)
let _ = (signal
|> deliverOnMainQueue).startStandalone(next: { [weak self] resolvedCallLink in
guard let self else {
return
}
self.context.sharedContext.callManager?.joinConferenceCall(
accountContext: self.context,
initialCall: EngineGroupCallDescription(
id: resolvedCallLink.id,
accessHash: resolvedCallLink.accessHash,
title: nil,
scheduleTimestamp: nil,
subscribedToScheduled: false,
isStream: false
),
reference: .message(id: message.id),
mode: .joining
)
})
}, longTap: { [weak self] action, params in }, longTap: { [weak self] action, params in
if let self { if let self {
self.openLinkLongTap(action, params: params) self.openLinkLongTap(action, params: params)

View File

@ -20,6 +20,7 @@ import TelegramUniversalVideoContent
import GradientBackground import GradientBackground
import Svg import Svg
import UniversalMediaPlayer import UniversalMediaPlayer
import RangeSet
public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> { public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
if let representation = representation as? CachedStickerAJpegRepresentation { if let representation = representation as? CachedStickerAJpegRepresentation {
@ -66,19 +67,49 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
return EmptyDisposable return EmptyDisposable
} }
} }
/*return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) } else if let _ = representation as? CachedVideoPrefixFirstFrameRepresentation {
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in return Signal { subscriber in
if data.complete { if let size = resource.size {
return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data) let videoSource = UniversalSoftwareVideoSource(mediaBox: account.postbox.mediaBox, source: .direct(resource: resource, size: size), automaticallyFetchHeader: false, hintVP9: false)
|> `catch` { _ -> Signal<CachedMediaResourceRepresentationResult, NoError> in let disposable = videoSource.takeFrame(at: 0.0).start(next: { value in
return .complete() switch value {
case let .image(image):
if let image {
if let imageData = image.jpegData(compressionQuality: 0.6) {
subscriber.putNext(.data(imageData))
subscriber.putNext(.done)
subscriber.putCompletion()
} }
} else if let size = resource.size {
return videoFirstFrameData(account: account, resource: resource, chunkSize: min(size, 192 * 1024))
} else {
return .complete()
} }
case .waitingForData:
break
}
})
subscriber.keepAlive(videoSource)
/*let reader = FFMpegFileReader(source: .resource(mediaBox: account.postbox.mediaBox, resource: resource, resourceSize: size, mappedRanges: [0 ..< size]), useHardwareAcceleration: false, selectedStream: .mediaType(.video), seek: nil, maxReadablePts: nil)
reader?.readFrame()
print("ready to fetch \(representation.prefixLength)")*/
return ActionDisposable {
disposable.dispose()
}
}
/*let disposable = (account.postbox.mediaBox.resourceRangesStatus(resource)
|> filter { ranges in
return ranges.isSuperset(of: RangeSet(0 ..< Int64(representation.prefixLength)))
}
|> take(1)).start(next: { _ in
})
return ActionDisposable {
disposable.dispose()
}*/ }*/
return EmptyDisposable
}
} else if let representation = representation as? CachedScaledVideoFirstFrameRepresentation { } else if let representation = representation as? CachedScaledVideoFirstFrameRepresentation {
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in |> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in

View File

@ -395,7 +395,12 @@ func openResolvedUrlImpl(
let _ = (signal let _ = (signal
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in |> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall( navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
inviter: resolvedCallLink.inviter, members: resolvedCallLink.members, totalMemberCount: resolvedCallLink.totalMemberCount id: resolvedCallLink.id,
accessHash: resolvedCallLink.accessHash,
slug: link,
inviter: resolvedCallLink.inviter,
members: resolvedCallLink.members,
totalMemberCount: resolvedCallLink.totalMemberCount
)))) ))))
}) })
case let .localization(identifier): case let .localization(identifier):

View File

@ -998,6 +998,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
convertedUrl = "https://t.me/c/\(channel)?boost" convertedUrl = "https://t.me/c/\(channel)?boost"
} }
} }
} else if parsedUrl.host == "call" {
if let components = URLComponents(string: "/?" + query) {
var slug: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "slug" {
slug = value
}
}
}
}
if let slug = slug {
convertedUrl = "https://t.me/call/\(slug)"
}
}
} }
} else { } else {
if parsedUrl.host == "importStickers" { if parsedUrl.host == "importStickers" {

View File

@ -117,6 +117,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, presentGlobalOverlayController: { _, _ in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, longTap: { _, _ in }, longTap: { _, _ in
}, openCheckoutOrReceipt: { _, _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
@ -317,7 +318,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location { if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location {
playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore) playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore)
} }
return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation))
} }
return false return false
} }

View File

@ -2113,7 +2113,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {
return nil return nil
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
}, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in
return .none return .none
}, canSendMessages: { }, canSendMessages: {

View File

@ -216,6 +216,11 @@ final class OngoingGroupCallBroadcastPartTaskImpl: NSObject, OngoingGroupCallBro
} }
} }
public protocol OngoingGroupCallEncryptionContext: AnyObject {
func encrypt(message: Data) -> Data?
func decrypt(message: Data) -> Data?
}
public final class OngoingGroupCallContext { public final class OngoingGroupCallContext {
public struct AudioStreamData { public struct AudioStreamData {
public var engine: TelegramEngine public var engine: TelegramEngine
@ -495,11 +500,11 @@ public final class OngoingGroupCallContext {
preferX264: Bool, preferX264: Bool,
logPath: String, logPath: String,
onMutedSpeechActivityDetected: @escaping (Bool) -> Void, onMutedSpeechActivityDetected: @escaping (Bool) -> Void,
encryptionKey: Data?,
isConference: Bool, isConference: Bool,
audioIsActiveByDefault: Bool, audioIsActiveByDefault: Bool,
isStream: Bool, isStream: Bool,
sharedAudioDevice: OngoingCallContext.AudioDevice? sharedAudioDevice: OngoingCallContext.AudioDevice?,
encryptionContext: OngoingGroupCallEncryptionContext?
) { ) {
self.queue = queue self.queue = queue
@ -632,9 +637,17 @@ public final class OngoingGroupCallContext {
onMutedSpeechActivityDetected(value) onMutedSpeechActivityDetected(value)
}, },
audioDevice: audioDevice?.impl, audioDevice: audioDevice?.impl,
encryptionKey: encryptionKey,
isConference: isConference, isConference: isConference,
isActiveByDefault: audioIsActiveByDefault isActiveByDefault: audioIsActiveByDefault,
encryptDecrypt: encryptionContext.flatMap { encryptionContext in
return { data, isEncrypt in
if isEncrypt {
return encryptionContext.encrypt(message: data)
} else {
return encryptionContext.decrypt(message: data)
}
}
}
) )
#else #else
self.context = GroupCallThreadLocalContext( self.context = GroupCallThreadLocalContext(
@ -733,9 +746,17 @@ public final class OngoingGroupCallContext {
logPath: logPath, logPath: logPath,
statsLogPath: tempStatsLogPath, statsLogPath: tempStatsLogPath,
audioDevice: nil, audioDevice: nil,
encryptionKey: encryptionKey,
isConference: isConference, isConference: isConference,
isActiveByDefault: true isActiveByDefault: true,
encryptDecrypt: encryptionContext.flatMap { encryptionContext in
return { data, isEncrypt in
if isEncrypt {
return encryptionContext.encrypt(data)
} else {
return encryptionContext.decrypt(data)
}
}
}
) )
#endif #endif
@ -1184,10 +1205,10 @@ public final class OngoingGroupCallContext {
} }
} }
public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, preferX264: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, encryptionKey: Data?, isConference: Bool, audioIsActiveByDefault: Bool, isStream: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice?) { public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, preferX264: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, isConference: Bool, audioIsActiveByDefault: Bool, isStream: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice?, encryptionContext: OngoingGroupCallEncryptionContext?) {
let queue = self.queue let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: { self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, enableSystemMute: enableSystemMute, preferX264: preferX264, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected, encryptionKey: encryptionKey, isConference: isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: isStream, sharedAudioDevice: sharedAudioDevice) return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, enableSystemMute: enableSystemMute, preferX264: preferX264, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected, isConference: isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: isStream, sharedAudioDevice: sharedAudioDevice, encryptionContext: encryptionContext)
}) })
} }

View File

@ -172,6 +172,7 @@ objc_library(
"//submodules/ffmpeg:ffmpeg", "//submodules/ffmpeg:ffmpeg",
"//third-party/rnnoise:rnnoise", "//third-party/rnnoise:rnnoise",
"//third-party/libyuv:libyuv", "//third-party/libyuv:libyuv",
"//third-party/td:TdBinding",
] + (["//third-party/libx264:libx264"] if enable_x264 else []), ] + (["//third-party/libx264:libx264"] if enable_x264 else []),
sdk_frameworks = [ sdk_frameworks = [
"Foundation", "Foundation",

View File

@ -452,9 +452,9 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
statsLogPath:(NSString * _Nonnull)statsLogPath statsLogPath:(NSString * _Nonnull)statsLogPath
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
encryptionKey:(NSData * _Nullable)encryptionKey
isConference:(bool)isConference isConference:(bool)isConference
isActiveByDefault:(bool)isActiveByDefault; isActiveByDefault:(bool)isActiveByDefault
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryptDecrypt;
- (void)stop:(void (^ _Nullable)())completion; - (void)stop:(void (^ _Nullable)())completion;

View File

@ -42,6 +42,8 @@
#import "platform/darwin/TGRTCCVPixelBuffer.h" #import "platform/darwin/TGRTCCVPixelBuffer.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
#import <TdBinding/TdBinding.h>
@implementation OngoingCallConnectionDescription @implementation OngoingCallConnectionDescription
- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag { - (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag {
@ -2374,9 +2376,9 @@ private:
statsLogPath:(NSString * _Nonnull)statsLogPath statsLogPath:(NSString * _Nonnull)statsLogPath
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
encryptionKey:(NSData * _Nullable)encryptionKey
isConference:(bool)isConference isConference:(bool)isConference
isActiveByDefault:(bool)isActiveByDefault { isActiveByDefault:(bool)isActiveByDefault
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryptDecrypt {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_queue = queue; _queue = queue;
@ -2444,16 +2446,17 @@ isActiveByDefault:(bool)isActiveByDefault {
std::string statsLogPathValue(statsLogPath.length == 0 ? "" : statsLogPath.UTF8String); std::string statsLogPathValue(statsLogPath.length == 0 ? "" : statsLogPath.UTF8String);
std::optional<tgcalls::EncryptionKey> mappedEncryptionKey; std::function<std::vector<uint8_t>(std::vector<uint8_t> const &, bool)> mappedEncryptDecrypt;
if (encryptionKey) { if (encryptDecrypt) {
auto encryptionKeyValue = std::make_shared<std::array<uint8_t, 256>>(); NSData * _Nullable (^encryptDecryptBlock)(NSData * _Nonnull, bool) = [encryptDecrypt copy];
memcpy(encryptionKeyValue->data(), encryptionKey.bytes, encryptionKey.length); mappedEncryptDecrypt = [encryptDecryptBlock](std::vector<uint8_t> const &message, bool isEncrypt) -> std::vector<uint8_t> {
NSData *mappedMessage = [[NSData alloc] initWithBytes:message.data() length:message.size()];
#if DEBUG NSData *result = encryptDecryptBlock(mappedMessage, isEncrypt);
NSLog(@"Encryption key: %@", [encryptionKey base64EncodedStringWithOptions:0]); if (!result) {
#endif return std::vector<uint8_t>();
}
mappedEncryptionKey = tgcalls::EncryptionKey(encryptionKeyValue, true); return std::vector<uint8_t>((uint8_t *)result.bytes, ((uint8_t *)result.bytes) + result.length);
};
} }
__weak GroupCallThreadLocalContext *weakSelf = self; __weak GroupCallThreadLocalContext *weakSelf = self;
@ -2681,7 +2684,7 @@ isActiveByDefault:(bool)isActiveByDefault {
} }
}]; }];
}, },
.encryptionKey = mappedEncryptionKey, .e2eEncryptDecrypt = mappedEncryptDecrypt,
.isConference = isConference .isConference = isConference
})); }));
} }

View File

@ -29,7 +29,9 @@ load(
licenses(["notice"]) licenses(["notice"])
exports_files(["LICENSE"]) exports_files(
["LICENSE"] + crypto_headers + ssl_headers,
)
# By default, the C files will expect assembly files, if any, to be linked in # By default, the C files will expect assembly files, if any, to be linked in
# with the build. This default can be flipped with -DOPENSSL_NO_ASM. If building # with the build. This default can be flipped with -DOPENSSL_NO_ASM. If building

150
third-party/td/BUILD vendored Normal file
View File

@ -0,0 +1,150 @@
load(
"//third-party/boringssl:BUILD.generated.bzl",
"crypto_headers",
)
headers = [
"td/e2e/e2e_api.h",
"td/e2e/e2e_errors.h",
]
libs = [
"tde2e",
"tdutils",
]
filegroup(
name = "td_sources",
srcs = glob([
"td/**/*"
]),
)
genrule(
name = "td_build",
srcs = [
"build-td-bazel.sh",
":td_sources",
"@cmake_tar_gz//file",
"//third-party/boringssl:crypto",
] + [
"//third-party/boringssl:{}".format(header) for header in crypto_headers
],
cmd_bash =
"""
set -ex
if [ "$(TARGET_CPU)" == "ios_arm64" ]; then
BUILD_ARCH="arm64"
elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then
BUILD_ARCH="sim_arm64"
else
echo "Unsupported architecture $(TARGET_CPU)"
fi
BUILD_DIR="$(RULEDIR)/build_$${BUILD_ARCH}"
rm -rf "$$BUILD_DIR"
mkdir -p "$$BUILD_DIR"
CMAKE_DIR="$$(pwd)/$$BUILD_DIR/cmake"
rm -rf "$$CMAKE_DIR"
mkdir -p "$$CMAKE_DIR"
tar -xzf "$(location @cmake_tar_gz//file)" -C "$$CMAKE_DIR"
OPENSSL_BASE_DIR="$$(pwd)/$$BUILD_DIR"
OPENSSL_DIR="$$OPENSSL_BASE_DIR/openssl"
rm -rf "$$OPENSSL_DIR"
mkdir -p "$$OPENSSL_DIR"
mkdir -p "$$OPENSSL_DIR/lib"
mkdir -p "$$OPENSSL_DIR/src/include/openssl"
mkdir -p "$$OPENSSL_DIR/src/include/openssl/experimental"
cp -R "$(location //third-party/boringssl:crypto)" "$$OPENSSL_DIR/lib/libcrypto.a"
# Copy header files
""" +
"\n".join([
"cp -f \"$(location //third-party/boringssl:{})\" \"$$OPENSSL_DIR/{}\"".format(header, header)
for header in crypto_headers
]) +
"""
cp $(location :build-td-bazel.sh) "$$BUILD_DIR/"
SOURCE_PATH="third-party/td/td"
cp -R "$$SOURCE_PATH" "$$BUILD_DIR/"
mkdir -p "$$BUILD_DIR/Public/td"
PATH="$$PATH:$$CMAKE_DIR/cmake-3.23.1-macos-universal/CMake.app/Contents/bin" sh $$BUILD_DIR/build-td-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/td" "$$BUILD_DIR" "$$OPENSSL_DIR"
""" +
"\n".join([
"cp -f \"$$BUILD_DIR/td/tde2e/{}\" \"$(location Public/td/{})\"".format(header, header) for header in headers
]) +
"\n" +
"\n".join([
"cp -f \"$$BUILD_DIR/build/tde2e/libtde2e.a\" \"$(location Public/td/lib/libtde2e.a)\"",
"cp -f \"$$BUILD_DIR/build/tdutils/libtdutils.a\" \"$(location Public/td/lib/libtdutils.a)\"",
]),
outs = [
"Public/td/" + x for x in headers
] +
[
"Public/td/lib/lib{}.a".format(x) for x in libs
],
visibility = [
"//visibility:public",
]
)
cc_library(
name = "td_lib",
srcs = [":Public/td/lib/lib" + x + ".a" for x in libs],
)
objc_library(
name = "td",
module_name = "td",
enable_modules = True,
hdrs = [":Public/td/" + x for x in headers],
includes = [
"Public",
"Public/td",
],
deps = [
":td_lib",
"//third-party/boringssl:crypto",
],
visibility = [
"//visibility:public",
],
)
objc_library(
name = "TdBinding",
module_name = "TdBinding",
enable_modules = True,
srcs = glob([
"TdBinding/Sources/**/*.m",
"TdBinding/Sources/**/*.mm",
"TdBinding/Sources/**/*.h",
]),
hdrs = glob([
"TdBinding/Public/**/*.h",
]),
copts = [
"-Werror",
],
includes = [
"TdBinding/Public",
],
deps = [
":td",
],
sdk_frameworks = [
],
visibility = [
"//visibility:public",
]
)

View File

@ -0,0 +1,56 @@
#ifndef TDBINDING_H
#define TDBINDING_H
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
NS_ASSUME_NONNULL_BEGIN
@interface TdKeyPair : NSObject
@property (nonatomic, readonly) int64_t keyId;
@property (nonatomic, strong, readonly) NSData *publicKey;
- (nullable instancetype)initWithKeyId:(int64_t)keyId publicKey:(NSData *)publicKey;
+ (nullable instancetype)generate;
@end
@interface TdCallParticipant : NSObject
@property (nonatomic, strong, readonly) NSData *publicKey;
@property (nonatomic, readonly) int64_t userId;
- (nullable instancetype)initWithPublicKey:(NSData *)publicKey userId:(int64_t)userId;
@end
@interface TdCall : NSObject
+ (nullable instancetype)makeWithKeyPair:(TdKeyPair *)keyPair latestBlock:(NSData *)latestBlock;
- (NSArray<NSData *> *)takeOutgoingBroadcastBlocks;
- (NSData *)emojiState;
- (void)applyBlock:(NSData *)block;
- (void)applyBroadcastBlock:(NSData *)block;
- (nullable NSData *)encrypt:(NSData *)message;
- (nullable NSData *)decrypt:(NSData *)message;
@end
NSData * _Nullable tdGenerateZeroBlock(TdKeyPair *keyPair, int64_t userId);
NSData * _Nullable tdGenerateSelfAddBlock(TdKeyPair *keyPair, int64_t userId, NSData *previousBlock);
NS_ASSUME_NONNULL_END
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,275 @@
#import <TdBinding/TdBinding.h>
#include <td/e2e/e2e_api.h>
static NSString *hexStringFromData(NSData *data) {
NSMutableString *string = [[NSMutableString alloc] initWithCapacity:data.length * 2];
for (NSUInteger i = 0; i < data.length; i++) {
[string appendFormat:@"%02x", ((uint8_t *)data.bytes)[i]];
}
return string;
}
@interface TdKeyPair () {
tde2e_api::PrivateKeyId _keyId;
NSData *_publicKey;
}
@end
@implementation TdKeyPair
- (nullable instancetype)initWithKeyId:(int64_t)keyId publicKey:(NSData *)publicKey {
self = [super init];
if (self != nil) {
_keyId = keyId;
_publicKey = publicKey;
}
return self;
}
- (int64_t)keyId {
return _keyId;
}
- (NSData *)publicKey {
return _publicKey;
}
+ (nullable instancetype)generate {
auto privateKey = tde2e_api::key_generate_private_key();
if (!privateKey.is_ok()) {
return nil;
}
tde2e_api::PrivateKeyId privateKeyId = privateKey.value();
auto publicKey = tde2e_api::key_to_public_key(privateKeyId);
if (!publicKey.is_ok()) {
return nil;
}
NSData *parsedPublicKey = [[NSData alloc] initWithBytes:publicKey.value().data() length:publicKey.value().size()];
return [[TdKeyPair alloc] initWithKeyId:privateKeyId publicKey:parsedPublicKey];
}
@end
@implementation TdCallParticipant
- (nullable instancetype)initWithPublicKey:(NSData *)publicKey userId:(int64_t)userId {
self = [super init];
if (self != nil) {
_publicKey = publicKey;
_userId = userId;
}
return self;
}
@end
@interface TdCall ()
@property (nonatomic, strong) TdKeyPair *keyPair;
@property (nonatomic) int64_t callId;
@end
@implementation TdCall
- (instancetype)initWithId:(int64_t)callId keyPair:(TdKeyPair *)keyPair {
self = [super init];
if (self != nil) {
_callId = callId;
_keyPair = keyPair;
}
return self;
}
- (void)dealloc {
tde2e_api::call_destroy(_callId);
}
+ (nullable instancetype)makeWithKeyPair:(TdKeyPair *)keyPair latestBlock:(NSData *)latestBlock {
std::string mappedLatestBlock((uint8_t *)latestBlock.bytes, ((uint8_t *)latestBlock.bytes) + latestBlock.length);
#if DEBUG
auto describeResult = tde2e_api::call_describe_block(mappedLatestBlock);
if (describeResult.is_ok()) {
NSString *utf8String = [[NSString alloc] initWithBytes:describeResult.value().data() length:describeResult.value().size() encoding:NSUTF8StringEncoding];
if (utf8String) {
NSLog(@"TdCall.makeWithKeyPair block: %@", utf8String);
} else {
NSString *lossyString = [[NSString alloc] initWithData:[NSData dataWithBytes:describeResult.value().data() length:describeResult.value().size()] encoding:NSASCIIStringEncoding];
if (lossyString) {
NSLog(@"TdCall.makeWithKeyPair block (lossy conversion): %@", lossyString);
} else {
NSLog(@"TdCall.makeWithKeyPair block: [binary data, length: %lu]", (unsigned long)describeResult.value().size());
}
}
} else {
NSLog(@"TdCall.makeWithKeyPair describe block failed");
}
#endif
auto call = tde2e_api::call_create(keyPair.keyId, mappedLatestBlock);
if (!call.is_ok()) {
return nil;
}
return [[TdCall alloc] initWithId:call.value() keyPair: keyPair];
}
- (NSArray<NSData *> *)takeOutgoingBroadcastBlocks {
NSMutableArray<NSData *> *outboundBroadcastBlocks = [[NSMutableArray alloc] init];
auto outboundMessages = tde2e_api::call_pull_outbound_messages(_callId);
if (!outboundMessages.is_ok()) {
return @[];
}
for (const auto &it : outboundMessages.value()) {
#if DEBUG
auto describeResult = tde2e_api::call_describe_message(it);
if (describeResult.is_ok()) {
NSLog(@"TdCall.takeOutgoingBroadcastBlocks call_pull_outbound_messages: block %@", [[NSString alloc] initWithBytes:describeResult.value().data() length:describeResult.value().size() encoding:NSUTF8StringEncoding]);
} else {
NSLog(@"TdCall.takeOutgoingBroadcastBlocks call_pull_outbound_messages: describe block failed");
}
#endif
NSData *outBlock = [[NSData alloc] initWithBytes:it.data() length:it.size()];
[outboundBroadcastBlocks addObject:outBlock];
}
return outboundBroadcastBlocks;
}
- (NSData *)emojiState {
auto result = tde2e_api::call_get_verification_state(_callId);
if (!result.is_ok()) {
return [NSData data];
}
auto emojiHash = result.value().emoji_hash;
if (!emojiHash.has_value()) {
return [NSData data];
}
if (emojiHash.value().empty()) {
return [NSData data];
}
NSData *outEmojiHash = [[NSData alloc] initWithBytes:emojiHash.value().data() length:emojiHash.value().size()];
return outEmojiHash;
}
- (void)applyBlock:(NSData *)block {
std::string mappedBlock((uint8_t *)block.bytes, ((uint8_t *)block.bytes) + block.length);
#if DEBUG
auto describeResult = tde2e_api::call_describe_block(mappedBlock);
if (describeResult.is_ok()) {
NSLog(@"TdCall.applyBlock block: %@", [[NSString alloc] initWithBytes:describeResult.value().data() length:describeResult.value().size() encoding:NSUTF8StringEncoding]);
} else {
NSLog(@"TdCall.applyBlock block: describe block failed");
}
#endif
auto result = tde2e_api::call_apply_block(_callId, mappedBlock);
if (!result.is_ok()) {
return;
}
}
- (void)applyBroadcastBlock:(NSData *)block {
std::string mappedBlock((uint8_t *)block.bytes, ((uint8_t *)block.bytes) + block.length);
#if DEBUG
auto describeResult = tde2e_api::call_describe_message(mappedBlock);
if (describeResult.is_ok()) {
NSLog(@"TdCall.applyBroadcastBlock block: %@", [[NSString alloc] initWithBytes:describeResult.value().data() length:describeResult.value().size() encoding:NSUTF8StringEncoding]);
} else {
NSLog(@"TdCall.applyBroadcastBlock block: describe block failed");
}
#endif
auto result = tde2e_api::call_receive_inbound_message(_callId, mappedBlock);
if (!result.is_ok()) {
return;
}
}
- (nullable NSData *)encrypt:(NSData *)message {
std::string mappedMessage((uint8_t *)message.bytes, ((uint8_t *)message.bytes) + message.length);
auto result = tde2e_api::call_encrypt(_callId, mappedMessage);
if (!result.is_ok()) {
return nil;
}
return [[NSData alloc] initWithBytes:result.value().data() length:result.value().size()];
}
- (nullable NSData *)decrypt:(NSData *)message {
std::string mappedMessage((uint8_t *)message.bytes, ((uint8_t *)message.bytes) + message.length);
auto result = tde2e_api::call_decrypt(_callId, mappedMessage);
if (!result.is_ok()) {
return nil;
}
return [[NSData alloc] initWithBytes:result.value().data() length:result.value().size()];
}
@end
NSData * _Nullable tdGenerateZeroBlock(TdKeyPair *keyPair, int64_t userId) {
if (!keyPair) {
return nil;
}
std::string mappedPublicKey((uint8_t *)keyPair.publicKey.bytes, ((uint8_t *)keyPair.publicKey.bytes) + keyPair.publicKey.length);
auto publicKeyId = tde2e_api::key_from_public_key(mappedPublicKey);
if (!publicKeyId.is_ok()) {
return nil;
}
tde2e_api::CallParticipant initialParticipant;
initialParticipant.user_id = userId;
initialParticipant.public_key_id = publicKeyId.value();
initialParticipant.permissions = (1 << 0) | (1 << 1);
tde2e_api::CallState initialCallState;
initialCallState.participants.push_back(std::move(initialParticipant));
auto zeroBlock = tde2e_api::call_create_zero_block(keyPair.keyId, initialCallState);
if (!zeroBlock.is_ok()) {
return nil;
}
NSData *zeroBlockData = [[NSData alloc] initWithBytes:zeroBlock.value().data() length:zeroBlock.value().size()];
#if DEBUG
NSLog(@"Zero block: %@", hexStringFromData(zeroBlockData));
#endif
return zeroBlockData;
}
NSData * _Nullable tdGenerateSelfAddBlock(TdKeyPair *keyPair, int64_t userId, NSData *previousBlock) {
if (!keyPair) {
return nil;
}
std::string mappedPublicKey((uint8_t *)keyPair.publicKey.bytes, ((uint8_t *)keyPair.publicKey.bytes) + keyPair.publicKey.length);
std::string mappedPreviousBlock((uint8_t *)previousBlock.bytes, ((uint8_t *)previousBlock.bytes) + previousBlock.length);
auto publicKeyId = tde2e_api::key_from_public_key(mappedPublicKey);
if (!publicKeyId.is_ok()) {
return nil;
}
tde2e_api::CallParticipant myParticipant;
myParticipant.user_id = userId;
myParticipant.public_key_id = publicKeyId.value();
myParticipant.permissions = (1 << 0) | (1 << 1);
auto result = tde2e_api::call_create_self_add_block(keyPair.keyId, mappedPreviousBlock, myParticipant);
if (!result.is_ok()) {
return nil;
}
NSData *resultBlock = [[NSData alloc] initWithBytes:result.value().data() length:result.value().size()];
#if DEBUG
NSLog(@"Self add block: %@", hexStringFromData(resultBlock));
#endif
return resultBlock;
}

52
third-party/td/build-td-bazel.sh vendored Executable file
View File

@ -0,0 +1,52 @@
#!/bin/sh
set -e
set -x
ARCH="$1"
SOURCE_DIR="$2"
BUILD_DIR=$(echo "$(cd "$(dirname "$3")"; pwd -P)/$(basename "$3")")
OPENSSL_DIR="$4"
openssl_crypto_library="${OPENSSL_DIR}/lib/libcrypto.a"
options=""
options="$options -DOPENSSL_FOUND=1"
options="$options -DOPENSSL_CRYPTO_LIBRARY=${openssl_crypto_library}"
options="$options -DOPENSSL_INCLUDE_DIR=${OPENSSL_DIR}/src/include"
#options="$options -DOPENSSL_LIBRARIES=${openssl_crypto_library}"
options="$options -DCMAKE_BUILD_TYPE=Release"
cd "$BUILD_DIR"
# Generate source files
mkdir native-build
cd native-build
cmake -DTD_GENERATE_SOURCE_FILES=ON ../td
cmake --build . -- -j$(sysctl -n hw.ncpu)
cd ..
if [ "$ARCH" = "arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneOS.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneOS*.sdk)
export CFLAGS="-arch arm64 -miphoneos-version-min=12.0"
elif [ "$ARCH" = "sim_arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
export CFLAGS="-arch arm64 --target=arm64-apple-ios12.0-simulator -miphonesimulator-version-min=12.0"
else
echo "Unsupported architecture $ARCH"
exit 1
fi
# Common build steps
mkdir build
cd build
touch toolchain.cmake
echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake
echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake
echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} ../td $options
make tde2e -j$(sysctl -n hw.ncpu)

194
third-party/td/td/.clang-format vendored Normal file
View File

@ -0,0 +1,194 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: None # All
AllowShortIfStatementsOnASingleLine: Never # WithoutElse
AllowShortLambdasOnASingleLine: Inline # All
AllowShortLoopsOnASingleLine: false # true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
# AttributeMacros:
# - __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterAttributes: Never
# BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: Always
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma # BeforeColon
BreakInheritanceList: BeforeComma # BeforeColon
BreakStringLiterals: true
ColumnLimit: 120 # 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- Q_FOREACH_THIS_LIST_MUST_BE_NON_EMPTY
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 0
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
# InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
Decimal: 0
Hex: 0
# JavaScriptQuotes: Leave
# JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
# ObjCBinPackProtocolList: Never
# ObjCBlockIndentWidth: 2
# ObjCBreakBeforeNestedBlockParam: true
# ObjCSpaceAfterProperty: false
# ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: NextLine
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right # Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false # true
RemoveBracesLLVM: false
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 0 # 1
SortIncludes: CaseInsensitive # CaseSensitive
# SortJavaStaticImport: Before
SortUsingDeclarations: Lexicographic # LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: 1 # -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 100 # 8
UseTab: Never
...

39
third-party/td/td/.gitattributes vendored Normal file
View File

@ -0,0 +1,39 @@
* text=auto
*.cpp text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.hpp text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.h text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.c text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.tl text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.mm text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.txt text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.sh text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent eol=lf
*.php text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.ps1 text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent eol=crlf
*.cmake text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.md text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.in text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.html text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.java text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.py text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.js text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.patch text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.swift text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.pbxproj text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.cs text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.xaml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.appxmanifest text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.vsixmanifest text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.nuspec text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.targets text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.json text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.csproj text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.sln text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.xml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.config text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
sqlite/sqlite/* linguist-vendored
*.pfx binary
*.png binary

8
third-party/td/td/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
**/*build*/
**/.*.swp
**/.DS_Store
**/auto/
docs/
/tdlib/
vcpkg/
.clang-tidy

1520
third-party/td/td/CHANGELOG.md vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
# - Adds a compiler flag if it is supported by the compiler
#
# This function checks that the supplied compiler flag is supported and then
# adds it to the corresponding compiler flags
#
# add_cxx_compiler_flag(<FLAG> [<VARIANT>])
#
# - Example
#
# include(AddCXXCompilerFlag)
# add_cxx_compiler_flag(-Wall)
# add_cxx_compiler_flag(-no-strict-aliasing RELEASE)
# Requires CMake 2.6+
if (__add_cxx_compiler_flag)
return()
endif()
set(__add_cxx_compiler_flag INCLUDED)
include(CheckCXXCompilerFlag)
function(mangle_compiler_flag FLAG OUTPUT)
string(TOUPPER "HAVE_CXX_FLAG_${FLAG}" SANITIZED_FLAG)
string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG})
string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
set(${OUTPUT} "${SANITIZED_FLAG}" PARENT_SCOPE)
endfunction(mangle_compiler_flag)
function(add_cxx_compiler_flag FLAG)
string(REPLACE "-Wno-" "-W" MAIN_FLAG ${FLAG})
mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG_NAME)
if (DEFINED CMAKE_REQUIRED_FLAGS)
set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}")
else()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
endif()
check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG_NAME})
if (DEFINED OLD_CMAKE_REQUIRED_FLAGS)
set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
else()
unset(CMAKE_REQUIRED_FLAGS)
endif()
if (${MANGLED_FLAG_NAME})
set(VARIANT ${ARGV1})
if (ARGV1)
string(TOUPPER "_${VARIANT}" VARIANT)
endif()
set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
endif()
endfunction()
function(add_required_cxx_compiler_flag FLAG)
string(REPLACE "-Wno-" "-W" MAIN_FLAG ${FLAG})
mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG_NAME)
set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}")
check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG_NAME})
set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
if (${MANGLED_FLAG_NAME})
set(VARIANT ${ARGV1})
if (ARGV1)
string(TOUPPER "_${VARIANT}" VARIANT)
endif()
set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE)
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}" PARENT_SCOPE)
else()
message(FATAL_ERROR "Required flag '${FLAG}' is not supported by the compiler")
endif()
endfunction()

View File

@ -0,0 +1,62 @@
# Original issue:
# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733
#
# For reference:
# * https://gcc.gnu.org/wiki/Atomic/GCCMM
#
# riscv64 specific:
# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html
#
# ATOMICS_FOUND - system has C++ atomics
# ATOMICS_LIBRARIES - libraries needed to use C++ atomics
if (ATOMICS_FOUND)
return()
endif()
include(CheckCXXSourceCompiles)
# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed
# to convert smaller atomics to those larger ones via masking and
# shifting like LLVM, but it's a known bug that it does not. This means
# anything that wants to use atomics on 1-byte or 2-byte types needs
# to link atomic library, but not 4-byte or 8-byte (though it does no harm).
set(ATOMIC_CODE
"
#include <atomic>
#include <cstdint>
std::atomic<std::uint8_t> n8{0}; // riscv64
std::atomic<std::uint64_t> n64{0}; // armel, mipsel, powerpc
int main() {
++n8;
++n64;
}")
set(ATOMICS_LIBS " " "-latomic")
if (CMAKE_SYSTEM_NAME MATCHES "NetBSD")
set(ATOMICS_LIBS "${ATOMICS_LIBS}" /usr/pkg/gcc12/x86_64--netbsd/lib/libatomic.so /usr/pkg/gcc12/i486--netbsdelf/lib/libatomic.so)
endif()
foreach (ATOMICS_LIBRARY ${ATOMICS_LIBS})
unset(ATOMICS_FOUND CACHE)
set(CMAKE_REQUIRED_LIBRARIES "${ATOMICS_LIBRARY}")
check_cxx_source_compiles("${ATOMIC_CODE}" ATOMICS_FOUND)
unset(CMAKE_REQUIRED_LIBRARIES)
if (ATOMICS_FOUND)
if (NOT ATOMICS_LIBRARY STREQUAL " ")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY)
set(ATOMICS_LIBRARIES "${ATOMICS_LIBRARY}" CACHE STRING "Atomic library" FORCE)
else()
set(ATOMICS_LIBRARIES "" CACHE STRING "Atomic operations library" FORCE)
endif()
break()
endif()
endforeach()
if (Atomics_FIND_REQUIRED AND NOT ATOMICS_FOUND)
message(FATAL_ERROR "Atomic operations library isn't found.")
endif()
unset(ATOMICS_LIBRARY)
unset(ATOMICS_LIBS)
unset(ATOMIC_CODE)

View File

@ -0,0 +1,25 @@
if (APPLE)
find_path(READLINE_INCLUDE_DIR readline/readline.h /opt/homebrew/opt/readline/include /usr/local/opt/readline/include /opt/local/include /opt/include /usr/local/include /usr/include NO_DEFAULT_PATH)
endif()
find_path(READLINE_INCLUDE_DIR readline/readline.h)
if (APPLE)
find_library(READLINE_LIBRARY readline /opt/homebrew/opt/readline/lib /usr/local/opt/readline/lib /opt/local/lib /opt/lib /usr/local/lib /usr/lib NO_DEFAULT_PATH)
endif()
find_library(READLINE_LIBRARY readline)
if (READLINE_INCLUDE_DIR AND READLINE_LIBRARY AND NOT GNU_READLINE_FOUND)
set(CMAKE_REQUIRED_INCLUDES "${READLINE_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES "${READLINE_LIBRARY}")
include(CheckCXXSourceCompiles)
unset(GNU_READLINE_FOUND CACHE)
check_cxx_source_compiles("#include <stdio.h>\n#include <readline/readline.h>\nint main() { rl_replace_line(\"\", 0); }" GNU_READLINE_FOUND)
if (NOT GNU_READLINE_FOUND)
unset(READLINE_INCLUDE_DIR CACHE)
unset(READLINE_LIBRARY CACHE)
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Readline DEFAULT_MSG READLINE_INCLUDE_DIR READLINE_LIBRARY)
mark_as_advanced(READLINE_INCLUDE_DIR READLINE_LIBRARY)

View File

@ -0,0 +1,99 @@
function(get_relative_link OUTPUT PATH)
if (PATH MATCHES "^[$]<[$]<CONFIG:DEBUG>:")
set(${OUTPUT} "" PARENT_SCOPE)
return()
endif()
string(REGEX REPLACE "^[$]<[$]<NOT:[$]<CONFIG:DEBUG>>:(.*)>$" "\\1" PATH "${PATH}")
get_filename_component(NAME "${PATH}" NAME_WE)
if (IS_ABSOLUTE ${PATH})
get_filename_component(DIRECTORY_NAME "${PATH}" DIRECTORY)
if (WIN32)
set(${OUTPUT} "-l\"${DIRECTORY_NAME}/${NAME}\"" PARENT_SCOPE)
else()
get_filename_component(FULL_NAME "${PATH}" NAME)
set(${OUTPUT} "-L\"${DIRECTORY_NAME}\" -l:${FULL_NAME}" PARENT_SCOPE)
endif()
return()
endif()
if (NOT WIN32 AND NAME MATCHES "^lib")
string(REGEX REPLACE "^lib" "-l" LINK "${NAME}")
elseif (NAME MATCHES "^-")
set(LINK "${NAME}")
else()
string(CONCAT LINK "-l" "${NAME}")
endif()
set(${OUTPUT} "${LINK}" PARENT_SCOPE)
endfunction()
function(generate_pkgconfig TARGET DESCRIPTION)
# message("Generating pkg-config for ${TARGET}")
get_filename_component(PREFIX "${CMAKE_INSTALL_PREFIX}" REALPATH)
get_target_property(LIST "${TARGET}" LINK_LIBRARIES)
set(REQS "")
set(LIBS "")
foreach (LIB ${LIST})
if (TARGET "${LIB}")
set(HAS_REQS 1)
list(APPEND REQS "${LIB}")
else()
set(HAS_LIBS 1)
get_relative_link(LINK "${LIB}")
if (NOT LINK EQUAL "")
list(APPEND LIBS "${LINK}")
endif()
endif()
endforeach()
if (HAS_REQS)
set(REQUIRES "")
foreach (REQ ${REQS})
set(REQUIRES "${REQUIRES} ${REQ}")
endforeach()
set(REQUIRES "Requires.private:${REQUIRES}\n")
endif()
if (HAS_LIBS)
set(LIBRARIES "")
list(REVERSE LIBS)
list(REMOVE_DUPLICATES LIBS)
foreach (LIB ${LIBS})
set(LIBRARIES " ${LIB}${LIBRARIES}")
endforeach()
set(LIBRARIES "Libs.private:${LIBRARIES}\n")
endif()
if (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
else()
set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif()
if (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
else()
set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}")
endif()
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig")
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${TARGET}.pc" CONTENT
"prefix=${PREFIX}
Name: ${TARGET}
Description: ${DESCRIPTION}
Version: ${PROJECT_VERSION}
CFlags: -I\"${PKGCONFIG_INCLUDEDIR}\"
Libs: -L\"${PKGCONFIG_LIBDIR}\" -l${TARGET}
${REQUIRES}${LIBRARIES}")
get_target_property(LIBRARY_TYPE "${TARGET}" TYPE)
if (LIBRARY_TYPE STREQUAL "STATIC_LIBRARY" OR LIBRARY_TYPE STREQUAL "SHARED_LIBRARY")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${TARGET}.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
elseif (LIBRARY_TYPE STREQUAL "INTERFACE_LIBRARY")
# TODO: support interface libraries
else()
message(FATAL_ERROR "Don't know how to handle ${TARGET} of type ${LIBRARY_TYPE}")
endif()
endfunction()

View File

@ -0,0 +1,127 @@
# - Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can
# trust the values of the variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar>)
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2020 Ryan Pavlik <ryan.pavlik@gmail.com> <abiryan@ryand.net>
# http://academic.cleardefinition.com
#
# Copyright 2009-2013, Iowa State University.
# Copyright 2013-2020, Ryan Pavlik
# Copyright 2013-2020, Contributors
# SPDX-License-Identifier: BSL-1.0
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if (__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# Function _git_find_closest_git_dir finds the next closest .git directory
# that is part of any directory in the path defined by _start_dir.
# The result is returned in the parent scope variable whose name is passed
# as variable _git_dir_var. If no .git directory can be found, the
# function returns an empty string via _git_dir_var.
#
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This will return
# C:/bla/.git
#
function(_git_find_closest_git_dir _start_dir _git_dir_var)
set(cur_dir "${_start_dir}")
set(git_dir "${_start_dir}/.git")
while (NOT EXISTS "${git_dir}")
# .git dir not found, search parent directories
set(git_previous_parent "${cur_dir}")
get_filename_component(cur_dir "${cur_dir}" DIRECTORY)
if (cur_dir STREQUAL git_previous_parent)
# We have reached the root directory, we are not in git
set(${_git_dir_var} "" PARENT_SCOPE)
return()
endif()
set(git_dir "${cur_dir}/.git")
endwhile()
set(${_git_dir_var} "${git_dir}" PARENT_SCOPE)
endfunction()
function(get_git_head_revision _refspecvar _hashvar)
_git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR)
if (NOT GIT_DIR STREQUAL "")
file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_CURRENT_SOURCE_DIR}" "${GIT_DIR}")
if (_relative_to_source_dir MATCHES "^[.][.]")
# We've gone above the CMake root dir.
set(GIT_DIR "")
endif()
endif()
if (GIT_DIR STREQUAL "")
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
return()
endif()
find_package(Git QUIET)
# Check if the current source dir is a git submodule or a worktree.
# In both cases .git is a file instead of a directory.
#
if ((NOT IS_DIRECTORY ${GIT_DIR}) AND Git_FOUND)
# The following git command will return a non empty string that
# points to the super project working tree if the current
# source dir is inside a git submodule.
# Otherwise, the command will return an empty string.
#
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if (NOT out STREQUAL "")
# If out is non-empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule})
string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE)
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
else()
# GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree
file(READ ${GIT_DIR} worktree_ref)
# The .git directory contains a path to the worktree information directory
# inside the parent git repo of the worktree.
string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref})
string(STRIP ${git_worktree_dir} git_worktree_dir)
_git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR)
set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD")
endif()
else()
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
endif()
if (NOT EXISTS "${HEAD_SOURCE_FILE}")
return()
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if (NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY)
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
endfunction()

View File

@ -0,0 +1,43 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright 2009-2012, Iowa State University
# Copyright 2011-2015, Contributors
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
# SPDX-License-Identifier: BSL-1.0
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if (HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if (EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
else()
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
if (PACKED_REFS MATCHES "([0-9a-z]*) ${HEAD_REF}")
set(HEAD_HASH "${CMAKE_MATCH_1}")
endif()
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if (NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

View File

@ -0,0 +1,14 @@
function(prevent_in_source_build)
get_filename_component(REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
get_filename_component(REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH)
if (REAL_BINARY_DIR STREQUAL REAL_SOURCE_DIR)
message(" Out-of-source build must be used. Remove the files already")
message(" created by CMake and rerun CMake from a new directory:")
message(" rm -rf CMakeFiles CMakeCache.txt")
message(" mkdir build")
message(" cd build")
message(" cmake ..")
message(FATAL_ERROR "In-source build failed.")
endif()
endfunction()

View File

@ -0,0 +1,185 @@
# Configures C++17 compiler, setting TDLib-specific compilation options.
function(td_set_up_compiler)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1 PARENT_SCOPE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON PARENT_SCOPE)
include(illumos)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GCC 1)
set(GCC 1 PARENT_SCOPE)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CLANG 1)
set(CLANG 1 PARENT_SCOPE)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
set(INTEL 1)
set(INTEL 1 PARENT_SCOPE)
elseif (NOT MSVC)
message(FATAL_ERROR "Compiler isn't supported")
endif()
include(CheckCXXCompilerFlag)
if (GCC OR CLANG OR INTEL)
if (WIN32 AND INTEL)
set(STD17_FLAG /Qstd=c++17)
else()
set(STD17_FLAG -std=c++17)
endif()
if (GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
message(FATAL_ERROR "No C++17 support in the compiler. Please upgrade the compiler to at least GCC 7.0.")
endif()
if (CLANG AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
message(FATAL_ERROR "No C++17 support in the compiler. Please upgrade the compiler to at least clang 5.0.")
endif()
check_cxx_compiler_flag(${STD17_FLAG} HAVE_STD17)
elseif (MSVC)
set(HAVE_STD17 MSVC_VERSION>=1914) # MSVC 2017 version 15.7
endif()
if (NOT HAVE_STD17)
message(FATAL_ERROR "No C++17 support in the compiler. Please upgrade the compiler.")
endif()
if (MSVC)
if (CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
endif()
add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /utf-8 /GR- /W4 /wd4100 /wd4127 /wd4324 /wd4505 /wd4814 /wd4702 /bigobj")
elseif (CLANG OR GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD17_FLAG} -fno-omit-frame-pointer -fno-exceptions -fno-rtti")
if (APPLE)
set(TD_LINKER_FLAGS "-Wl,-dead_strip")
if (NOT CMAKE_BUILD_TYPE MATCHES "Deb")
set(TD_LINKER_FLAGS "${TD_LINKER_FLAGS},-x,-S")
endif()
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
set(TD_LINKER_FLAGS "-Wl,-z,ignore")
elseif (EMSCRIPTEN)
set(TD_LINKER_FLAGS "-Wl,--gc-sections")
elseif (ANDROID)
set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL -Wl,--icf=safe")
else()
set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL")
endif()
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
if (WIN32 OR CYGWIN)
if (GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj")
endif()
endif()
elseif (INTEL)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD17_FLAG}")
endif()
if (WIN32)
add_definitions(-DNTDDI_VERSION=0x06020000 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -DPSAPI_VERSION=1 -DNOMINMAX -DUNICODE -D_UNICODE -DWIN32_LEAN_AND_MEAN)
endif()
if (CYGWIN)
add_definitions(-D_DEFAULT_SOURCE=1 -DFD_SETSIZE=4096)
endif()
# _FILE_OFFSET_BITS is broken in Android NDK r15, r15b and r17 and doesn't work prior to Android 7.0
add_definitions(-D_FILE_OFFSET_BITS=64)
# _GNU_SOURCE might not be defined by g++
add_definitions(-D_GNU_SOURCE)
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lsocket -lnsl")
if (ILLUMOS)
add_definitions(-DTD_ILLUMOS=1)
endif()
endif()
include(AddCXXCompilerFlag)
if (NOT MSVC)
add_cxx_compiler_flag("-Wall")
add_cxx_compiler_flag("-Wextra")
add_cxx_compiler_flag("-Wimplicit-fallthrough=2")
add_cxx_compiler_flag("-Wpointer-arith")
add_cxx_compiler_flag("-Wcast-qual")
add_cxx_compiler_flag("-Wsign-compare")
add_cxx_compiler_flag("-Wduplicated-branches")
add_cxx_compiler_flag("-Wduplicated-cond")
add_cxx_compiler_flag("-Walloc-zero")
add_cxx_compiler_flag("-Wlogical-op")
add_cxx_compiler_flag("-Wno-tautological-compare")
add_cxx_compiler_flag("-Wpointer-arith")
add_cxx_compiler_flag("-Wvla")
add_cxx_compiler_flag("-Wnon-virtual-dtor")
add_cxx_compiler_flag("-Wno-unused-parameter")
add_cxx_compiler_flag("-Wconversion")
add_cxx_compiler_flag("-Wno-sign-conversion")
add_cxx_compiler_flag("-Wc++17-compat-pedantic")
add_cxx_compiler_flag("-Wdeprecated")
add_cxx_compiler_flag("-Wno-unused-command-line-argument")
add_cxx_compiler_flag("-Qunused-arguments")
add_cxx_compiler_flag("-Wno-unknown-warning-option")
add_cxx_compiler_flag("-Wodr")
add_cxx_compiler_flag("-flto-odr-type-merging")
add_cxx_compiler_flag("-Wno-psabi")
add_cxx_compiler_flag("-Wunused-member-function")
add_cxx_compiler_flag("-Wunused-private-field")
# add_cxx_compiler_flag("-Werror")
# add_cxx_compiler_flag("-Wcast-align")
#std::int32_t <-> int and off_t <-> std::size_t/std::int64_t
# add_cxx_compiler_flag("-Wuseless-cast")
#external headers like openssl
# add_cxx_compiler_flag("-Wzero-as-null-pointer-constant")
endif()
if (GCC)
add_cxx_compiler_flag("-Wno-maybe-uninitialized") # too many false positives
endif()
if (WIN32 AND GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0))
# warns about casts of function pointers returned by GetProcAddress
add_cxx_compiler_flag("-Wno-cast-function-type")
endif()
if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0))
# warns about a lot of "return std::move", which are not redundant for compilers without fix for DR 1579, i.e. GCC 4.9 or clang 3.8
# see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1579
add_cxx_compiler_flag("-Wno-redundant-move")
endif()
if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0))
add_cxx_compiler_flag("-Wno-stringop-overflow") # some false positives
endif()
if (CLANG AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5))
# https://stackoverflow.com/questions/26744556/warning-returning-a-captured-reference-from-a-lambda
add_cxx_compiler_flag("-Wno-return-stack-address")
endif()
if (GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.0))
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104030
add_cxx_compiler_flag("-Wbidi-chars=none")
endif()
if (MINGW)
add_cxx_compiler_flag("-ftrack-macro-expansion=0")
endif()
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem /usr/include/c++/v1")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=leak")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}" PARENT_SCOPE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}" PARENT_SCOPE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" PARENT_SCOPE)
endfunction()

278
third-party/td/td/CMake/iOS.cmake vendored Normal file
View File

@ -0,0 +1,278 @@
# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake
# files which are included with CMake 2.8.4
# It has been altered for iOS development
# Options:
#
# IOS_PLATFORM = OS (default) or SIMULATOR
# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders
# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch.
# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch.
#
# IOS_ARCH = automatic(default) or "arch1;arch2" (e.q. "x86_64;arm64")
# By default this value will be automatically chosen based on the IOS_PLATFORM value above.
# If set manually, it will override the default and force to build those architectures only.
#
# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder
# By default this location is automatically chosen based on the IOS_PLATFORM value above.
# If set manually, it will override the default location and force the user of a particular Developer Platform
#
# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder
# By default this location is automatically chosen based on the CMAKE_IOS_DEVELOPER_ROOT value.
# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path.
# If set manually, this will force the use of a specific SDK version
# Macros:
#
# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE)
# A convenience macro for setting xcode specific properties on targets
# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1")
#
# find_host_package (PROGRAM ARGS)
# A macro used to find executable programs on the host system, not within the iOS environment.
# Thanks to the android-cmake project for providing the command
# Standard settings
set (CMAKE_SYSTEM_NAME Darwin)
set (CMAKE_SYSTEM_VERSION 1)
set (UNIX True)
set (APPLE True)
set (IOS True)
# Required as of cmake 2.8.10
set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE)
# Determine the cmake host system version so we know where to find the iOS SDKs
find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin)
if (CMAKE_UNAME)
execute_process(COMMAND uname -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}")
endif (CMAKE_UNAME)
# Force the compilers to gcc for iOS
set (CMAKE_C_COMPILER /usr/bin/gcc)
set (CMAKE_CXX_COMPILER /usr/bin/g++)
set (CMAKE_AR ar CACHE FILEPATH "" FORCE)
set (CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE)
set (PKG_CONFIG_EXECUTABLE pkg-config CACHE FILEPATH "" FORCE)
# Setup iOS platform unless specified manually with IOS_PLATFORM
if (NOT DEFINED IOS_PLATFORM)
set (IOS_PLATFORM "OS")
endif (NOT DEFINED IOS_PLATFORM)
set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform")
# Check the platform selection and setup for developer root
if (IOS_PLATFORM STREQUAL "OS")
set (IOS_PLATFORM_LOCATION "iPhoneOS.platform")
set (XCODE_IOS_PLATFORM ios)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos")
set (APPLE_IOS True)
elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
set (XCODE_IOS_PLATFORM ios-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
set (APPLE_IOS True)
elseif (IOS_PLATFORM STREQUAL "WATCHOS")
set (IOS_PLATFORM_LOCATION "WatchOS.platform")
set (XCODE_IOS_PLATFORM watchos)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-watchos")
set (APPLE_WATCH True)
elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "WatchSimulator.platform")
set (XCODE_IOS_PLATFORM watchos-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-watchsimulator")
set (APPLE_WATCH True)
elseif (IOS_PLATFORM STREQUAL "TVOS")
set (IOS_PLATFORM_LOCATION "AppleTvOS.platform")
set (XCODE_IOS_PLATFORM tvos)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvos")
set (APPLE_TV True)
elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "AppleTvSimulator.platform")
set (XCODE_IOS_PLATFORM tvos-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-tvsimulator")
set (APPLE_TV True)
elseif (IOS_PLATFORM STREQUAL "VISIONOS")
set (IOS_PLATFORM_LOCATION "XROS.platform")
set (XCODE_IOS_PLATFORM xros)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xros")
set (APPLE_VISION True)
elseif (IOS_PLATFORM STREQUAL "VISIONSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "XRSimulator.platform")
set (XCODE_IOS_PLATFORM xros-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xrsimulator")
set (APPLE_VISION True)
else (IOS_PLATFORM STREQUAL "OS")
message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS, SIMULATOR, or WATCHOS.")
endif ()
# All iOS/Darwin specific settings - some may be redundant
set (CMAKE_SHARED_LIBRARY_PREFIX "lib")
set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib")
set (CMAKE_SHARED_MODULE_PREFIX "lib")
set (CMAKE_SHARED_MODULE_SUFFIX ".so")
set (CMAKE_MODULE_EXISTS 1)
set (CMAKE_DL_LIBS "")
set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ")
set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ")
set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}")
set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}")
if (IOS_DEPLOYMENT_TARGET)
set (XCODE_IOS_PLATFORM_VERSION_FLAGS "-m${XCODE_IOS_PLATFORM}-version-min=${IOS_DEPLOYMENT_TARGET}")
endif()
set (CMAKE_SHARED_LINKER_FLAGS_INIT "-fapplication-extension")
set (CMAKE_C_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS}")
# Hidden visibility is required for cxx on iOS
set (CMAKE_CXX_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fvisibility-inlines-hidden")
set (CMAKE_C_LINK_FLAGS "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fapplication-extension -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}")
set (CMAKE_CXX_LINK_FLAGS "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fapplication-extension -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}")
set (CMAKE_PLATFORM_HAS_INSTALLNAME 1)
set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names")
set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names")
set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,")
set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,")
set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a")
# hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree
# (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache
# and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun)
# hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex
if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL)
find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool)
endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL)
# Setup iOS deployment target
set (IOS_DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET} CACHE STRING "Minimum iOS version")
# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT
# Note Xcode 4.3 changed the installation location, choose the most recent one available
execute_process(COMMAND /usr/bin/xcode-select -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
set (XCODE_POST_43_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
if (EXISTS ${XCODE_POST_43_ROOT})
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT})
elseif (EXISTS ${XCODE_PRE_43_ROOT})
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT})
endif (EXISTS ${XCODE_POST_43_ROOT})
endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform")
# Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT
if (NOT DEFINED CMAKE_IOS_SDK_ROOT)
file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*")
if (_CMAKE_IOS_SDKS)
list (SORT _CMAKE_IOS_SDKS)
list (REVERSE _CMAKE_IOS_SDKS)
list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT)
else (_CMAKE_IOS_SDKS)
message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.")
endif (_CMAKE_IOS_SDKS)
message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}")
endif (NOT DEFINED CMAKE_IOS_SDK_ROOT)
set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK")
# Set the sysroot default to the most recent SDK
set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support")
# Set the architectures unless specified manually with IOS_ARCH
if (NOT DEFINED IOS_ARCH)
if (IOS_PLATFORM STREQUAL "OS")
set (IOS_ARCH "arm64")
elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
set (IOS_ARCH "x86_64;arm64")
elseif (IOS_PLATFORM STREQUAL "WATCHOS")
set (IOS_ARCH "armv7k;arm64_32;arm64")
# Include C++ Standard Library for Xcode 15 builds.
include_directories(SYSTEM "${CMAKE_IOS_SDK_ROOT}/usr/include/c++/v1")
elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
set (IOS_ARCH "x86_64;arm64")
# Include C++ Standard Library for Xcode 15 builds.
include_directories(SYSTEM "${CMAKE_IOS_SDK_ROOT}/usr/include/c++/v1")
elseif (IOS_PLATFORM STREQUAL "TVOS")
set (IOS_ARCH "arm64")
elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
set (IOS_ARCH "x86_64;arm64")
elseif (IOS_PLATFORM STREQUAL "VISIONOS")
set (IOS_ARCH "arm64")
elseif (IOS_PLATFORM STREQUAL "VISIONSIMULATOR")
set (IOS_ARCH "x86_64;arm64")
endif()
endif()
message (STATUS "The iOS architectures: ${IOS_ARCH}")
set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS")
# Set the find root to the iOS developer roots and to user defined paths
set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE STRING "iOS find search path root")
# default to searching for frameworks first
set (CMAKE_FIND_FRAMEWORK FIRST)
# set up the default search directories for frameworks
set (CMAKE_SYSTEM_FRAMEWORK_PATH
${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks
${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks
${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks
)
# only search the iOS sdks, not the remainder of the host filesystem
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# This little macro lets you set any Xcode specific property
macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE)
set_property (TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE})
endmacro (set_xcode_property)
# This macro lets you find executable programs on the host system
macro (find_host_package)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
set (IOS FALSE)
find_package(${ARGN})
set (IOS TRUE)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endmacro (find_host_package)

10
third-party/td/td/CMake/illumos.cmake vendored Normal file
View File

@ -0,0 +1,10 @@
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
#
# Determine if the host is running an illumos distribution:
#
execute_process(COMMAND /usr/bin/uname -o OUTPUT_VARIABLE UNAME_O OUTPUT_STRIP_TRAILING_WHITESPACE)
if (UNAME_O STREQUAL "illumos")
set(ILLUMOS 1)
endif()
endif()

1440
third-party/td/td/CMakeLists.txt vendored Normal file

File diff suppressed because it is too large Load Diff

2473
third-party/td/td/Doxyfile vendored Normal file

File diff suppressed because it is too large Load Diff

23
third-party/td/td/LICENSE_1_0.txt vendored Normal file
View File

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

145
third-party/td/td/README.md vendored Normal file
View File

@ -0,0 +1,145 @@
# TDLib
TDLib (Telegram Database library) is a cross-platform library for building [Telegram](https://telegram.org) clients. It can be easily used from almost any programming language.
## Table of Contents
- [Features](#features)
- [Examples and documentation](#usage)
- [Dependencies](#dependencies)
- [Building](#building)
- [Using in CMake C++ projects](#using-cxx)
- [Using in Java projects](#using-java)
- [Using in .NET projects](#using-dotnet)
- [Using with other programming languages](#using-json)
- [License](#license)
<a name="features"></a>
## Features
`TDLib` has many advantages. Notably `TDLib` is:
* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, illumos, Windows Phone, WebAssembly, watchOS, tvOS, visionOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort.
* **Multilanguage**: `TDLib` can be easily used with any programming language that is able to execute C functions. Additionally, it already has native Java (using `JNI`) bindings and .NET (using `C++/CLI` and `C++/CX`) bindings.
* **Easy to use**: `TDLib` takes care of all network implementation details, encryption and local data storage.
* **High-performance**: in the [Telegram Bot API](https://core.telegram.org/bots/api), each `TDLib` instance handles more than 25000 active bots simultaneously.
* **Well-documented**: all `TDLib` API methods and public interfaces are fully documented.
* **Consistent**: `TDLib` guarantees that all updates are delivered in the right order.
* **Reliable**: `TDLib` remains stable on slow and unreliable Internet connections.
* **Secure**: all local data is encrypted using a user-provided encryption key.
* **Fully-asynchronous**: requests to `TDLib` don't block each other or anything else, responses are sent when they are available.
<a name="usage"></a>
## Examples and documentation
See our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
Take a look at our [examples](https://github.com/tdlib/td/blob/master/example/README.md#tdlib-usage-and-build-examples).
See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
See description of our [JSON](#using-json), [C++](#using-cxx), [Java](#using-java) and [.NET](#using-dotnet) interfaces.
See the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html)
for a list of all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
<a name="dependencies"></a>
## Dependencies
`TDLib` depends on:
* C++17 compatible compiler (Clang 5.0+, GCC 7.0+, MSVC 19.1+ (Visual Studio 2017.7+), Intel C++ Compiler 19+)
* OpenSSL
* zlib
* gperf (build only)
* CMake (3.10+, build only)
* PHP (optional, for documentation generation)
<a name="building"></a>
## Building
The simplest way to build `TDLib` is to use our [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
You need only to choose your programming language and target operating system to receive complete build instructions.
In general, you need to install all `TDLib` [dependencies](#dependencies), enter directory containing `TDLib` sources and compile them using CMake:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
```
To build `TDLib` on low memory devices you can run [SplitSource.php](https://github.com/tdlib/td/blob/master/SplitSource.php) script
before compiling main `TDLib` source code and compile only needed targets:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --target prepare_cross_compiling
cd ..
php SplitSource.php
cd build
cmake --build . --target tdjson
cmake --build . --target tdjson_static
cd ..
php SplitSource.php --undo
```
In our tests clang 6.0 with libc++ required less than 500 MB of RAM per file and GCC 4.9/6.3 used less than 1 GB of RAM per file.
<a name="using-cxx"></a>
## Using in CMake C++ projects
For C++ projects that use CMake, the best approach is to build `TDLib` as part of your project or to install it system-wide.
There are several libraries that you could use in your CMake project:
* Td::TdJson, Td::TdJsonStatic — dynamic and static version of a JSON interface. This has a simple C interface, so it can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for more information.
* Td::TdStatic — static library with C++ interface for general usage.
See [ClientManager](https://core.telegram.org/tdlib/docs/classtd_1_1_client_manager.html) and [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) documentation for more information.
For example, part of your CMakeLists.txt may look like this:
```
add_subdirectory(td)
target_link_libraries(YourTarget PRIVATE Td::TdStatic)
```
Or you could install `TDLib` and then reference it in your CMakeLists.txt like this:
```
find_package(Td 1.8.46 REQUIRED)
target_link_libraries(YourTarget PRIVATE Td::TdStatic)
```
See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/blob/master/example/cpp/CMakeLists.txt).
<a name="using-java"></a>
## Using in Java projects
`TDLib` provides native Java interface through JNI. To enable it, specify option `-DTD_ENABLE_JNI=ON` to CMake.
See [example/java](https://github.com/tdlib/td/tree/master/example/java) for example of using `TDLib` from Java and detailed build and usage instructions.
<a name="using-dotnet"></a>
## Using in .NET projects
`TDLib` provides native .NET interface through `C++/CLI` and `C++/CX`. To enable it, specify option `-DTD_ENABLE_DOTNET=ON` to CMake.
.NET Core supports `C++/CLI` only since version 3.1 and only on Windows, so if older .NET Core is used or portability is needed, then `TDLib` JSON interface should be used through P/Invoke instead.
See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for example of using `TDLib` from C# and detailed build and usage instructions.
See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for example of using `TDLib` from C# UWP application and detailed build and usage instructions for Visual Studio Extension "TDLib for Universal Windows Platform".
When `TDLib` is built with `TD_ENABLE_DOTNET` option enabled, `C++` documentation is removed from some files. You need to checkout these files to return `C++` documentation back:
```
git checkout td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
```
<a name="using-json"></a>
## Using from other programming languages
`TDLib` provides efficient native C++, Java, and .NET interfaces.
But for most use cases we suggest to use the JSON interface, which can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for detailed JSON interface description,
the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of
all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
`TDLib` JSON interface adheres to semantic versioning and versions with the same major version number are binary and backward compatible, but the underlying `TDLib` API can be different for different minor and even patch versions.
If you need to support different `TDLib` versions, then you can use a value of the `version` option to find exact `TDLib` version to use appropriate API methods.
See [example/python/tdjson_example.py](https://github.com/tdlib/td/blob/master/example/python/tdjson_example.py) for an example of such usage.
<a name="license"></a>
## License
`TDLib` is licensed under the terms of the Boost Software License. See [LICENSE_1_0.txt](http://www.boost.org/LICENSE_1_0.txt) for more information.

508
third-party/td/td/SplitSource.php vendored Normal file
View File

@ -0,0 +1,508 @@
<?php
function disjoint_set_find(&$parents, $x) {
if ($parents[$x] !== $x) {
return $parents[$x] = disjoint_set_find($parents, $parents[$x]);
}
return $x;
}
function disjoint_set_union(&$parents, $x, $y) {
$x = disjoint_set_find($parents, $x);
$y = disjoint_set_find($parents, $y);
if ($x !== $y) {
if (rand(0, 1) == 0) {
$parents[$x] = $y;
} else {
$parents[$y] = $x;
}
}
}
function split_file($file, $chunks, $undo) {
$cpp_name = "$file.cpp";
echo "Processing file $cpp_name".PHP_EOL;
$new_files = array();
foreach (range(0, $chunks - 1) as $n) {
$new_files[] = "$file$n.cpp";
}
$is_generated = (strpos($file, 'td/generate/') === 0);
$cmake_file = $is_generated ? 'td/generate/CMakeLists.txt' : 'CMakeLists.txt';
$cmake = file_get_contents($cmake_file);
$cmake_cpp_name = $cpp_name;
$cmake_new_files = $new_files;
if ($is_generated) {
foreach ($cmake_new_files as &$file_ref) {
$file_ref = str_replace('td/generate/auto/td', '${TD_AUTO_INCLUDE_DIR}', $file_ref);
}
$cmake_cpp_name = str_replace('td/generate/auto/td', '${TD_AUTO_INCLUDE_DIR}', $cmake_cpp_name);
}
if ($undo) {
foreach ($new_files as $file) {
if (file_exists($file)) {
echo "Unlinking ".$file.PHP_EOL;
unlink($file);
}
}
if (strpos($cmake, $cmake_cpp_name) === false) {
$cmake = str_replace(implode(PHP_EOL.' ', $cmake_new_files), $cmake_cpp_name, $cmake);
file_put_contents($cmake_file, $cmake);
}
return;
}
if (strpos($cmake, $cmake_cpp_name) !== false) {
$cmake = str_replace($cmake_cpp_name, implode(PHP_EOL.' ', $cmake_new_files), $cmake);
file_put_contents($cmake_file, $cmake);
}
if (!file_exists($cpp_name)) {
echo "ERROR: skip nonexistent file $cpp_name".PHP_EOL;
return;
}
$lines = file($cpp_name);
$depth = 0;
$target_depth = 1 + $is_generated;
$is_static = false;
$in_define = false;
$in_comment = false;
$current = '';
$common = '';
$functions = array();
$namespace_begin = '';
$namespace_end = '';
foreach ($lines as $line) {
$add_depth = strpos($line, 'namespace ') === 0 ? 1 : (strpos($line, '} // namespace') === 0 ? -1 : 0);
if ($add_depth) {
# namespace begin/end
if ($add_depth > 0) {
$depth += $add_depth;
}
if ($depth <= $target_depth) {
if ($add_depth > 0) {
$namespace_begin .= $line;
} else {
$namespace_end .= $line;
}
}
if ($add_depth < 0) {
$depth += $add_depth;
}
if ($is_static) {
$common .= $current;
} else {
$functions[] = $current;
}
$common .= $line;
$current = '';
$is_static = false;
$in_define = false;
continue;
}
if (strpos($line, '#undef') === 0 && !trim($current)) {
continue;
}
if ($in_comment && strpos($line, '*/') === 0) {
$in_comment = false;
continue;
}
if (strpos($line, '/*') === 0) {
$in_comment = true;
}
if ($in_comment) {
continue;
}
if ($depth !== $target_depth) {
$common .= $line;
continue;
}
if (strpos($line, 'static ') === 0 && $depth === $target_depth) {
$is_static = true;
}
if (!trim($current) && strpos($line, '#define ') === 0) {
$is_static = true;
$in_define = true;
}
$current .= $line;
if ((strpos($line, '}') === 0 || ($in_define && !trim($line)) || preg_match('/^[a-z].*;\s*$/i', $line)) && $depth === $target_depth) {
# block end
if ($is_static) {
$common .= $current;
} else {
$functions[] = $current;
}
$current = '';
$is_static = false;
$in_define = false;
}
}
$current = trim($current);
if (!empty($current)) {
fwrite(STDERR, "ERROR: $current".PHP_EOL);
exit();
}
if (count($functions) < $chunks) {
fwrite(STDERR, "ERROR: file is too small to be split more".PHP_EOL);
return;
}
$deps = array(); // all functions from the same subarray must be in the same file
$parents = array();
foreach ($functions as $i => $f) {
if (preg_match_all('/(?J)create_handler<(?<name>[A-Z][A-Za-z]*)>|'.
'(?<name>[A-Z][A-Za-z]*) (final )?: public (Td::ResultHandler|Request)|'.
'(CREATE_REQUEST|CREATE_NO_ARGS_REQUEST)[(](?<name>[A-Z][A-Za-z]*)|'.
'(?<name>complete_pending_preauthentication_requests)|'.
'(?<name>get_message_history_slice)|'.
'(Up|Down)load(?!ManagerCallback)[a-zA-Z]+C(?<name>allback)|(up|down)load_[a-z_]*_c(?<name>allback)_|'.
'(?<name>lazy_to_json)|'.
'(?<name>LogEvent)[^sA]|'.
'(?<name>parse)[(]|'.
'(?<name>store)[(]/', $f, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$name = $match['name'];
if ($name === 'parse' || $name === 'store') {
if ($is_generated) {
continue;
}
$name = 'LogEvent';
}
$deps[$name][] = $i;
}
}
$parents[$i] = $i;
}
foreach ($deps as $func_ids) {
foreach ($func_ids as $func_id) {
disjoint_set_union($parents, $func_ids[0], $func_id);
}
}
$sets = array();
$set_sizes = array();
foreach ($functions as $i => $f) {
$parent = disjoint_set_find($parents, $i);
if (!isset($sets[$parent])) {
$sets[$parent] = '';
$set_sizes[$parent] = 0;
}
$sets[$parent] .= $f;
$set_sizes[$parent] += strlen($f);
}
arsort($set_sizes);
$files = array_fill(0, $chunks, '');
$file_sizes = array_fill(0, $chunks, 0);
foreach ($set_sizes as $parent => $size) {
$file_id = array_search(min($file_sizes), $file_sizes);
$files[$file_id] .= $sets[$parent];
$file_sizes[$file_id] += $size;
}
foreach ($files as $n => $f) {
$new_content = $common.$namespace_begin.$f.$namespace_end;
$std_methods = array();
preg_match_all('/std::[a-z_0-9]*|td::unique(?!_)/', $new_content, $std_methods);
$std_methods = array_unique($std_methods[0]);
$needed_std_headers = array();
$type_headers = array(
'std::move' => '',
'std::vector' => '',
'std::string' => '',
'std::uint32_t' => '',
'std::int32_t' => '',
'std::int64_t' => '',
'td::unique' => 'algorithm',
'std::count_if' => 'algorithm',
'std::fill' => 'algorithm',
'std::find' => 'algorithm',
'std::is_sorted' => 'algorithm',
'std::lower_bound' => 'algorithm',
'std::max' => 'algorithm',
'std::merge' => 'algorithm',
'std::min' => 'algorithm',
'std::partial_sort' => 'algorithm',
'std::partition' => 'algorithm',
'std::remove' => 'algorithm',
'std::reverse' => 'algorithm',
'std::rotate' => 'algorithm',
'std::sort' => 'algorithm',
'std::stable_sort' => 'algorithm',
'std::upper_bound' => 'algorithm',
'std::abs' => 'cmath',
'std::isfinite' => 'cmath',
'std::function' => 'functional',
'std::greater' => 'functional',
'std::reference_wrapper' => 'functional',
'std::make_move_iterator' => 'iterator',
'std::numeric_limits' => 'limits',
'std::map' => 'map',
'std::multimap' => 'map',
'std::make_shared' => 'memory',
'std::shared_ptr' => 'memory',
'std::multiset' => 'set',
'std::set' => 'set',
'std::get' => 'tuple',
'std::make_tuple' => 'tuple',
'std::tie' => 'tuple',
'std::tuple' => 'tuple',
'std::decay_t' => 'type_traits',
'std::is_same' => 'type_traits',
'std::unordered_map' => 'unordered_map',
'std::unordered_set' => 'unordered_set',
'std::make_pair' => 'utility',
'std::pair' => 'utility',
'std::swap' => 'utility');
foreach ($type_headers as $type => $header) {
if (in_array($type, $std_methods)) {
$std_methods = array_diff($std_methods, array($type));
if ($header && !in_array($header, $needed_std_headers)) {
$needed_std_headers[] = $header;
}
}
}
if (!$std_methods) { // know all needed std headers
$new_content = preg_replace_callback(
'/#include <([a-z_]*)>/',
function ($matches) use ($needed_std_headers) {
if (in_array($matches[1], $needed_std_headers)) {
return $matches[0];
}
return '';
},
$new_content
);
}
$td_methods = array(
'AccentColorId' => 'AccentColorId',
'account_manager[_(-](?![.]get[(][)])|AccountManager[^;>]' => 'AccountManager',
'AffiliateType' => 'AffiliateType',
'alarm_manager[_(-](?![.]get[(][)])|AlarmManager' => 'AlarmManager',
'animations_manager[_(-](?![.]get[(][)])|AnimationsManager[^;>]' => 'AnimationsManager',
'attach_menu_manager[_(-](?![.]get[(][)])|AttachMenuManager[^;>]' => 'AttachMenuManager',
'audios_manager[_(-](?![.]get[(][)])|AudiosManager' => 'AudiosManager',
'auth_manager[_(-](?![.]get[(][)])|AuthManager' => 'AuthManager',
'AutoDownloadSettings|[a-z_]*auto_download_settings' => 'AutoDownloadSettings',
'autosave_manager[_(-](?![.]get[(][)])|AutosaveManager' => 'AutosaveManager',
'BackgroundId' => 'BackgroundId',
'background_manager[_(-](?![.]get[(][)])|BackgroundManager' => 'BackgroundManager',
'BackgroundType' => 'BackgroundType',
'Birthdate' => 'Birthdate',
'boost_manager[_(-](?![.]get[(][)])|BoostManager' => 'BoostManager',
'bot_info_manager[_(-](?![.]get[(][)])|BotInfoManager' => 'BotInfoManager',
'BotMenuButton|[a-z_]*_menu_button' => 'BotMenuButton',
'send_bot_custom_query|answer_bot_custom_query|set_bot_updates_status' => 'BotQueries',
'bot_recommendation_manager[_(-](?![.]get[(][)])|BotRecommendationManager' => 'BotRecommendationManager',
'BotVerification' => 'BotVerification',
'BotVerifierSettings' => 'BotVerifierSettings',
'BusinessAwayMessage' => 'BusinessAwayMessage',
'BusinessChatLink' => 'BusinessChatLink',
'BusinessConnectedBot' => 'BusinessConnectedBot',
'BusinessConnectionId' => 'BusinessConnectionId',
'business_connection_manager[_(-](?![.]get[(][)])|BusinessConnectionManager' => 'BusinessConnectionManager',
'BusinessGreetingMessage' => 'BusinessGreetingMessage',
'BusinessInfo|business_info' => 'BusinessInfo',
'BusinessIntro' => 'BusinessIntro',
'business_manager[_(-](?![.]get[(][)])|BusinessManager' => 'BusinessManager',
'BusinessRecipients' => 'BusinessRecipients',
'BusinessWorkHours' => 'BusinessWorkHours',
'callback_queries_manager[_(-](?![.]get[(][)])|CallbackQueriesManager' => 'CallbackQueriesManager',
'CallId' => 'CallId',
'call_manager[_(-](?![.]get[(][)])|CallManager' => 'CallManager',
'ChannelId' => 'ChannelId',
'channel_recommendation_manager[_(-](?![.]get[(][)])|ChannelRecommendationManager' => 'ChannelRecommendationManager',
'ChatId' => 'ChatId',
'chat_manager[_(-](?![.]get[(][)])|ChatManager([^ ;.]| [^*])' => 'ChatManager',
'common_dialog_manager[_(-](?![.]get[(][)])|CommonDialogManager' => 'CommonDialogManager',
'connection_state_manager[_(-](?![.]get[(][)])|ConnectionStateManager' => 'ConnectionStateManager',
'country_info_manager[_(-](?![.]get[(][)])|CountryInfoManager' => 'CountryInfoManager',
'CustomEmojiId' => 'CustomEmojiId',
'device_token_manager[_(-](?![.]get[(][)])|DeviceTokenManager' => 'DeviceTokenManager',
'DialogAction[^M]' => 'DialogAction',
'dialog_action_manager[_(-](?![.]get[(][)])|DialogActionManager' => 'DialogActionManager',
'DialogFilter[^A-Z]' => 'DialogFilter',
'DialogFilterId' => 'DialogFilterId',
'dialog_filter_manager[_(-](?![.]get[(][)])|DialogFilterManager' => 'DialogFilterManager',
'DialogId' => 'DialogId',
'dialog_invite_link_manager[_(-](?![.]get[(][)])|DialogInviteLinkManager' => 'DialogInviteLinkManager',
'DialogListId' => 'DialogListId',
'DialogLocation' => 'DialogLocation',
'dialog_manager[_(-](?![.]get[(][)])|DialogManager' => 'DialogManager',
'DialogParticipantFilter' => 'DialogParticipantFilter',
'dialog_participant_manager[_(-](?![.]get[(][)])|DialogParticipantManager' => 'DialogParticipantManager',
'DialogSource' => 'DialogSource',
'documents_manager[_(-](?![.]get[(][)])|DocumentsManager' => 'DocumentsManager',
'download_manager[_(-](?![.]get[(][)])|DownloadManager[^C]' => 'DownloadManager',
'DownloadManagerCallback' => 'DownloadManagerCallback',
'EmailVerification' => 'EmailVerification',
'EmojiGroup' => 'EmojiGroup',
'FactCheck' => 'FactCheck',
'file_reference_manager[_(-](?![.]get[(][)])|FileReferenceManager|file_references[)]' => 'FileReferenceManager',
'file_manager[_(-](?![.]get[(][)])|FileManager([^ ;.]| [^*])|update_file[)]' => 'files/FileManager',
'FolderId' => 'FolderId',
'forum_topic_manager[_(-](?![.]get[(][)])|ForumTopicManager' => 'ForumTopicManager',
'game_manager[_(-](?![.]get[(][)])|GameManager' => 'GameManager',
'G[(][)]|Global[^A-Za-z]' => 'Global',
'GlobalPrivacySettings' => 'GlobalPrivacySettings',
'GroupCallId' => 'GroupCallId',
'group_call_manager[_(-](?![.]get[(][)])|GroupCallManager' => 'GroupCallManager',
'hashtag_hints[_(-](?![.]get[(][)])|HashtagHints' => 'HashtagHints',
'inline_message_manager[_(-](?![.]get[(][)])|InlineMessageManager' => 'InlineMessageManager',
'inline_queries_manager[_(-](?![.]get[(][)])|InlineQueriesManager' => 'InlineQueriesManager',
'InputBusinessChatLink' => 'InputBusinessChatLink',
'language_pack_manager[_(-]|LanguagePackManager' => 'LanguagePackManager',
'link_manager[_(-](?![.]get[(][)])|LinkManager' => 'LinkManager',
'LogeventIdWithGeneration|add_log_event|delete_log_event|get_erase_log_event_promise|parse_time|store_time' => 'logevent/LogEventHelper',
'MessageCopyOptions' => 'MessageCopyOptions',
'MessageEffectId' => 'MessageEffectId',
'MessageForwardInfo|LastForwardedMessageInfo|forward_info' => 'MessageForwardInfo',
'MessageFullId' => 'MessageFullId',
'MessageId' => 'MessageId',
'message_import_manager[_(-](?![.]get[(][)])|MessageImportManager' => 'MessageImportManager',
'message_query_manager[_(-](?![.]get[(][)])|MessageQueryManager' => 'MessageQueryManager',
'MessageLinkInfo' => 'MessageLinkInfo',
'MessageQuote' => 'MessageQuote',
'MessageReaction|UnreadMessageReaction|[a-z_]*message[a-z_]*reaction|reload_paid_reaction_privacy|get_chosen_tags' => 'MessageReaction',
'MessageReactor' => 'MessageReactor',
'MessageSearchOffset' => 'MessageSearchOffset',
'[a-z_]*_message_sender' => 'MessageSender',
'messages_manager[_(-](?![.]get[(][)])|MessagesManager' => 'MessagesManager',
'MessageThreadInfo' => 'MessageThreadInfo',
'MessageTtl' => 'MessageTtl',
'MissingInvitee' => 'MissingInvitee',
'notification_manager[_(-](?![.]get[(][)])|NotificationManager|notifications[)]' => 'NotificationManager',
'notification_settings_manager[_(-](?![.]get[(][)])|NotificationSettingsManager' => 'NotificationSettingsManager',
'online_manager[_(-](?![.]get[(][)])|OnlineManager' => 'OnlineManager',
'option_manager[_(-](?![.]get[(][)])|OptionManager' => 'OptionManager',
'PaidReactionType' => 'PaidReactionType',
'password_manager[_(-](?![.]get[(][)])|PasswordManager' => 'PasswordManager',
'people_nearby_manager[_(-](?![.]get[(][)])|PeopleNearbyManager' => 'PeopleNearbyManager',
'phone_number_manager[_(-](?![.]get[(][)])|PhoneNumberManager' => 'PhoneNumberManager',
'PhotoSizeSource' => 'PhotoSizeSource',
'poll_manager[_(-](?![.]get[(][)])|PollManager' => 'PollManager',
'privacy_manager[_(-](?![.]get[(][)])|PrivacyManager' => 'PrivacyManager',
'promo_data_manager[_(-](?![.]get[(][)])|PromoDataManager' => 'PromoDataManager',
'PublicDialogType|get_public_dialog_type' => 'PublicDialogType',
'quick_reply_manager[_(-](?![.]get[(][)])|QuickReplyManager' => 'QuickReplyManager',
'ReactionListType|[a-z_]*_reaction_list_type' => 'ReactionListType',
'reaction_manager[_(-](?![.]get[(][)])|ReactionManager' => 'ReactionManager',
'ReactionNotificationSettings' => 'ReactionNotificationSettings',
'ReactionNotificationsFrom' => 'ReactionNotificationsFrom',
'ReactionType|[a-z_]*_reaction_type' => 'ReactionType',
'ReferralProgramInfo' => 'ReferralProgramInfo',
'referral_program_manager[_(-](?![.]get[(][)])|ReferralProgramManager' => 'ReferralProgramManager',
'ReferralProgramParameters' => 'ReferralProgramParameters',
'RequestActor|RequestOnceActor' => 'RequestActor',
'saved_messages_manager[_(-](?![.]get[(][)])|SavedMessagesManager' => 'SavedMessagesManager',
'ScopeNotificationSettings|[a-z_]*_scope_notification_settings' => 'ScopeNotificationSettings',
'SecretChatActor' => 'SecretChatActor',
'secret_chats_manager[_(-]|SecretChatsManager' => 'SecretChatsManager',
'secure_manager[_(-](?![.]get[(][)])|SecureManager' => 'SecureManager',
'SentEmailCode' => 'SentEmailCode',
'SharedDialog' => 'SharedDialog',
'sponsored_message_manager[_(-](?![.]get[(][)])|SponsoredMessageManager' => 'SponsoredMessageManager',
'StarAmount' => 'StarAmount',
'StarGift[^A-Z]' => 'StarGift',
'StarGiftAttribute' => 'StarGiftAttribute',
'StarGiftId' => 'StarGiftId',
'star_gift_manager[_(-](?![.]get[(][)])|StarGiftManager' => 'StarGiftManager',
'star_manager[_(-](?![.]get[(][)])|StarManager' => 'StarManager',
'StarSubscription[^P]' => 'StarSubscription',
'StarSubscriptionPricing' => 'StarSubscriptionPricing',
'state_manager[_(-](?![.]get[(][)])|StateManager' => 'StateManager',
'statistics_manager[_(-](?![.]get[(][)])|StatisticsManager' => 'StatisticsManager',
'StickerSetId' => 'StickerSetId',
'stickers_manager[_(-](?![.]get[(][)])|StickersManager' => 'StickersManager',
'storage_manager[_(-](?![.]get[(][)])|StorageManager' => 'StorageManager',
'StoryId' => 'StoryId',
'StoryListId' => 'StoryListId',
'story_manager[_(-](?![.]get[(][)])|StoryManager' => 'StoryManager',
'SuggestedAction|[a-z_]*_suggested_action' => 'SuggestedAction',
'suggested_action_manager[_(-](?![.]get[(][)])|SuggestedActionManager' => 'SuggestedActionManager',
'SynchronousRequests' => 'SynchronousRequests',
'TargetDialogTypes' => 'TargetDialogTypes',
'td_api' => 'td_api',
'td_db[(][)]|TdDb[^A-Za-z]' => 'TdDb',
'telegram_api' => 'telegram_api',
'terms_of_service_manager[_(-](?![.]get[(][)])|TermsOfServiceManager' => 'TermsOfServiceManager',
'theme_manager[_(-](?![.]get[(][)])|ThemeManager' => 'ThemeManager',
'ThemeSettings' => 'ThemeSettings',
'time_zone_manager[_(-](?![.]get[(][)])|TimeZoneManager' => 'TimeZoneManager',
'TopDialogCategory|get_top_dialog_category' => 'TopDialogCategory',
'top_dialog_manager[_(-](?![.]get[(][)])|TopDialogManager' => 'TopDialogManager',
'translation_manager[_(-](?![.]get[(][)])|TranslationManager' => 'TranslationManager',
'transcription_manager[_(-](?![.]get[(][)])|TranscriptionManager' => 'TranscriptionManager',
'updates_manager[_(-](?![.]get[(][)])|UpdatesManager|get_difference[)]|updateSentMessage|dummyUpdate' => 'UpdatesManager',
'UserId' => 'UserId',
'user_manager[_(-](?![.]get[(][)])|UserManager([^ ;.]| [^*])' => 'UserManager',
'UserStarGift' => 'UserStarGift',
'video_notes_manager[_(-](?![.]get[(][)])|VideoNotesManager' => 'VideoNotesManager',
'videos_manager[_(-](?![.]get[(][)])|VideosManager' => 'VideosManager',
'voice_notes_manager[_(-](?![.]get[(][)])|VoiceNotesManager' => 'VoiceNotesManager',
'web_app_manager[_(-](?![.]get[(][)])|WebAppManager' => 'WebAppManager',
'WebAppOpenParameters' => 'WebAppOpenParameters',
'WebPageId(Hash)?' => 'WebPageId',
'web_pages_manager[_(-](?![.]get[(][)])|WebPagesManager' => 'WebPagesManager');
foreach ($td_methods as $pattern => $header) {
if (strpos($cpp_name, $header) !== false) {
continue;
}
$include_name = '#include "td/telegram/'.$header.'.h"';
if (strpos($new_content, $include_name) !== false && preg_match('/[^a-zA-Z0-9_]('.$pattern.')/', str_replace($include_name, '', $new_content)) === 0) {
$new_content = str_replace($include_name, '', $new_content);
}
}
if (!file_exists($new_files[$n]) || file_get_contents($new_files[$n]) !== $new_content) {
echo "Writing file ".$new_files[$n].PHP_EOL;
file_put_contents($new_files[$n], $new_content);
}
}
}
if (in_array('--help', $argv) || in_array('-h', $argv)) {
echo "Usage: php SplitSource.php [OPTION]...\n".
"Splits some source files to reduce a maximum amount of RAM needed for compiling a single file.\n".
" -u, --undo Undo all source code changes.\n".
" -h, --help Show this help.\n";
exit(2);
}
$undo = in_array('--undo', $argv) || in_array('-u', $argv);
$files = array('td/telegram/ChatManager' => 10,
'td/telegram/MessagesManager' => 50,
'td/telegram/NotificationManager' => 10,
'td/telegram/Requests' => 50,
'td/telegram/StickersManager' => 10,
'td/telegram/StoryManager' => 10,
'td/telegram/UpdatesManager' => 10,
'td/telegram/UserManager' => 10,
'td/generate/auto/td/telegram/td_api' => 10,
'td/generate/auto/td/telegram/td_api_json' => 10,
'td/generate/auto/td/telegram/telegram_api' => 10);
foreach ($files as $file => $chunks) {
split_file($file, $chunks, $undo);
}

Some files were not shown because too many files have changed in this diff Show More