mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Conference calls
This commit is contained in:
parent
66c244f540
commit
aaf52d4282
@ -166,11 +166,11 @@ private final class EmbeddedBroadcastUploadImpl: BroadcastUploadImpl {
|
||||
preferX264: false,
|
||||
logPath: "",
|
||||
onMutedSpeechActivityDetected: { _ in },
|
||||
encryptionKey: nil,
|
||||
isConference: false,
|
||||
audioIsActiveByDefault: true,
|
||||
isStream: false,
|
||||
sharedAudioDevice: nil
|
||||
sharedAudioDevice: nil,
|
||||
encryptionContext: nil
|
||||
)
|
||||
self.callContext = callContext
|
||||
self.joinPayloadDisposable = (callContext.joinPayload
|
||||
|
@ -23,7 +23,6 @@ swift_library(
|
||||
"//submodules/PersistentStringHash:PersistentStringHash",
|
||||
"//submodules/Utils/RangeSet",
|
||||
"//submodules/Media/ConvertOpusToAAC",
|
||||
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -925,8 +925,14 @@ private final class NotificationServiceHandler {
|
||||
var peer: EnginePeer?
|
||||
var localContactId: String?
|
||||
}
|
||||
|
||||
struct ConferenceCallData {
|
||||
var id: Int64
|
||||
var updates: String
|
||||
}
|
||||
|
||||
var callData: CallData?
|
||||
var conferenceCallData: ConferenceCallData?
|
||||
|
||||
if let messageIdString = payloadJson["msg_id"] as? String {
|
||||
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) {
|
||||
var peer: EnginePeer?
|
||||
|
||||
|
@ -955,11 +955,17 @@ public enum JoinSubjectScreenMode {
|
||||
}
|
||||
|
||||
public final class GroupCall {
|
||||
public let id: Int64
|
||||
public let accessHash: Int64
|
||||
public let slug: String
|
||||
public let inviter: EnginePeer?
|
||||
public let members: [EnginePeer]
|
||||
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.members = members
|
||||
self.totalMemberCount = totalMemberCount
|
||||
|
@ -38,6 +38,7 @@ public final class OpenChatMessageParams {
|
||||
public let openUrl: (String) -> Void
|
||||
public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void
|
||||
public let callPeer: (PeerId, Bool) -> Void
|
||||
public let openConferenceCall: (Message) -> Void
|
||||
public let enqueueMessage: (EnqueueMessage) -> Void
|
||||
public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
|
||||
public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?
|
||||
@ -70,6 +71,7 @@ public final class OpenChatMessageParams {
|
||||
openUrl: @escaping (String) -> Void,
|
||||
openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void,
|
||||
callPeer: @escaping (PeerId, Bool) -> Void,
|
||||
openConferenceCall: @escaping (Message) -> Void,
|
||||
enqueueMessage: @escaping (EnqueueMessage) -> Void,
|
||||
sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?,
|
||||
sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?,
|
||||
@ -100,6 +102,7 @@ public final class OpenChatMessageParams {
|
||||
self.openUrl = openUrl
|
||||
self.openPeer = openPeer
|
||||
self.callPeer = callPeer
|
||||
self.openConferenceCall = openConferenceCall
|
||||
self.enqueueMessage = enqueueMessage
|
||||
self.sendSticker = sendSticker
|
||||
self.sendEmoji = sendEmoji
|
||||
|
@ -422,6 +422,7 @@ public protocol PresentationGroupCall: AnyObject {
|
||||
var accountContext: AccountContext { get }
|
||||
var internalId: CallSessionInternalId { get }
|
||||
var peerId: EnginePeer.Id? { get }
|
||||
var callId: Int64? { get }
|
||||
|
||||
var hasVideo: Bool { get }
|
||||
var hasScreencast: Bool { get }
|
||||
@ -431,7 +432,6 @@ public protocol PresentationGroupCall: AnyObject {
|
||||
var isStream: Bool { get }
|
||||
var isConference: Bool { get }
|
||||
var conferenceSource: CallSessionInternalId? { get }
|
||||
var encryptionKeyValue: Data? { get }
|
||||
|
||||
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
|
||||
|
||||
@ -447,6 +447,8 @@ public protocol PresentationGroupCall: AnyObject {
|
||||
var isMuted: Signal<Bool, NoError> { get }
|
||||
var isNoiseSuppressionEnabled: Signal<Bool, NoError> { get }
|
||||
|
||||
var e2eEncryptionKeyHash: Signal<Data?, NoError> { get }
|
||||
|
||||
var memberEvents: Signal<PresentationGroupCallMemberEvent, 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 {
|
||||
var currentCallSignal: Signal<PresentationCall?, 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 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 joinConferenceCall(
|
||||
accountContext: AccountContext,
|
||||
initialCall: EngineGroupCallDescription,
|
||||
reference: InternalGroupCallReference,
|
||||
mode: JoinConferenceCallMode
|
||||
)
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ public final class BrowserBookmarksScreen: ViewController {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, longTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
|
@ -33,6 +33,7 @@ swift_library(
|
||||
"//submodules/ItemListPeerActionItem",
|
||||
"//submodules/InviteLinksUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramCallsUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -15,6 +15,7 @@ import ContextUI
|
||||
import TelegramBaseController
|
||||
import InviteLinksUI
|
||||
import UndoUI
|
||||
import TelegramCallsUI
|
||||
|
||||
public enum CallListControllerMode {
|
||||
case tab
|
||||
@ -206,25 +207,53 @@ public final class CallListController: TelegramBaseController {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if let result {
|
||||
switch result {
|
||||
case .linkCopied:
|
||||
//TODO:localize
|
||||
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
|
||||
if case .undo = action {
|
||||
//TODO:release
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if let result {
|
||||
switch result {
|
||||
case .linkCopied:
|
||||
//TODO:localize
|
||||
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
|
||||
if case .undo = action {
|
||||
openCall()
|
||||
}
|
||||
return false
|
||||
}), 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() {
|
||||
|
@ -117,13 +117,6 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
switch search {
|
||||
case .recentPeers:
|
||||
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:
|
||||
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)
|
||||
@ -539,9 +532,30 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
} else if case let .search(search) = source {
|
||||
switch 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 !items.isEmpty {
|
||||
items.append(.separator)
|
||||
if !addedSeparator {
|
||||
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
|
||||
if let chatListController = chatListController {
|
||||
|
@ -133,7 +133,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
peerSelected(peer, nil, false, .generic)
|
||||
}, peerContextAction: { peer, node, gesture, location in
|
||||
if let peerContextAction = peerContextAction {
|
||||
peerContextAction(peer, .recentPeers, node, gesture, location)
|
||||
peerContextAction(peer, .recentPeers(isTopPeer: true), node, gesture, location)
|
||||
} else {
|
||||
gesture?.cancel()
|
||||
}
|
||||
@ -1409,7 +1409,7 @@ private struct ChatListSearchMessagesContext {
|
||||
}
|
||||
|
||||
public enum ChatListSearchContextActionSource {
|
||||
case recentPeers
|
||||
case recentPeers(isTopPeer: Bool)
|
||||
case recentSearch
|
||||
case recentApps
|
||||
case popularApps
|
||||
@ -3212,6 +3212,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
interaction.openUrl(url)
|
||||
}, openPeer: { _, _ in
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, enqueueMessage: { _ 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)
|
||||
@ -3406,6 +3407,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
interaction.openUrl(url)
|
||||
}, openPeer: { peer, navigation in
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource))
|
||||
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in
|
||||
|
@ -298,6 +298,9 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
messageText = invoice.title
|
||||
case let action as TelegramMediaAction:
|
||||
switch action.action {
|
||||
case .conferenceCall:
|
||||
//TODO:localize
|
||||
messageText = "Group call"
|
||||
case let .phoneCall(_, discardReason, _, isVideo):
|
||||
hideAuthor = !isPeerGroup
|
||||
let incoming = message.flags.contains(.Incoming)
|
||||
|
@ -162,6 +162,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
|
||||
public enum CompletionResult {
|
||||
case linkCopied
|
||||
case openCall
|
||||
}
|
||||
|
||||
private var animatedIn = false
|
||||
|
@ -360,7 +360,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
}
|
||||
)
|
||||
//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 {
|
||||
justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string))
|
||||
}
|
||||
@ -575,6 +575,9 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
if strongSelf.justCreatedCallTextNode !== justCreatedCallTextNode {
|
||||
strongSelf.justCreatedCallTextNode?.removeFromSupernode()
|
||||
strongSelf.justCreatedCallTextNode = justCreatedCallTextNode
|
||||
|
||||
//justCreatedCallTextNode.highlig
|
||||
|
||||
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))
|
||||
|
@ -29,6 +29,7 @@ swift_library(
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent",
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -19,6 +19,7 @@ import AvatarVideoNode
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import StorySetIndicatorComponent
|
||||
import HierarchyTrackingLayer
|
||||
|
||||
private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
|
||||
private var currentInHierarchy = false
|
||||
@ -224,6 +225,8 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
private var progress: Signal<Float?, NoError>?
|
||||
private var loadingProgressDisposable = MetaDisposable()
|
||||
private var hasProgress = false
|
||||
|
||||
private let hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
||||
public let isReady = Promise<Bool>()
|
||||
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 {
|
||||
@ -364,6 +381,9 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
guard let videoContent = self.videoContent, let isCentral = self.isCentral, isCentral, self.videoNode == nil else {
|
||||
return
|
||||
}
|
||||
if !self.hierarchyTrackingLayer.isInHierarchy {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1471112230] = { return $0.readInt32() }
|
||||
dict[570911930] = { return $0.readInt64() }
|
||||
dict[571523412] = { return $0.readDouble() }
|
||||
dict[0x0929C32F] = { return parseInt256($0) }
|
||||
dict[-1255641564] = { return parseString($0) }
|
||||
dict[-1194283041] = { return Api.AccountDaysTTL.parse_accountDaysTTL($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[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($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[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($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[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($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[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($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[-339958837] = { return Api.MessageAction.parse_messageActionChatJoinedByRequest($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[-85549226] = { return Api.MessageAction.parse_messageActionCustomAction($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[-193510921] = { return Api.PeerSettings.parse_peerSettings($0) }
|
||||
dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) }
|
||||
dict[1000707084] = { return Api.PhoneCall.parse_phoneCall($0) }
|
||||
dict[587035009] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
|
||||
dict[-103656189] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) }
|
||||
dict[810769141] = { return Api.PhoneCall.parse_phoneCall($0) }
|
||||
dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
|
||||
dict[1355435489] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) }
|
||||
dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) }
|
||||
dict[1161174115] = { return Api.PhoneCall.parse_phoneCallRequested($0) }
|
||||
dict[-288085928] = { return Api.PhoneCall.parse_phoneCallWaiting($0) }
|
||||
dict[-1344096199] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonAllowGroupCall($0) }
|
||||
dict[347139340] = { return Api.PhoneCall.parse_phoneCallRequested($0) }
|
||||
dict[-987599081] = { return Api.PhoneCall.parse_phoneCallWaiting($0) }
|
||||
dict[-84416311] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonBusy($0) }
|
||||
dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) }
|
||||
dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) }
|
||||
dict[-1615072777] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMigrateConferenceCall($0) }
|
||||
dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) }
|
||||
dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($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[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($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[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) }
|
||||
dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) }
|
||||
|
@ -169,6 +169,8 @@ public extension Api {
|
||||
public extension Api {
|
||||
enum InputGroupCall: TypeConstructorDescription {
|
||||
case inputGroupCall(id: Int64, accessHash: Int64)
|
||||
case inputGroupCallInviteMessage(msgId: Int32)
|
||||
case inputGroupCallSlug(slug: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -179,6 +181,18 @@ public extension Api {
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
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 {
|
||||
case .inputGroupCall(let id, let accessHash):
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -349,6 +349,7 @@ public extension Api {
|
||||
case messageActionChatJoinedByLink(inviterId: Int64)
|
||||
case messageActionChatJoinedByRequest
|
||||
case messageActionChatMigrateTo(channelId: Int64)
|
||||
case messageActionConferenceCall(flags: Int32, callId: Int64, duration: Int32?, otherParticipants: [Api.Peer]?)
|
||||
case messageActionContactSignUp
|
||||
case messageActionCustomAction(message: String)
|
||||
case messageActionEmpty
|
||||
@ -479,6 +480,19 @@ public extension Api {
|
||||
}
|
||||
serializeInt64(channelId, buffer: buffer, boxed: false)
|
||||
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:
|
||||
if boxed {
|
||||
buffer.appendInt32(-202219658)
|
||||
@ -834,6 +848,8 @@ public extension Api {
|
||||
return ("messageActionChatJoinedByRequest", [])
|
||||
case .messageActionChatMigrateTo(let channelId):
|
||||
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:
|
||||
return ("messageActionContactSignUp", [])
|
||||
case .messageActionCustomAction(let message):
|
||||
@ -1058,6 +1074,28 @@ public extension Api {
|
||||
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? {
|
||||
return Api.MessageAction.messageActionContactSignUp
|
||||
}
|
||||
|
@ -1092,18 +1092,18 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum PhoneCall: TypeConstructorDescription {
|
||||
case phoneCall(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, protocol: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32, customParameters: Api.DataJSON?, conferenceCall: Api.InputGroupCall?)
|
||||
case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol, conferenceCall: Api.InputGroupCall?)
|
||||
case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?, 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)
|
||||
case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?)
|
||||
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 phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?, 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?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
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 {
|
||||
buffer.appendInt32(1000707084)
|
||||
buffer.appendInt32(810769141)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
@ -1121,11 +1121,10 @@ public extension Api {
|
||||
}
|
||||
serializeInt32(startDate, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 7) != 0 {customParameters!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
|
||||
break
|
||||
case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`, let conferenceCall):
|
||||
case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`):
|
||||
if boxed {
|
||||
buffer.appendInt32(587035009)
|
||||
buffer.appendInt32(912311057)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
@ -1135,17 +1134,15 @@ public extension Api {
|
||||
serializeInt64(participantId, buffer: buffer, boxed: false)
|
||||
serializeBytes(gB, buffer: buffer, boxed: false)
|
||||
`protocol`.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
|
||||
break
|
||||
case .phoneCallDiscarded(let flags, let id, let reason, let duration, let conferenceCall):
|
||||
case .phoneCallDiscarded(let flags, let id, let reason, let duration):
|
||||
if boxed {
|
||||
buffer.appendInt32(-103656189)
|
||||
buffer.appendInt32(1355435489)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {reason!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
|
||||
break
|
||||
case .phoneCallEmpty(let id):
|
||||
if boxed {
|
||||
@ -1153,9 +1150,9 @@ public extension Api {
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`, let conferenceCall):
|
||||
case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`):
|
||||
if boxed {
|
||||
buffer.appendInt32(1161174115)
|
||||
buffer.appendInt32(347139340)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
@ -1165,11 +1162,10 @@ public extension Api {
|
||||
serializeInt64(participantId, buffer: buffer, boxed: false)
|
||||
serializeBytes(gAHash, buffer: buffer, boxed: false)
|
||||
`protocol`.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
|
||||
break
|
||||
case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate, let conferenceCall):
|
||||
case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-288085928)
|
||||
buffer.appendInt32(-987599081)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
@ -1179,25 +1175,24 @@ public extension Api {
|
||||
serializeInt64(participantId, buffer: buffer, boxed: false)
|
||||
`protocol`.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(receiveDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate, let customParameters, let conferenceCall):
|
||||
return ("phoneCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAOrB", gAOrB as Any), ("keyFingerprint", keyFingerprint as Any), ("`protocol`", `protocol` as Any), ("connections", connections as Any), ("startDate", startDate as Any), ("customParameters", customParameters as Any), ("conferenceCall", conferenceCall as Any)])
|
||||
case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`, let conferenceCall):
|
||||
return ("phoneCallAccepted", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gB", gB as Any), ("`protocol`", `protocol` as Any), ("conferenceCall", conferenceCall as Any)])
|
||||
case .phoneCallDiscarded(let flags, let id, let reason, let duration, let conferenceCall):
|
||||
return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any), ("conferenceCall", conferenceCall as Any)])
|
||||
case .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)])
|
||||
case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`):
|
||||
return ("phoneCallAccepted", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gB", gB as Any), ("`protocol`", `protocol` as Any)])
|
||||
case .phoneCallDiscarded(let flags, let id, let reason, let duration):
|
||||
return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any)])
|
||||
case .phoneCallEmpty(let id):
|
||||
return ("phoneCallEmpty", [("id", id as Any)])
|
||||
case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`, let conferenceCall):
|
||||
return ("phoneCallRequested", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAHash", gAHash as Any), ("`protocol`", `protocol` as Any), ("conferenceCall", conferenceCall as Any)])
|
||||
case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate, let conferenceCall):
|
||||
return ("phoneCallWaiting", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("`protocol`", `protocol` as Any), ("receiveDate", receiveDate as Any), ("conferenceCall", conferenceCall as Any)])
|
||||
case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`):
|
||||
return ("phoneCallRequested", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAHash", gAHash as Any), ("`protocol`", `protocol` as Any)])
|
||||
case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate):
|
||||
return ("phoneCallWaiting", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("`protocol`", `protocol` as Any), ("receiveDate", receiveDate as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1232,10 +1227,6 @@ public extension Api {
|
||||
if Int(_1!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() {
|
||||
_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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -1248,9 +1239,8 @@ public extension Api {
|
||||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != 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 && _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, conferenceCall: _13)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
|
||||
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)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -1275,10 +1265,6 @@ public extension Api {
|
||||
if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol
|
||||
}
|
||||
var _9: Api.InputGroupCall?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -1287,9 +1273,8 @@ public extension Api {
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!, conferenceCall: _9)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -1306,17 +1291,12 @@ public extension Api {
|
||||
} }
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Api.InputGroupCall?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4, conferenceCall: _5)
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -1352,10 +1332,6 @@ public extension Api {
|
||||
if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol
|
||||
}
|
||||
var _9: Api.InputGroupCall?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -1364,9 +1340,8 @@ public extension Api {
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!, conferenceCall: _9)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -1391,10 +1366,6 @@ public extension Api {
|
||||
}
|
||||
var _8: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_8 = reader.readInt32() }
|
||||
var _9: Api.InputGroupCall?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -1403,9 +1374,8 @@ public extension Api {
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != 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 && _c9 {
|
||||
return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8, conferenceCall: _9)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -1,19 +1,13 @@
|
||||
public extension Api {
|
||||
enum PhoneCallDiscardReason: TypeConstructorDescription {
|
||||
case phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer)
|
||||
case phoneCallDiscardReasonBusy
|
||||
case phoneCallDiscardReasonDisconnect
|
||||
case phoneCallDiscardReasonHangup
|
||||
case phoneCallDiscardReasonMigrateConferenceCall(slug: String)
|
||||
case phoneCallDiscardReasonMissed
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .phoneCallDiscardReasonAllowGroupCall(let encryptedKey):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1344096199)
|
||||
}
|
||||
serializeBytes(encryptedKey, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .phoneCallDiscardReasonBusy:
|
||||
if boxed {
|
||||
buffer.appendInt32(-84416311)
|
||||
@ -31,6 +25,12 @@ public extension Api {
|
||||
buffer.appendInt32(1471006352)
|
||||
}
|
||||
|
||||
break
|
||||
case .phoneCallDiscardReasonMigrateConferenceCall(let slug):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1615072777)
|
||||
}
|
||||
serializeString(slug, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .phoneCallDiscardReasonMissed:
|
||||
if boxed {
|
||||
@ -43,30 +43,19 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .phoneCallDiscardReasonAllowGroupCall(let encryptedKey):
|
||||
return ("phoneCallDiscardReasonAllowGroupCall", [("encryptedKey", encryptedKey as Any)])
|
||||
case .phoneCallDiscardReasonBusy:
|
||||
return ("phoneCallDiscardReasonBusy", [])
|
||||
case .phoneCallDiscardReasonDisconnect:
|
||||
return ("phoneCallDiscardReasonDisconnect", [])
|
||||
case .phoneCallDiscardReasonHangup:
|
||||
return ("phoneCallDiscardReasonHangup", [])
|
||||
case .phoneCallDiscardReasonMigrateConferenceCall(let slug):
|
||||
return ("phoneCallDiscardReasonMigrateConferenceCall", [("slug", slug as Any)])
|
||||
case .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? {
|
||||
return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy
|
||||
}
|
||||
@ -76,6 +65,17 @@ public extension Api {
|
||||
public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? {
|
||||
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? {
|
||||
return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed
|
||||
}
|
||||
|
@ -1048,6 +1048,7 @@ public extension Api {
|
||||
case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32)
|
||||
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
|
||||
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 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?)
|
||||
@ -1738,6 +1739,19 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(chatId!, buffer: buffer, boxed: false)}
|
||||
call.serialize(buffer, true)
|
||||
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):
|
||||
if boxed {
|
||||
buffer.appendInt32(192428418)
|
||||
@ -2499,6 +2513,8 @@ public extension Api {
|
||||
return ("updateGeoLiveViewed", [("peer", peer as Any), ("msgId", msgId as Any)])
|
||||
case .updateGroupCall(let flags, let chatId, let call):
|
||||
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):
|
||||
return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)])
|
||||
case .updateGroupCallParticipants(let call, let participants, let version):
|
||||
@ -3939,6 +3955,30 @@ public extension Api {
|
||||
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? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
|
@ -9855,16 +9855,15 @@ 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()
|
||||
buffer.appendInt32(-540472917)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt64(keyFingerprint, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("peer", String(describing: peer)), ("keyFingerprint", String(describing: keyFingerprint))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in
|
||||
buffer.appendInt32(-70320410)
|
||||
serializeInt32(randomId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("randomId", String(describing: randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.phone.PhoneCall?
|
||||
var result: Api.phone.GroupCall?
|
||||
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
|
||||
})
|
||||
@ -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 {
|
||||
static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
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 {
|
||||
static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.JoinAsPeers>) {
|
||||
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 {
|
||||
static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
@ -10103,16 +10172,18 @@ 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()
|
||||
buffer.appendInt32(-702669325)
|
||||
buffer.appendInt32(-624854114)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
call.serialize(buffer, true)
|
||||
joinAs.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(inviteHash!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(keyFingerprint!, buffer: buffer, boxed: false)}
|
||||
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)
|
||||
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)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -10185,16 +10256,15 @@ 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()
|
||||
buffer.appendInt32(-1497079796)
|
||||
buffer.appendInt32(1124046573)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
userId.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 1) != 0 {conferenceCall!.serialize(buffer, true)}
|
||||
serializeInt32(randomId, buffer: buffer, boxed: false)
|
||||
serializeBytes(gAHash, buffer: buffer, boxed: false)
|
||||
`protocol`.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("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)
|
||||
var result: Api.phone.PhoneCall?
|
||||
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 {
|
||||
static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -992,14 +992,14 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
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)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
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 {
|
||||
buffer.appendInt32(-839330845)
|
||||
buffer.appendInt32(-711498484)
|
||||
}
|
||||
serializeInt32(flags, 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)}
|
||||
serializeInt32(unmutedVideoLimit, buffer: buffer, boxed: false)
|
||||
serializeInt32(version, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 14) != 0 {serializeInt64(conferenceFromCall!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||
if boxed {
|
||||
@ -1027,8 +1026,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
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):
|
||||
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)])
|
||||
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)])
|
||||
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||
return ("groupCallDiscarded", [("id", id as Any), ("accessHash", accessHash as Any), ("duration", duration as Any)])
|
||||
}
|
||||
@ -1057,8 +1056,6 @@ public extension Api {
|
||||
_10 = reader.readInt32()
|
||||
var _11: Int32?
|
||||
_11 = reader.readInt32()
|
||||
var _12: Int64?
|
||||
if Int(_1!) & Int(1 << 14) != 0 {_12 = reader.readInt64() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -1070,9 +1067,8 @@ public extension Api {
|
||||
let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil
|
||||
let _c10 = _10 != 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 && _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!, conferenceFromCall: _12)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
||||
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!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -3,13 +3,48 @@ import Foundation
|
||||
public struct Int128 {
|
||||
public var _0: 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 _1: Int64
|
||||
public var _2: 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) {
|
||||
|
@ -39,6 +39,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
|
||||
}, openUrl: { _ in
|
||||
}, openPeer: { peer, navigation in
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, enqueueMessage: { message in
|
||||
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
|
||||
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in
|
||||
@ -469,7 +470,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
return
|
||||
}
|
||||
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(
|
||||
presentationData: presentationData,
|
||||
|
@ -120,6 +120,7 @@ swift_library(
|
||||
"//submodules/DirectMediaImageCache",
|
||||
"//submodules/FastBlur",
|
||||
"//submodules/InviteLinksUI",
|
||||
"//third-party/td:TdBinding",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -233,7 +233,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
public let internalId: CallSessionInternalId
|
||||
public let peerId: EnginePeer.Id
|
||||
public let isOutgoing: Bool
|
||||
private let isIncomingConference: Bool
|
||||
private let incomingConferenceSource: EngineMessage.Id?
|
||||
public var isVideo: Bool
|
||||
public var isVideoPossible: Bool
|
||||
private let enableStunMarking: Bool
|
||||
@ -354,6 +354,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
private var conferenceCallDisposable: Disposable?
|
||||
private var upgradedToConferenceCompletions = Bag<(PresentationGroupCall) -> Void>()
|
||||
|
||||
private var isAcceptingIncomingConference: Bool = false
|
||||
private var waitForConferenceCallReadyDisposable: Disposable?
|
||||
private let conferenceStatePromise = ValuePromise<PresentationCallConferenceState?>(nil)
|
||||
public private(set) var conferenceStateValue: PresentationCallConferenceState? {
|
||||
@ -386,7 +387,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
internalId: CallSessionInternalId,
|
||||
peerId: EnginePeer.Id,
|
||||
isOutgoing: Bool,
|
||||
isIncomingConference: Bool,
|
||||
incomingConferenceSource: EngineMessage.Id?,
|
||||
peer: EnginePeer?,
|
||||
proxyServer: ProxyServerSettings?,
|
||||
auxiliaryServers: [CallAuxiliaryServer],
|
||||
@ -421,7 +422,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
self.internalId = internalId
|
||||
self.peerId = peerId
|
||||
self.isOutgoing = isOutgoing
|
||||
self.isIncomingConference = isIncomingConference
|
||||
self.incomingConferenceSource = incomingConferenceSource
|
||||
self.isVideo = initialState?.type == .video
|
||||
self.isVideoPossible = isVideoPossible
|
||||
self.enableStunMarking = enableStunMarking
|
||||
@ -729,12 +730,15 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
var wasTerminated = false
|
||||
if let previous = previous {
|
||||
switch previous.state {
|
||||
case .active:
|
||||
wasActive = true
|
||||
case .terminated, .dropping:
|
||||
case .active:
|
||||
wasActive = true
|
||||
case let .terminated(_, reason, _):
|
||||
if case .ended(.switchedToConference) = reason {
|
||||
} else {
|
||||
wasTerminated = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -858,22 +862,23 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
self.callWasActive = true
|
||||
presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
case let .dropping(reason):
|
||||
presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
if case .ended(.switchedToConference) = reason {
|
||||
} else {
|
||||
presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
}
|
||||
case let .terminated(id, reason, options):
|
||||
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
case let .requesting(ringing, _):
|
||||
case let .requesting(ringing):
|
||||
presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
case let .active(_, _, keyVisualHash, _, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _):
|
||||
case .active(_, _, _, _, _, _, _, _), .switchedToConference:
|
||||
self.callWasActive = true
|
||||
|
||||
var isConference = false
|
||||
if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state {
|
||||
isConference = conferenceCall != nil
|
||||
} else if case .switchedToConference = sessionState.state {
|
||||
if case .switchedToConference = sessionState.state {
|
||||
isConference = true
|
||||
}
|
||||
|
||||
if let callContextState = callContextState, !isConference {
|
||||
if let callContextState = callContextState, !isConference, case let .active(_, _, keyVisualHash, _, _, _, _, _) = sessionState.state {
|
||||
switch callContextState.state {
|
||||
case .initializing:
|
||||
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel)
|
||||
@ -899,151 +904,178 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var conferenceCallData: (key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)?
|
||||
var conferenceFromCallId: CallId?
|
||||
switch sessionState.state {
|
||||
case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall, isIncomingConference):
|
||||
if let conferenceCall, !isIncomingConference {
|
||||
conferenceFromCallId = id
|
||||
conferenceCallData = (key, keyVisualHash, conferenceCall)
|
||||
var conferenceCallData: InternalGroupCallReference?
|
||||
if let incomingConferenceSource = self.incomingConferenceSource {
|
||||
if self.isAcceptingIncomingConference {
|
||||
conferenceCallData = .message(id: incomingConferenceSource)
|
||||
}
|
||||
} else {
|
||||
switch sessionState.state {
|
||||
case let .switchedToConference(slug):
|
||||
conferenceCallData = .link(slug: slug)
|
||||
default:
|
||||
break
|
||||
}
|
||||
case let .switchedToConference(key, keyVisualHash, conferenceCall):
|
||||
conferenceCallData = (key, keyVisualHash, conferenceCall)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let (key, _, conferenceCall) = conferenceCallData {
|
||||
if let conferenceCallData {
|
||||
if self.conferenceCallDisposable == nil {
|
||||
self.conferenceCallDisposable = EmptyDisposable
|
||||
|
||||
#if DEBUG
|
||||
print("Switching to conference call with encryption key: \(key.base64EncodedString())")
|
||||
#endif
|
||||
|
||||
let conferenceCall = PresentationGroupCallImpl(
|
||||
accountContext: self.context,
|
||||
audioSession: self.audioSession,
|
||||
callKitIntegration: self.callKitIntegration,
|
||||
getDeviceAccessData: self.getDeviceAccessData,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: conferenceCall.id,
|
||||
accessHash: conferenceCall.accessHash,
|
||||
title: nil,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
),
|
||||
internalId: CallSessionInternalId(),
|
||||
peerId: nil,
|
||||
isChannel: false,
|
||||
invite: nil,
|
||||
joinAsPeerId: nil,
|
||||
isStream: false,
|
||||
encryptionKey: (key, 1),
|
||||
conferenceFromCallId: conferenceFromCallId,
|
||||
conferenceSourceId: self.internalId,
|
||||
isConference: true,
|
||||
sharedAudioContext: self.sharedAudioContext
|
||||
)
|
||||
self.conferenceCallImpl = conferenceCall
|
||||
conferenceCall.upgradedConferenceCall = self
|
||||
|
||||
conferenceCall.setConferenceInvitedPeers(self.pendingInviteToConferencePeerIds)
|
||||
for peerId in self.pendingInviteToConferencePeerIds {
|
||||
let _ = conferenceCall.invitePeer(peerId)
|
||||
}
|
||||
|
||||
conferenceCall.setIsMuted(action: self.isMutedValue ? .muted(isPushToTalkActive: false) : .unmuted)
|
||||
if let videoCapturer = self.videoCapturer {
|
||||
conferenceCall.requestVideo(capturer: videoCapturer)
|
||||
}
|
||||
|
||||
let waitForLocalVideo = self.videoCapturer != nil
|
||||
|
||||
let waitForRemotePeerId: EnginePeer.Id? = self.peerId
|
||||
var waitForRemoteVideo: EnginePeer.Id?
|
||||
if let callContextState = self.callContextState {
|
||||
switch callContextState.remoteVideoState {
|
||||
case .active, .paused:
|
||||
waitForRemoteVideo = self.peerId
|
||||
case .inactive:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.conferenceStateValue = .preparing
|
||||
|
||||
self.waitForConferenceCallReadyDisposable?.dispose()
|
||||
self.waitForConferenceCallReadyDisposable = (combineLatest(queue: .mainQueue(),
|
||||
conferenceCall.state,
|
||||
conferenceCall.members
|
||||
)
|
||||
|> filter { state, members in
|
||||
if state.networkState != .connected {
|
||||
return false
|
||||
}
|
||||
if let waitForRemotePeerId {
|
||||
var found = false
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.peer.id == waitForRemotePeerId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if waitForLocalVideo {
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.peer.id == state.myPeerId {
|
||||
if participant.videoDescription == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let waitForRemoteVideo {
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.peer.id == waitForRemoteVideo {
|
||||
if participant.videoDescription == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|> map { _, _ -> Void in
|
||||
return Void()
|
||||
}
|
||||
|> take(1)
|
||||
|> timeout(10.0, queue: .mainQueue(), alternate: .single(Void()))).start(next: { [weak self] _ in
|
||||
let conferenceCallSignal = self.context.engine.calls.getCurrentGroupCall(reference: conferenceCallData)
|
||||
self.conferenceCallDisposable = (conferenceCallSignal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] groupCall in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.ongoingContextStateDisposable?.dispose()
|
||||
|
||||
self.conferenceStateValue = .ready
|
||||
|
||||
let upgradedToConferenceCompletions = self.upgradedToConferenceCompletions.copyItems()
|
||||
self.upgradedToConferenceCompletions.removeAll()
|
||||
for f in upgradedToConferenceCompletions {
|
||||
f(conferenceCall)
|
||||
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(
|
||||
accountContext: self.context,
|
||||
audioSession: self.audioSession,
|
||||
callKitIntegration: self.callKitIntegration,
|
||||
getDeviceAccessData: self.getDeviceAccessData,
|
||||
initialCall: (EngineGroupCallDescription(
|
||||
id: groupCall.info.id,
|
||||
accessHash: groupCall.info.accessHash,
|
||||
title: nil,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
), conferenceCallData),
|
||||
internalId: CallSessionInternalId(),
|
||||
peerId: nil,
|
||||
isChannel: false,
|
||||
invite: nil,
|
||||
joinAsPeerId: nil,
|
||||
isStream: false,
|
||||
keyPair: keyPair,
|
||||
conferenceSourceId: self.internalId,
|
||||
isConference: true,
|
||||
sharedAudioContext: self.sharedAudioContext
|
||||
)
|
||||
self.conferenceCallImpl = conferenceCall
|
||||
conferenceCall.upgradedConferenceCall = self
|
||||
|
||||
conferenceCall.setConferenceInvitedPeers(self.pendingInviteToConferencePeerIds)
|
||||
for peerId in self.pendingInviteToConferencePeerIds {
|
||||
let _ = conferenceCall.invitePeer(peerId)
|
||||
}
|
||||
|
||||
conferenceCall.setIsMuted(action: self.isMutedValue ? .muted(isPushToTalkActive: false) : .unmuted)
|
||||
if let videoCapturer = self.videoCapturer {
|
||||
conferenceCall.requestVideo(capturer: videoCapturer)
|
||||
}
|
||||
|
||||
let waitForLocalVideo = self.videoCapturer != nil
|
||||
|
||||
let waitForRemotePeerId: EnginePeer.Id? = self.peerId
|
||||
var waitForRemoteVideo: EnginePeer.Id?
|
||||
if let callContextState = self.callContextState {
|
||||
switch callContextState.remoteVideoState {
|
||||
case .active, .paused:
|
||||
waitForRemoteVideo = self.peerId
|
||||
case .inactive:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.conferenceStateValue = .preparing
|
||||
|
||||
self.waitForConferenceCallReadyDisposable?.dispose()
|
||||
self.waitForConferenceCallReadyDisposable = (combineLatest(queue: .mainQueue(),
|
||||
conferenceCall.state,
|
||||
conferenceCall.members
|
||||
)
|
||||
|> filter { state, members in
|
||||
if state.networkState != .connected {
|
||||
return false
|
||||
}
|
||||
if let waitForRemotePeerId {
|
||||
var found = false
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.peer.id == waitForRemotePeerId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if waitForLocalVideo {
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.peer.id == state.myPeerId {
|
||||
if participant.videoDescription == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let waitForRemoteVideo {
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.peer.id == waitForRemoteVideo {
|
||||
if participant.videoDescription == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|> map { _, _ -> Void in
|
||||
return Void()
|
||||
}
|
||||
|> take(1)
|
||||
|> timeout(10.0, queue: .mainQueue(), alternate: .single(Void()))).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.ongoingContextStateDisposable?.dispose()
|
||||
|
||||
self.conferenceStateValue = .ready
|
||||
|
||||
let upgradedToConferenceCompletions = self.upgradedToConferenceCompletions.copyItems()
|
||||
self.upgradedToConferenceCompletions.removeAll()
|
||||
for f in upgradedToConferenceCompletions {
|
||||
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 {
|
||||
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)
|
||||
|
||||
if conferenceCallData != nil {
|
||||
@ -1150,8 +1182,13 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
var terminating = false
|
||||
if case .terminated = sessionState.state {
|
||||
terminating = true
|
||||
} else if case .dropping = sessionState.state {
|
||||
terminating = true
|
||||
} else if case let .dropping(reason) = sessionState.state {
|
||||
switch reason {
|
||||
case .ended(.switchedToConference):
|
||||
break
|
||||
default:
|
||||
terminating = true
|
||||
}
|
||||
}
|
||||
|
||||
if terminating, !wasTerminated {
|
||||
@ -1180,9 +1217,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
|
||||
var isConference = false
|
||||
if case let .active(_, _, _, _, _, _, _, _, conferenceCall, _) = sessionState.state {
|
||||
isConference = conferenceCall != nil
|
||||
} else if case .switchedToConference = sessionState.state {
|
||||
if case .switchedToConference = sessionState.state {
|
||||
isConference = true
|
||||
}
|
||||
if self.conferenceCallImpl != nil {
|
||||
@ -1191,7 +1226,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
if self.conferenceStateValue != nil {
|
||||
isConference = true
|
||||
}
|
||||
if self.isIncomingConference {
|
||||
if self.incomingConferenceSource != nil {
|
||||
isConference = true
|
||||
}
|
||||
|
||||
@ -1219,39 +1254,39 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
} else if let previous = previous {
|
||||
switch previous.state {
|
||||
case .accepting, .active, .dropping, .requesting:
|
||||
switch state.state {
|
||||
case .connecting:
|
||||
if case .requesting = previous.state {
|
||||
tone = .ringing
|
||||
} else {
|
||||
if !self.isVideo {
|
||||
tone = .connecting
|
||||
}
|
||||
case .accepting, .active, .dropping, .requesting:
|
||||
switch state.state {
|
||||
case .connecting:
|
||||
if case .requesting = previous.state {
|
||||
tone = .ringing
|
||||
} else {
|
||||
if !self.isVideo {
|
||||
tone = .connecting
|
||||
}
|
||||
}
|
||||
case .requesting(true):
|
||||
tone = .ringing
|
||||
case let .terminated(_, reason, _):
|
||||
if let reason = reason {
|
||||
switch reason {
|
||||
case let .ended(type):
|
||||
switch type {
|
||||
case .busy:
|
||||
tone = .busy
|
||||
case .hungUp, .missed:
|
||||
tone = .ended
|
||||
case .switchedToConference:
|
||||
tone = nil
|
||||
}
|
||||
case .requesting(true):
|
||||
tone = .ringing
|
||||
case let .terminated(_, reason, _):
|
||||
if let reason = reason {
|
||||
switch reason {
|
||||
case let .ended(type):
|
||||
switch type {
|
||||
case .busy:
|
||||
tone = .busy
|
||||
case .hungUp, .missed:
|
||||
tone = .ended
|
||||
case .switchedToConference:
|
||||
tone = nil
|
||||
}
|
||||
case .error:
|
||||
tone = .failed
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
case .error:
|
||||
tone = .failed
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if tone != self.currentTone {
|
||||
@ -1303,11 +1338,22 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
if strongSelf.isIncomingConference {
|
||||
if strongSelf.incomingConferenceSource != nil {
|
||||
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 {
|
||||
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
|
||||
}
|
||||
@ -1316,11 +1362,22 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if strongSelf.isIncomingConference {
|
||||
if strongSelf.incomingConferenceSource != nil {
|
||||
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 {
|
||||
strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId)
|
||||
}
|
||||
@ -1336,6 +1393,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
return .single(true)
|
||||
}
|
||||
let debugLogValue = Promise<String?>()
|
||||
|
||||
self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get())
|
||||
self.ongoingContext?.stop(debugLogValue: debugLogValue)
|
||||
|
||||
|
@ -348,7 +348,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
internalId: firstState.2.id,
|
||||
peerId: firstState.2.peerId,
|
||||
isOutgoing: false,
|
||||
isIncomingConference: firstState.2.isIncomingConference,
|
||||
incomingConferenceSource: firstState.2.conferenceSource,
|
||||
peer: EnginePeer(firstState.1),
|
||||
proxyServer: strongSelf.proxyServer,
|
||||
auxiliaryServers: [],
|
||||
@ -571,7 +571,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
|> mapToSignal { areVideoCallsAvailable -> Signal<CallSessionInternalId, NoError> in
|
||||
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(),
|
||||
@ -616,7 +616,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
internalId: internalId,
|
||||
peerId: peerId,
|
||||
isOutgoing: true,
|
||||
isIncomingConference: false,
|
||||
incomingConferenceSource: nil,
|
||||
peer: nil,
|
||||
proxyServer: strongSelf.proxyServer,
|
||||
auxiliaryServers: [],
|
||||
@ -847,8 +847,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
invite: nil,
|
||||
joinAsPeerId: nil,
|
||||
isStream: false,
|
||||
encryptionKey: nil,
|
||||
conferenceFromCallId: nil,
|
||||
keyPair: nil,
|
||||
conferenceSourceId: nil,
|
||||
isConference: false,
|
||||
sharedAudioContext: nil
|
||||
@ -1067,19 +1066,53 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
audioSession: self.audioSession,
|
||||
callKitIntegration: nil,
|
||||
getDeviceAccessData: self.getDeviceAccessData,
|
||||
initialCall: initialCall,
|
||||
initialCall: (initialCall, .id(id: initialCall.id, accessHash: initialCall.accessHash)),
|
||||
internalId: internalId,
|
||||
peerId: peerId,
|
||||
isChannel: isChannel,
|
||||
invite: invite,
|
||||
joinAsPeerId: joinAsPeerId,
|
||||
isStream: initialCall.isStream ?? false,
|
||||
encryptionKey: nil,
|
||||
conferenceFromCallId: nil,
|
||||
keyPair: nil,
|
||||
conferenceSourceId: nil,
|
||||
isConference: false,
|
||||
sharedAudioContext: nil
|
||||
)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import DeviceProximity
|
||||
import UndoUI
|
||||
import TemporaryCachedPeerDataManager
|
||||
import CallsEmoji
|
||||
import TdBinding
|
||||
|
||||
private extension GroupCallParticipantsContext.Participant {
|
||||
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) {
|
||||
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)
|
||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||
return .single(nil)
|
||||
@ -118,7 +119,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
peerId: peerId,
|
||||
myPeerId: account.peerId,
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
reference: .id(id: call.id, accessHash: call.accessHash),
|
||||
state: state,
|
||||
previousServiceState: nil
|
||||
)
|
||||
@ -147,7 +148,21 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
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,
|
||||
participantCount: state.totalCount,
|
||||
activeSpeakers: activeSpeakers,
|
||||
@ -537,11 +552,11 @@ private final class ScreencastInProcessIPCContext: ScreencastIPCContext {
|
||||
preferX264: false,
|
||||
logPath: "",
|
||||
onMutedSpeechActivityDetected: { _ in },
|
||||
encryptionKey: nil,
|
||||
isConference: self.isConference,
|
||||
audioIsActiveByDefault: true,
|
||||
isStream: false,
|
||||
sharedAudioDevice: nil
|
||||
sharedAudioDevice: nil,
|
||||
encryptionContext: nil
|
||||
)
|
||||
)
|
||||
self.screencastCallContext = screencastCallContext
|
||||
@ -613,10 +628,12 @@ private final class PendingConferenceInvitationContext {
|
||||
|
||||
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.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
|
||||
guard let self else {
|
||||
return
|
||||
@ -642,7 +659,7 @@ private final class PendingConferenceInvitationContext {
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -786,7 +803,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
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 peerId: EnginePeer.Id?
|
||||
private let isChannel: Bool
|
||||
@ -795,10 +812,28 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var ignorePreviousJoinAsPeerId: (PeerId, UInt32)?
|
||||
private var reconnectingAsPeer: EnginePeer?
|
||||
|
||||
public private(set) var callId: Int64?
|
||||
|
||||
public private(set) var hasVideo: Bool
|
||||
public private(set) var hasScreencast: 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 temporaryActivityTimestamp: Double?
|
||||
private var temporaryActivityRank: Int?
|
||||
@ -864,6 +899,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
return self.isNoiseSuppressionEnabledPromise.get()
|
||||
}
|
||||
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 let isVideoMutedDisposable = MetaDisposable()
|
||||
@ -1053,18 +1093,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var screencastStateDisposable: Disposable?
|
||||
|
||||
public let isStream: Bool
|
||||
private let encryptionKey: (key: Data, fingerprint: Int64)?
|
||||
private let sharedAudioContext: SharedCallAudioContext?
|
||||
|
||||
private let conferenceFromCallId: CallId?
|
||||
public let isConference: Bool
|
||||
public var encryptionKeyValue: Data? {
|
||||
if let key = self.encryptionKey?.key {
|
||||
return dataForEmojiRawKey(key)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private let conferenceSourceId: CallSessionInternalId?
|
||||
public var conferenceSource: CallSessionInternalId? {
|
||||
@ -1085,15 +1116,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
audioSession: ManagedAudioSession,
|
||||
callKitIntegration: CallKitIntegration?,
|
||||
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
|
||||
initialCall: EngineGroupCallDescription?,
|
||||
initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?,
|
||||
internalId: CallSessionInternalId,
|
||||
peerId: EnginePeer.Id?,
|
||||
isChannel: Bool,
|
||||
invite: String?,
|
||||
joinAsPeerId: EnginePeer.Id?,
|
||||
isStream: Bool,
|
||||
encryptionKey: (key: Data, fingerprint: Int64)?,
|
||||
conferenceFromCallId: CallId?,
|
||||
keyPair: TelegramKeyPair?,
|
||||
conferenceSourceId: CallSessionInternalId?,
|
||||
isConference: Bool,
|
||||
sharedAudioContext: SharedCallAudioContext?
|
||||
@ -1105,15 +1135,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.getDeviceAccessData = getDeviceAccessData
|
||||
|
||||
self.initialCall = initialCall
|
||||
self.callId = initialCall?.description.id
|
||||
|
||||
self.internalId = internalId
|
||||
self.peerId = peerId
|
||||
self.isChannel = isChannel
|
||||
self.invite = invite
|
||||
self.joinAsPeerId = joinAsPeerId ?? accountContext.account.peerId
|
||||
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.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
@ -1122,10 +1154,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.hasVideo = false
|
||||
self.hasScreencast = false
|
||||
self.isStream = isStream
|
||||
self.conferenceFromCallId = conferenceFromCallId
|
||||
self.conferenceSourceId = conferenceSourceId
|
||||
self.isConference = isConference
|
||||
self.encryptionKey = encryptionKey
|
||||
self.keyPair = keyPair
|
||||
|
||||
var sharedAudioContext = sharedAudioContext
|
||||
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, _, _, _, _, _, _):
|
||||
if isTerminated {
|
||||
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
|
||||
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)
|
||||
} else {
|
||||
@ -1447,6 +1511,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.peerUpdatesSubscription?.dispose()
|
||||
self.screencastStateDisposable?.dispose()
|
||||
self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate()
|
||||
self.e2ePoll0Timer?.invalidate()
|
||||
self.e2ePoll0Disposable?.dispose()
|
||||
self.e2ePoll1Timer?.invalidate()
|
||||
self.e2ePoll1Disposable?.dispose()
|
||||
}
|
||||
|
||||
private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) {
|
||||
@ -1471,7 +1539,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
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.participantsContextStateDisposable.set((combineLatest(queue: .mainQueue(),
|
||||
myPeerData,
|
||||
@ -1702,7 +1770,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
peerId: self.peerId,
|
||||
myPeerId: self.joinAsPeerId,
|
||||
id: callInfo.id,
|
||||
accessHash: callInfo.accessHash,
|
||||
reference: .id(id: callInfo.id, accessHash: callInfo.accessHash),
|
||||
state: GroupCallParticipantsContext.State(
|
||||
participants: [],
|
||||
nextParticipantsFetchOffset: nil,
|
||||
@ -1718,7 +1786,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
isVideoEnabled: callInfo.isVideoEnabled,
|
||||
unmutedVideoLimit: callInfo.unmutedVideoLimit,
|
||||
isStream: callInfo.isStream,
|
||||
upgradedPrivateCallId: callInfo.upgradedPrivateCallId,
|
||||
version: 0
|
||||
),
|
||||
previousServiceState: nil
|
||||
@ -1817,7 +1884,21 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.stateValue.subscribedToScheduled = state.subscribedToScheduled
|
||||
self.stateValue.scheduleTimestamp = self.isScheduledStarted ? nil : state.scheduleTimestamp
|
||||
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 {
|
||||
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
||||
id: callInfo.id,
|
||||
@ -1832,8 +1913,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||
isVideoEnabled: state.isVideoEnabled,
|
||||
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||
isStream: callInfo.isStream,
|
||||
upgradedPrivateCallId: callInfo.upgradedPrivateCallId
|
||||
isStream: callInfo.isStream
|
||||
))))
|
||||
|
||||
self.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
@ -1929,9 +2009,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
outgoingAudioBitrateKbit = Int32(value)
|
||||
}
|
||||
|
||||
var encryptionKey: Data?
|
||||
encryptionKey = self.encryptionKey?.key
|
||||
|
||||
let contextAudioSessionActive: Signal<Bool, NoError>
|
||||
if self.sharedAudioContext != nil {
|
||||
contextAudioSessionActive = .single(true)
|
||||
@ -1943,6 +2020,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
if self.isConference && self.conferenceSourceId != nil {
|
||||
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
|
||||
let disposable = MetaDisposable()
|
||||
@ -1969,7 +2066,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
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
|
||||
@ -2084,18 +2181,51 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
} else {
|
||||
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.requestDisposable.set((self.accountContext.engine.calls.joinGroupCall(
|
||||
peerId: self.peerId,
|
||||
joinAs: self.joinAsPeerId,
|
||||
callId: callInfo.id,
|
||||
accessHash: callInfo.accessHash,
|
||||
reference: reference,
|
||||
preferMuted: true,
|
||||
joinPayload: joinPayload,
|
||||
peerAdminIds: peerAdminIds,
|
||||
inviteHash: self.invite,
|
||||
keyFingerprint: self.encryptionKey?.fingerprint
|
||||
generateE2E: generateE2EData
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] joinCallResult in
|
||||
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.e2ePoll(subChainId: 0)
|
||||
self.e2ePoll(subChainId: 1)
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
@ -2394,11 +2527,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
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(
|
||||
peerId: self.peerId,
|
||||
myPeerId: self.joinAsPeerId,
|
||||
id: callInfo.id,
|
||||
accessHash: callInfo.accessHash,
|
||||
reference: reference,
|
||||
state: initialState,
|
||||
previousServiceState: serviceState
|
||||
)
|
||||
@ -2701,8 +2841,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||
isVideoEnabled: state.isVideoEnabled,
|
||||
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||
isStream: callInfo.isStream,
|
||||
upgradedPrivateCallId: callInfo.upgradedPrivateCallId
|
||||
isStream: callInfo.isStream
|
||||
))))
|
||||
|
||||
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() {
|
||||
if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext {
|
||||
groupCall.activateIncomingAudio()
|
||||
@ -2807,7 +3057,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
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
|
||||
extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: state.participants, into: &result)
|
||||
|
||||
@ -2913,7 +3163,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
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._canBeRemoved.set(.single(true))
|
||||
@ -3615,7 +3865,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let context = self.accountContext
|
||||
let currentCall: Signal<GroupCallInfo?, CallError>
|
||||
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
|
||||
return .generic
|
||||
}
|
||||
@ -3623,7 +3873,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
return summary?.info
|
||||
}
|
||||
} 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
|
||||
return .generic
|
||||
}
|
||||
@ -3662,7 +3912,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
@ -3673,7 +3933,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
public func invitePeer(_ peerId: PeerId) -> Bool {
|
||||
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
|
||||
}
|
||||
if conferenceInvitationContexts[peerId] != nil {
|
||||
@ -3685,7 +3952,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let invitationContext = PendingConferenceInvitationContext(
|
||||
callSessionManager: self.accountContext.account.callSessionManager,
|
||||
groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash),
|
||||
encryptionKey: encryptionKey.key,
|
||||
peerId: peerId,
|
||||
onStateUpdated: { state in
|
||||
onStateUpdated?(state)
|
||||
@ -3733,7 +3999,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return false*/
|
||||
} else {
|
||||
guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(where: { $0.id == peerId }) else {
|
||||
return false
|
||||
@ -3750,9 +4016,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
func setConferenceInvitedPeers(_ peerIds: [PeerId]) {
|
||||
self.invitedPeersValue = peerIds.map {
|
||||
//TODO:release
|
||||
/*self.invitedPeersValue = peerIds.map {
|
||||
PresentationGroupCallInvitedPeer(id: $0, state: .requesting)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public func removedPeer(_ peerId: PeerId) {
|
||||
@ -3771,6 +4038,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
public var inviteLinks: Signal<GroupCallInviteLinks?, NoError> {
|
||||
let engine = self.accountContext.engine
|
||||
let initialCall = self.initialCall
|
||||
let isConference = self.isConference
|
||||
|
||||
return self.state
|
||||
|> map { state -> PeerId in
|
||||
@ -3787,8 +4056,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
}
|
||||
|> mapToSignal { state in
|
||||
if let callInfo = state.callInfo {
|
||||
return engine.calls.groupCallInviteLinks(callId: callInfo.id, accessHash: callInfo.accessHash)
|
||||
if let callInfo = state.callInfo {
|
||||
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 {
|
||||
return .complete()
|
||||
}
|
||||
@ -3975,3 +4251,24 @@ private final class InProcessScreencastContext: ScreencastContext {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import TelegramAudio
|
||||
import LegacyComponents
|
||||
import TooltipUI
|
||||
import BlurredBackgroundComponent
|
||||
import CallsEmoji
|
||||
|
||||
extension VideoChatCall {
|
||||
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
|
||||
@ -262,6 +263,9 @@ final class VideoChatScreenComponent: Component {
|
||||
var speakingParticipantPeers: [EnginePeer] = []
|
||||
var visibleParticipants: Set<EnginePeer.Id> = Set()
|
||||
|
||||
var encryptionKeyEmoji: [String]?
|
||||
var encryptionKeyEmojiDisposable: Disposable?
|
||||
|
||||
let isPresentedValue = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
var applicationStateDisposable: Disposable?
|
||||
|
||||
@ -313,6 +317,7 @@ final class VideoChatScreenComponent: Component {
|
||||
self.updateAvatarDisposable.dispose()
|
||||
self.inviteDisposable.dispose()
|
||||
self.conferenceCallStateDisposable?.dispose()
|
||||
self.encryptionKeyEmojiDisposable?.dispose()
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
@ -647,9 +652,6 @@ final class VideoChatScreenComponent: Component {
|
||||
guard case let .group(groupCall) = self.currentCall else {
|
||||
return
|
||||
}
|
||||
guard let peerId = groupCall.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let formatSendTitle: (String) -> String = { string in
|
||||
var string = string
|
||||
@ -663,36 +665,89 @@ final class VideoChatScreenComponent: Component {
|
||||
return string
|
||||
}
|
||||
|
||||
let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
||||
return
|
||||
}
|
||||
guard let peer = self.peer else {
|
||||
return
|
||||
}
|
||||
guard let callState = self.callState else {
|
||||
return
|
||||
}
|
||||
var inviteLinks = inviteLinks
|
||||
|
||||
if case let .channel(peer) = peer, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
|
||||
let isMuted = defaultParticipantMuteState == .muted
|
||||
|
||||
if !isMuted {
|
||||
inviteLinks = GroupCallInviteLinks(listenerLink: inviteLinks.listenerLink, speakerLink: nil)
|
||||
if let peerId = groupCall.peerId {
|
||||
let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
|
||||
return
|
||||
}
|
||||
guard let peer = self.peer else {
|
||||
return
|
||||
}
|
||||
guard let callState = self.callState else {
|
||||
return
|
||||
}
|
||||
var inviteLinks = inviteLinks
|
||||
|
||||
if case let .channel(peer) = peer, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
|
||||
let isMuted = defaultParticipantMuteState == .muted
|
||||
|
||||
if !isMuted {
|
||||
inviteLinks = GroupCallInviteLinks(listenerLink: inviteLinks.listenerLink, speakerLink: nil)
|
||||
}
|
||||
}
|
||||
|
||||
var segmentedValues: [ShareControllerSegmentedValue]?
|
||||
if let speakerLink = inviteLinks.speakerLink {
|
||||
segmentedValues = [ShareControllerSegmentedValue(title: environment.strings.VoiceChat_InviteLink_Speaker, subject: .url(speakerLink), actionTitle: environment.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(environment.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
|
||||
}), ShareControllerSegmentedValue(title: environment.strings.VoiceChat_InviteLink_Listener, subject: .url(inviteLinks.listenerLink), actionTitle: environment.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(environment.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
|
||||
})]
|
||||
}
|
||||
let shareController = ShareController(context: groupCall.accountContext, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, 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))
|
||||
})
|
||||
} else if groupCall.isConference {
|
||||
guard let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
|
||||
var segmentedValues: [ShareControllerSegmentedValue]?
|
||||
if let speakerLink = inviteLinks.speakerLink {
|
||||
segmentedValues = [ShareControllerSegmentedValue(title: environment.strings.VoiceChat_InviteLink_Speaker, subject: .url(speakerLink), actionTitle: environment.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(environment.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
|
||||
}), ShareControllerSegmentedValue(title: environment.strings.VoiceChat_InviteLink_Listener, subject: .url(inviteLinks.listenerLink), actionTitle: environment.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(environment.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
|
||||
})]
|
||||
}
|
||||
let shareController = ShareController(context: groupCall.accountContext, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: environment.theme, forcedActionTitle: environment.strings.VoiceChat_CopyInviteLink)
|
||||
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
|
||||
@ -738,7 +793,7 @@ final class VideoChatScreenComponent: Component {
|
||||
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() {
|
||||
@ -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 = nil
|
||||
|
||||
@ -1961,7 +2038,7 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
var encryptionKeyFrame: CGRect?
|
||||
if component.initialCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
||||
if let encryptionKeyEmoji = self.encryptionKeyEmoji {
|
||||
navigationHeight -= 2.0
|
||||
let encryptionKey: ComponentView<Empty>
|
||||
var encryptionKeyTransition = transition
|
||||
@ -1978,7 +2055,7 @@ final class VideoChatScreenComponent: Component {
|
||||
component: AnyComponent(VideoChatEncryptionKeyComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
emoji: ["👌", "🧡", "🌹", "🤷"],
|
||||
emoji: encryptionKeyEmoji,
|
||||
isExpanded: self.isEncryptionKeyExpanded,
|
||||
tapAction: { [weak self] in
|
||||
guard let self else {
|
||||
@ -2283,10 +2360,17 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
if let encryptionKeyView = self.encryptionKey?.view, let encryptionKeyFrame {
|
||||
var encryptionKeyTransition = transition
|
||||
if encryptionKeyView.superview == nil {
|
||||
encryptionKeyTransition = encryptionKeyTransition.withAnimation(.none)
|
||||
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)
|
||||
|
||||
if self.isEncryptionKeyExpanded {
|
||||
|
@ -16,7 +16,7 @@ extension VideoChatScreenComponent.View {
|
||||
return
|
||||
}
|
||||
|
||||
if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
||||
/*if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
||||
guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
@ -39,7 +39,7 @@ extension VideoChatScreenComponent.View {
|
||||
})
|
||||
self.environment?.controller()?.present(controller, in: .window(.root), with: nil)
|
||||
return
|
||||
}
|
||||
}*/
|
||||
|
||||
if groupCall.isConference {
|
||||
var disablePeerIds: [EnginePeer.Id] = []
|
||||
|
@ -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
|
||||
let emojiKey = resolvedEmojiKey(data: encryptionKey)
|
||||
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))
|
||||
})))
|
||||
items.append(.separator)
|
||||
}
|
||||
}*/
|
||||
|
||||
if let (availableOutputs, currentOutput) = self.audioOutputState, availableOutputs.count > 1 {
|
||||
var currentOutputTitle = ""
|
||||
|
@ -77,7 +77,7 @@ public final class VoiceChatJoinScreen: ViewController {
|
||||
if let call = call {
|
||||
let peer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> 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
|
||||
if let peer = peer, let call = call {
|
||||
return (peer, call)
|
||||
|
@ -116,6 +116,7 @@ enum AccountStateMutationOperation {
|
||||
case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?)
|
||||
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
|
||||
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 UpdateAttachMenuBots
|
||||
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
|
||||
@ -403,6 +404,10 @@ struct AccountMutableState {
|
||||
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?) {
|
||||
self.addOperation(.UpdateAutoremoveTimeout(peer: peer, value: value))
|
||||
}
|
||||
@ -709,7 +714,7 @@ struct AccountMutableState {
|
||||
|
||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||
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
|
||||
case let .AddMessages(messages, location):
|
||||
for message in messages {
|
||||
@ -858,6 +863,7 @@ struct AccountReplayedFinalState {
|
||||
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
|
||||
let sentScheduledMessageIds: Set<MessageId>
|
||||
let reportMessageDelivery: Set<MessageId>
|
||||
let addedConferenceInvitationMessagesIds: [MessageId]
|
||||
}
|
||||
|
||||
struct AccountFinalStateEvents {
|
||||
@ -889,12 +895,13 @@ struct AccountFinalStateEvents {
|
||||
let updatedStarsBalance: [PeerId: StarsAmount]
|
||||
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
|
||||
let reportMessageDelivery: Set<MessageId>
|
||||
let addedConferenceInvitationMessagesIds: [MessageId]
|
||||
|
||||
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.addedReactionEvents = addedReactionEvents
|
||||
self.wasScheduledMessageIds = wasScheduledMessageIds
|
||||
@ -923,6 +930,7 @@ struct AccountFinalStateEvents {
|
||||
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
|
||||
self.sentScheduledMessageIds = sentScheduledMessageIds
|
||||
self.reportMessageDelivery = reportMessageDelivery
|
||||
self.addedConferenceInvitationMessagesIds = addedConferenceInvitationMessagesIds
|
||||
}
|
||||
|
||||
init(state: AccountReplayedFinalState) {
|
||||
@ -954,6 +962,7 @@ struct AccountFinalStateEvents {
|
||||
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
|
||||
self.sentScheduledMessageIds = state.sentScheduledMessageIds
|
||||
self.reportMessageDelivery = state.reportMessageDelivery
|
||||
self.addedConferenceInvitationMessagesIds = state.addedConferenceInvitationMessagesIds
|
||||
}
|
||||
|
||||
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
|
||||
@ -988,7 +997,38 @@ struct AccountFinalStateEvents {
|
||||
|
||||
var reportMessageDelivery = self.reportMessageDelivery
|
||||
reportMessageDelivery.formUnion(other.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)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -262,6 +262,10 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
||||
result.append(boostPeer.peerId)
|
||||
case let .messageActionPaymentRefunded(_, peer, _, _, _, _):
|
||||
result.append(peer.peerId)
|
||||
case let .messageActionConferenceCall(_, _, _, otherParticipants):
|
||||
if let otherParticipants {
|
||||
result.append(contentsOf: otherParticipants.map(\.peerId))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -80,6 +80,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
switch call {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: nil, duration: duration))
|
||||
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
|
||||
return nil
|
||||
}
|
||||
case let .messageActionInviteToGroupCall(call, userIds):
|
||||
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
|
||||
PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
}))
|
||||
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
|
||||
return nil
|
||||
}
|
||||
case let .messageActionSetMessagesTTL(_, period, autoSettingFrom):
|
||||
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 {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil))
|
||||
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
|
||||
return nil
|
||||
}
|
||||
case let .messageActionSetChatTheme(emoji):
|
||||
return TelegramMediaAction(action: .setChatTheme(emoji: emoji))
|
||||
@ -195,22 +201,28 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
|
||||
case let .messageActionPaidMessagesPrice(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) }) ?? []
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
extension PhoneCallDiscardReason {
|
||||
init(apiReason: Api.PhoneCallDiscardReason) {
|
||||
switch apiReason {
|
||||
case .phoneCallDiscardReasonBusy:
|
||||
self = .busy
|
||||
case .phoneCallDiscardReasonDisconnect:
|
||||
self = .disconnect
|
||||
case .phoneCallDiscardReasonHangup:
|
||||
self = .hangup
|
||||
case .phoneCallDiscardReasonMissed:
|
||||
self = .missed
|
||||
case .phoneCallDiscardReasonAllowGroupCall:
|
||||
self = .hangup
|
||||
case .phoneCallDiscardReasonBusy:
|
||||
self = .busy
|
||||
case .phoneCallDiscardReasonDisconnect:
|
||||
self = .disconnect
|
||||
case .phoneCallDiscardReasonHangup:
|
||||
self = .hangup
|
||||
case .phoneCallDiscardReasonMissed:
|
||||
self = .missed
|
||||
case .phoneCallDiscardReasonMigrateConferenceCall:
|
||||
self = .hangup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1629,10 +1629,16 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
switch call {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
updatedState.updateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version)
|
||||
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
|
||||
break
|
||||
}
|
||||
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.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):
|
||||
updatedState.updateAutoremoveTimeout(peer: peer, value: CachedPeerAutoremoveTimeout.Value(ttl))
|
||||
case let .updateLangPackTooLong(langCode):
|
||||
@ -3322,7 +3328,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
var currentAddQuickReplyMessages: OptimizeAddMessagesState?
|
||||
for operation in operations {
|
||||
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 {
|
||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||
}
|
||||
@ -3505,6 +3511,8 @@ func replayFinalState(
|
||||
var readInboxCloudMessageIds: [PeerId: Int32] = [:]
|
||||
|
||||
var addedOperationIncomingMessageIds: [MessageId] = []
|
||||
var addedConferenceInvitationMessagesIds: [MessageId] = []
|
||||
|
||||
for operation in finalState.state.operations {
|
||||
switch operation {
|
||||
case let .AddMessages(messages, location):
|
||||
@ -3526,6 +3534,10 @@ func replayFinalState(
|
||||
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) {
|
||||
wasOperationScheduledMessageIds.append(id)
|
||||
@ -4505,7 +4517,7 @@ func replayFinalState(
|
||||
}
|
||||
|
||||
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 canChange = (flags & (1 << 2)) != 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):
|
||||
let peerId = peer.peerId
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
@ -5420,6 +5437,7 @@ func replayFinalState(
|
||||
updatedStarsBalance: updatedStarsBalance,
|
||||
updatedStarsRevenueStatus: updatedStarsRevenueStatus,
|
||||
sentScheduledMessageIds: finalState.state.sentScheduledMessageIds,
|
||||
reportMessageDelivery: reportMessageDelivery
|
||||
reportMessageDelivery: reportMessageDelivery,
|
||||
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds
|
||||
)
|
||||
}
|
||||
|
@ -1145,6 +1145,9 @@ public final class AccountStateManager {
|
||||
if !events.reportMessageDelivery.isEmpty {
|
||||
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 {
|
||||
strongSelf.addIsContactUpdates(events.isContactUpdates)
|
||||
}
|
||||
@ -2203,7 +2206,7 @@ public final class AccountStateManager {
|
||||
switch update {
|
||||
case let .updatePhoneCall(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 {
|
||||
return nil
|
||||
}
|
||||
@ -2213,7 +2216,7 @@ public final class AccountStateManager {
|
||||
timestamp: date,
|
||||
peer: EnginePeer(peer),
|
||||
isVideo: (flags & (1 << 6)) != 0,
|
||||
isConference: conferenceCall != nil
|
||||
isConference: false
|
||||
)
|
||||
default:
|
||||
break
|
||||
|
@ -4,7 +4,6 @@ import MtProtoKit
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
|
||||
|
||||
private let minLayer: Int32 = 65
|
||||
|
||||
public enum CallSessionError: Equatable {
|
||||
@ -15,11 +14,11 @@ public enum CallSessionError: Equatable {
|
||||
case disconnected
|
||||
}
|
||||
|
||||
public enum CallSessionEndedType {
|
||||
public enum CallSessionEndedType: Equatable {
|
||||
case hungUp
|
||||
case busy
|
||||
case missed
|
||||
case switchedToConference
|
||||
case switchedToConference(slug: String)
|
||||
}
|
||||
|
||||
public enum CallSessionTerminationReason: Equatable {
|
||||
@ -65,41 +64,47 @@ public struct GroupCallReference: Equatable {
|
||||
}
|
||||
|
||||
extension GroupCallReference {
|
||||
init(_ apiGroupCall: Api.InputGroupCall) {
|
||||
init?(_ apiGroupCall: Api.InputGroupCall) {
|
||||
switch apiGroupCall {
|
||||
case let .inputGroupCall(id, 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 {
|
||||
case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String], conferenceCall: GroupCallReference?)
|
||||
case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
|
||||
case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String])
|
||||
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 requesting(a: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
|
||||
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?, conferenceCall: GroupCallReference?)
|
||||
case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, conferenceCall: GroupCallReference?, disposable: Disposable)
|
||||
case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool)
|
||||
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)
|
||||
case requesting(a: Data, disposable: Disposable)
|
||||
case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?)
|
||||
case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, disposable: Disposable)
|
||||
case 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(slug: String)
|
||||
case dropping(reason: CallSessionTerminationReason, disposable: Disposable)
|
||||
case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool)
|
||||
|
||||
var stableId: Int64? {
|
||||
switch self {
|
||||
case let .ringing(id, _, _, _, _, _):
|
||||
case let .ringing(id, _, _, _, _):
|
||||
return id
|
||||
case let .accepting(id, _, _, _, _, _):
|
||||
case let .accepting(id, _, _, _, _):
|
||||
return id
|
||||
case let .awaitingConfirmation(id, _, _, _, _):
|
||||
return id
|
||||
case .requesting:
|
||||
return nil
|
||||
case let .requested(id, _, _, _, _, _, _):
|
||||
case let .requested(id, _, _, _, _, _):
|
||||
return id
|
||||
case let .confirming(id, _, _, _, _, _, _):
|
||||
case let .confirming(id, _, _, _, _, _):
|
||||
return id
|
||||
case let .active(id, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, _, _, _, _, _, _, _, _, _, _):
|
||||
return id
|
||||
case .switchedToConference:
|
||||
return nil
|
||||
@ -138,7 +143,8 @@ public struct CallSessionRingingState: Equatable {
|
||||
public let peerId: PeerId
|
||||
public let isVideo: Bool
|
||||
public let isVideoPossible: Bool
|
||||
public let isIncomingConference: Bool
|
||||
public let conferenceSource: MessageId?
|
||||
public let otherParticipants: [EnginePeer]
|
||||
}
|
||||
|
||||
public enum DropCallReason {
|
||||
@ -166,9 +172,9 @@ public struct CallTerminationOptions: OptionSet {
|
||||
public enum CallSessionState {
|
||||
case ringing
|
||||
case accepting
|
||||
case requesting(ringing: Bool, conferenceCall: GroupCallReference?)
|
||||
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?, willSwitchToConference: Bool)
|
||||
case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)
|
||||
case requesting(ringing: Bool)
|
||||
case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool)
|
||||
case switchedToConference(slug: String)
|
||||
case dropping(reason: CallSessionTerminationReason)
|
||||
case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions)
|
||||
|
||||
@ -178,33 +184,37 @@ public enum CallSessionState {
|
||||
self = .ringing
|
||||
case .accepting, .awaitingConfirmation:
|
||||
self = .accepting
|
||||
case let .requesting(_, conferenceCall, _):
|
||||
self = .requesting(ringing: false, conferenceCall: conferenceCall)
|
||||
case let .confirming(_, _, _, _, _, conferenceCall, _):
|
||||
self = .requesting(ringing: true, conferenceCall: conferenceCall)
|
||||
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp, conferenceCall):
|
||||
self = .requesting(ringing: remoteConfirmationTimestamp != nil, conferenceCall: conferenceCall)
|
||||
case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall, willSwitchToConference):
|
||||
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)
|
||||
case .requesting:
|
||||
self = .requesting(ringing: false)
|
||||
case .confirming:
|
||||
self = .requesting(ringing: true)
|
||||
case let .requested(_, _, _, _, _, remoteConfirmationTimestamp):
|
||||
self = .requesting(ringing: remoteConfirmationTimestamp != nil)
|
||||
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)
|
||||
case let .dropping(reason, _):
|
||||
self = .dropping(reason: reason)
|
||||
case let .terminated(id, accessHash, reason, reportRating, sendDebugLogs):
|
||||
var options = CallTerminationOptions()
|
||||
if reportRating {
|
||||
options.insert(.reportRating)
|
||||
}
|
||||
if sendDebugLogs {
|
||||
options.insert(.sendDebugLogs)
|
||||
}
|
||||
let callId: CallId?
|
||||
if let id = id, let accessHash = accessHash {
|
||||
callId = CallId(id: id, accessHash: accessHash)
|
||||
if case let .ended(endedReason) = reason, case let .switchedToConference(slug) = endedReason {
|
||||
self = .switchedToConference(slug: slug)
|
||||
} else {
|
||||
callId = nil
|
||||
var options = CallTerminationOptions()
|
||||
if reportRating {
|
||||
options.insert(.reportRating)
|
||||
}
|
||||
if sendDebugLogs {
|
||||
options.insert(.sendDebugLogs)
|
||||
}
|
||||
let callId: CallId?
|
||||
if let id = id, let accessHash = accessHash {
|
||||
callId = CallId(id: id, accessHash: accessHash)
|
||||
} else {
|
||||
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 isVideoPossible: Bool
|
||||
|
||||
init(
|
||||
public init(
|
||||
id: CallSessionInternalId,
|
||||
stableId: Int64?,
|
||||
isOutgoing: Bool,
|
||||
@ -336,17 +346,15 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api.
|
||||
private final class CallSessionContext {
|
||||
let peerId: PeerId
|
||||
let isOutgoing: Bool
|
||||
let isIncomingConference: Bool
|
||||
var type: CallSession.CallType
|
||||
var isVideoPossible: Bool
|
||||
let pendingConference: (conference: GroupCallReference, encryptionKey: Data)?
|
||||
var state: CallSessionInternalState
|
||||
let subscribers = Bag<(CallSession) -> Void>()
|
||||
var signalingReceiver: (([Data]) -> Void)?
|
||||
|
||||
let signalingDisposables = DisposableSet()
|
||||
var createConferenceCallDisposable: Disposable?
|
||||
let acknowledgeIncomingCallDisposable = MetaDisposable()
|
||||
var createConferenceCallDisposable: Disposable?
|
||||
|
||||
var isEmpty: Bool {
|
||||
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.isOutgoing = isOutgoing
|
||||
self.isIncomingConference = isIncomingConference
|
||||
self.type = type
|
||||
self.isVideoPossible = isVideoPossible
|
||||
self.pendingConference = pendingConference
|
||||
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]? {
|
||||
let filteredVersions = localVersions.map({ $0.version }).filter(remoteVersions.contains)
|
||||
if filteredVersions.isEmpty {
|
||||
@ -406,10 +479,13 @@ private final class CallSessionManagerContext {
|
||||
private var futureContextSubscribers: [CallSessionInternalId: Bag<(CallSession) -> Void>] = [:]
|
||||
private var contextIdByStableId: [CallSessionStableId: CallSessionInternalId] = [:]
|
||||
|
||||
private var incomingConferenceInvitationContexts: [MessageId: IncomingConferenceInvitationContext] = [:]
|
||||
|
||||
private var enqueuedSignalingData: [Int64: [Data]] = [:]
|
||||
|
||||
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) {
|
||||
self.queue = queue
|
||||
self.postbox = postbox
|
||||
@ -423,6 +499,7 @@ private final class CallSessionManagerContext {
|
||||
deinit {
|
||||
assert(self.queue.isCurrent())
|
||||
self.disposables.dispose()
|
||||
self.rejectConferenceInvitationDisposables.dispose()
|
||||
}
|
||||
|
||||
func updateVersions(versions: [CallSessionManagerImplementationVersion]) {
|
||||
@ -556,7 +633,20 @@ private final class CallSessionManagerContext {
|
||||
peerId: context.peerId,
|
||||
isVideo: context.type == .video,
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -594,13 +684,10 @@ private final class CallSessionManagerContext {
|
||||
let b = Data(bytesNoCopy: bBytes, count: 256, deallocator: .free)
|
||||
|
||||
if randomStatus == 0 {
|
||||
var isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
|
||||
//#if DEBUG
|
||||
isVideoPossible = true
|
||||
//#endif
|
||||
let isVideoPossible = true
|
||||
|
||||
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
|
||||
let queue = self.queue
|
||||
|
||||
@ -632,38 +719,62 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
|
||||
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] {
|
||||
var dropData: (CallSessionStableId, Int64, DropCallSessionReason)?
|
||||
var wasRinging = false
|
||||
let isVideo = context.type == .video
|
||||
switch context.state {
|
||||
case let .ringing(id, accessHash, _, _, _, _):
|
||||
case let .ringing(id, accessHash, _, _, _):
|
||||
wasRinging = true
|
||||
let internalReason: DropCallSessionReason
|
||||
switch reason {
|
||||
case .busy:
|
||||
internalReason = .busy
|
||||
case .hangUp:
|
||||
internalReason = .hangUp(0)
|
||||
case .disconnect:
|
||||
internalReason = .disconnect
|
||||
case .missed:
|
||||
internalReason = .missed
|
||||
case .busy:
|
||||
internalReason = .busy
|
||||
case .hangUp:
|
||||
internalReason = .hangUp(0)
|
||||
case .disconnect:
|
||||
internalReason = .disconnect
|
||||
case .missed:
|
||||
internalReason = .missed
|
||||
}
|
||||
dropData = (id, accessHash, internalReason)
|
||||
case let .accepting(id, accessHash, _, _, _, disposable):
|
||||
case let .accepting(id, accessHash, _, _, disposable):
|
||||
dropData = (id, accessHash, .abort)
|
||||
disposable.dispose()
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _):
|
||||
let duration = max(0, Int32(CFAbsoluteTimeGetCurrent()) - beginTimestamp)
|
||||
let internalReason: DropCallSessionReason
|
||||
switch reason {
|
||||
case .busy, .hangUp:
|
||||
internalReason = .hangUp(duration)
|
||||
case .disconnect:
|
||||
internalReason = .disconnect
|
||||
case .missed:
|
||||
internalReason = .missed
|
||||
case .busy, .hangUp:
|
||||
internalReason = .hangUp(duration)
|
||||
case .disconnect:
|
||||
internalReason = .disconnect
|
||||
case .missed:
|
||||
internalReason = .missed
|
||||
}
|
||||
dropData = (id, accessHash, internalReason)
|
||||
case .switchedToConference:
|
||||
@ -672,21 +783,21 @@ private final class CallSessionManagerContext {
|
||||
break
|
||||
case let .awaitingConfirmation(id, accessHash, _, _, _):
|
||||
dropData = (id, accessHash, .abort)
|
||||
case let .confirming(id, accessHash, _, _, _, _, disposable):
|
||||
case let .confirming(id, accessHash, _, _, _, disposable):
|
||||
disposable.dispose()
|
||||
dropData = (id, accessHash, .abort)
|
||||
case let .requested(id, accessHash, _, _, _, _, _):
|
||||
case let .requested(id, accessHash, _, _, _, _):
|
||||
let internalReason: DropCallSessionReason
|
||||
switch reason {
|
||||
case .busy, .hangUp:
|
||||
internalReason = .missed
|
||||
case .disconnect:
|
||||
internalReason = .disconnect
|
||||
case .missed:
|
||||
internalReason = .missed
|
||||
case .busy, .hangUp:
|
||||
internalReason = .missed
|
||||
case .disconnect:
|
||||
internalReason = .disconnect
|
||||
case .missed:
|
||||
internalReason = .missed
|
||||
}
|
||||
dropData = (id, accessHash, internalReason)
|
||||
case let .requesting(_, _, disposable):
|
||||
case let .requesting(_, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated(id: nil, accessHash: nil, reason: .ended(.hungUp), reportRating: false, sendDebugLogs: false)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
@ -709,8 +820,8 @@ private final class CallSessionManagerContext {
|
||||
mappedReason = .ended(.hungUp)
|
||||
case .missed:
|
||||
mappedReason = .ended(.missed)
|
||||
case .switchToConference:
|
||||
mappedReason = .ended(.switchedToConference)
|
||||
case let .switchToConference(slug):
|
||||
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)
|
||||
|> 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] {
|
||||
var dropData: (CallSessionStableId, Int64)?
|
||||
let isVideo = context.type == .video
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
dropData = (id, accessHash)
|
||||
default:
|
||||
break
|
||||
@ -764,11 +875,11 @@ private final class CallSessionManagerContext {
|
||||
|
||||
if let (id, accessHash) = dropData {
|
||||
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
|
||||
if let strongSelf = self {
|
||||
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)
|
||||
if context.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: internalId)
|
||||
@ -793,9 +904,9 @@ private final class CallSessionManagerContext {
|
||||
func accept(internalId: CallSessionInternalId) {
|
||||
if let context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case let .ringing(id, accessHash, gAHash, b, _, conferenceCall):
|
||||
case let .ringing(id, accessHash, gAHash, b, _):
|
||||
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 case .accepting = context.state {
|
||||
switch result {
|
||||
@ -806,9 +917,9 @@ private final class CallSessionManagerContext {
|
||||
case let .waiting(config):
|
||||
context.state = .awaitingConfirmation(id: id, accessHash: accessHash, gAHash: gAHash, b: b, config: config)
|
||||
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) {
|
||||
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)
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -829,7 +940,7 @@ private final class CallSessionManagerContext {
|
||||
func sendSignalingData(internalId: CallSessionInternalId, data: Data) {
|
||||
if let context = self.contexts[internalId] {
|
||||
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())
|
||||
default:
|
||||
break
|
||||
@ -845,33 +956,29 @@ private final class CallSessionManagerContext {
|
||||
|
||||
var idAndAccessHash: (id: Int64, accessHash: Int64)?
|
||||
switch context.state {
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _):
|
||||
if conferenceCall != nil {
|
||||
return
|
||||
}
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
idAndAccessHash = (id, accessHash)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let (id, accessHash) = idAndAccessHash {
|
||||
context.createConferenceCallDisposable = (createConferenceCall(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, callId: CallId(id: id, accessHash: accessHash))
|
||||
if idAndAccessHash != nil {
|
||||
context.createConferenceCallDisposable = (_internal_createConferenceCall(
|
||||
postbox: self.postbox,
|
||||
network: self.network,
|
||||
accountPeerId: self.accountPeerId
|
||||
)
|
||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.dropToConference(internalId: internalId, slug: result.slug)
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let context = self.contexts[internalId] else {
|
||||
return
|
||||
}
|
||||
if let result {
|
||||
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
|
||||
}
|
||||
}
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -889,7 +996,7 @@ private final class CallSessionManagerContext {
|
||||
switch call {
|
||||
case .phoneCallEmpty:
|
||||
break
|
||||
case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol, conferenceCall):
|
||||
case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol):
|
||||
let remoteVersions: [String]
|
||||
switch remoteProtocol {
|
||||
case let .phoneCallProtocol(_, _, _, versions):
|
||||
@ -903,7 +1010,7 @@ private final class CallSessionManagerContext {
|
||||
|
||||
if let context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case let .requested(_, accessHash, a, gA, config, _, _):
|
||||
case let .requested(_, accessHash, a, gA, config, _):
|
||||
let p = config.p.makeData()
|
||||
if !MTCheckIsSafeGAOrB(self.network.encryptionProvider, gA, p) {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -928,14 +1035,10 @@ private final class CallSessionManagerContext {
|
||||
|
||||
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 updatedCall = updatedCall {
|
||||
strongSelf.updateSession(updatedCall, completion: { _ in })
|
||||
|
||||
if let pendingConference = context.pendingConference {
|
||||
strongSelf.dropToConference(internalId: internalId, encryptedGroupKey: pendingConference.encryptionKey)
|
||||
}
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
}
|
||||
@ -949,8 +1052,7 @@ private final class CallSessionManagerContext {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
case let .phoneCallDiscarded(flags, id, reason, _, conferenceCall):
|
||||
let _ = conferenceCall
|
||||
case let .phoneCallDiscarded(flags, id, reason, _):
|
||||
let reportRating = (flags & (1 << 2)) != 0
|
||||
let sendDebugLogs = (flags & (1 << 3)) != 0
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
@ -963,47 +1065,39 @@ private final class CallSessionManagerContext {
|
||||
case .phoneCallDiscardReasonDisconnect:
|
||||
parsedReason = .error(.disconnected)
|
||||
case .phoneCallDiscardReasonHangup:
|
||||
if context.pendingConference != nil {
|
||||
parsedReason = .ended(.switchedToConference)
|
||||
} else {
|
||||
parsedReason = .ended(.hungUp)
|
||||
}
|
||||
parsedReason = .ended(.hungUp)
|
||||
case .phoneCallDiscardReasonMissed:
|
||||
parsedReason = .ended(.missed)
|
||||
case .phoneCallDiscardReasonAllowGroupCall:
|
||||
parsedReason = .ended(.switchedToConference)
|
||||
case let .phoneCallDiscardReasonMigrateConferenceCall(slug):
|
||||
parsedReason = .ended(.switchedToConference(slug: slug))
|
||||
}
|
||||
} else {
|
||||
parsedReason = .ended(.hungUp)
|
||||
}
|
||||
|
||||
switch context.state {
|
||||
case let .accepting(id, accessHash, _, _, _, disposable):
|
||||
case let .accepting(id, accessHash, _, _, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall, _):
|
||||
if let conferenceCall, case let .phoneCallDiscardReasonAllowGroupCall(encryptedGroupKey) = reason {
|
||||
context.state = .switchedToConference(key: encryptedGroupKey.makeData(), keyVisualHash: MTSha256(encryptedGroupKey.makeData()), conferenceCall: conferenceCall)
|
||||
} else {
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
}
|
||||
case let .active(id, accessHash, _, _, _, _, _, _, _, _, _):
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .awaitingConfirmation(id, accessHash, _, _, _):
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .requested(id, accessHash, _, _, _, _, _):
|
||||
case let .requested(id, accessHash, _, _, _, _):
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .confirming(id, accessHash, _, _, _, _, disposable):
|
||||
case let .confirming(id, accessHash, _, _, _, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .requesting(_, _, disposable):
|
||||
case let .requesting(_, disposable):
|
||||
disposable.dispose()
|
||||
context.state = .terminated(id: nil, accessHash: nil, reason: parsedReason, reportRating: false, sendDebugLogs: false)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .ringing(id, accessHash, _, _, _, _):
|
||||
case let .ringing(id, accessHash, _, _, _):
|
||||
context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||
self.ringingStatesUpdated()
|
||||
self.contextUpdated(internalId: internalId)
|
||||
@ -1014,15 +1108,15 @@ private final class CallSessionManagerContext {
|
||||
//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
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
if let context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case .accepting, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference:
|
||||
break
|
||||
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)
|
||||
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)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
case let .awaitingConfirmation(_, accessHash, gAHash, b, config):
|
||||
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) })
|
||||
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)
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -1053,7 +1147,7 @@ private final class CallSessionManagerContext {
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
}
|
||||
case let .confirming(id, accessHash, key, keyId, keyVisualHash, _, _):
|
||||
case let .confirming(id, accessHash, key, keyId, keyVisualHash, _):
|
||||
switch callProtocol {
|
||||
case let .phoneCallProtocol(_, _, maxLayer, versions):
|
||||
if !versions.isEmpty {
|
||||
@ -1068,7 +1162,7 @@ private final class CallSessionManagerContext {
|
||||
let isVideoPossible = self.videoVersions().contains(where: { versions.contains($0) })
|
||||
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)
|
||||
} else {
|
||||
self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
@ -1079,7 +1173,7 @@ private final class CallSessionManagerContext {
|
||||
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 versions: [String]
|
||||
switch requestedProtocol {
|
||||
@ -1087,7 +1181,7 @@ private final class CallSessionManagerContext {
|
||||
versions = libraryVersions
|
||||
}
|
||||
if self.contextIdByStableId[id] == nil {
|
||||
let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions, isVideo: isVideo, 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 {
|
||||
var resultRingingStateValue: CallSessionRingingState?
|
||||
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 context = self.contexts[internalId] {
|
||||
switch context.state {
|
||||
case let .requested(id, accessHash, a, gA, config, remoteConfirmationTimestamp, _):
|
||||
case let .requested(id, accessHash, a, gA, config, remoteConfirmationTimestamp):
|
||||
if let receiveDate = receiveDate, remoteConfirmationTimestamp == nil {
|
||||
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate, conferenceCall: conferenceCall.flatMap(GroupCallReference.init))
|
||||
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate)
|
||||
self.contextUpdated(internalId: internalId)
|
||||
}
|
||||
default:
|
||||
@ -1144,6 +1238,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)? {
|
||||
var key = MTExp(self.network.encryptionProvider, gA, b, config.p.makeData())!
|
||||
@ -1173,17 +1290,17 @@ private final class CallSessionManagerContext {
|
||||
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 randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
|
||||
if randomStatus == 0 {
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, 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 case .requesting = context.state {
|
||||
switch result {
|
||||
case let .success(id, accessHash, config, gA, remoteConfirmationTimestamp):
|
||||
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp, conferenceCall: conferenceCall?.conference)
|
||||
context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp)
|
||||
strongSelf.contextIdByStableId[id] = internalId
|
||||
strongSelf.contextUpdated(internalId: internalId)
|
||||
strongSelf.deliverCallSignalingData(id: id)
|
||||
@ -1253,6 +1370,12 @@ public final class CallSessionManager {
|
||||
context.addCallSignalingData(id: id, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
func addConferenceInvitationMessages(ids: [MessageId]) {
|
||||
self.withContext { context in
|
||||
context.addConferenceInvitationMessages(ids: ids)
|
||||
}
|
||||
}
|
||||
|
||||
public func drop(internalId: CallSessionInternalId, reason: DropCallReason, debugLog: Signal<String?, NoError>) {
|
||||
self.withContext { context in
|
||||
@ -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
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
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.putCompletion()
|
||||
}
|
||||
@ -1354,7 +1477,7 @@ public final class CallSessionManager {
|
||||
|
||||
private enum AcceptedCall {
|
||||
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 {
|
||||
@ -1394,7 +1517,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
|
||||
return .failed
|
||||
case .phoneCallWaiting:
|
||||
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 {
|
||||
switch callProtocol{
|
||||
case let .phoneCallProtocol(_, _, maxLayer, versions):
|
||||
@ -1407,7 +1530,7 @@ private func acceptCallSession(accountPeerId: PeerId, postbox: Postbox, network:
|
||||
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 {
|
||||
return .failed
|
||||
}
|
||||
@ -1430,7 +1553,7 @@ private enum RequestCallSessionResult {
|
||||
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)
|
||||
|> mapToSignal { config -> 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 {
|
||||
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
|
||||
switch result {
|
||||
case let .phoneCall(phoneCall, _):
|
||||
switch phoneCall {
|
||||
case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _, _):
|
||||
case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _):
|
||||
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil)
|
||||
case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate, _):
|
||||
case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate):
|
||||
return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate)
|
||||
default:
|
||||
return .failed(.generic)
|
||||
@ -1514,7 +1634,7 @@ private enum DropCallSessionReason {
|
||||
case busy
|
||||
case disconnect
|
||||
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> {
|
||||
@ -1532,8 +1652,8 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
|
||||
mappedReason = .phoneCallDiscardReasonDisconnect
|
||||
case .missed:
|
||||
mappedReason = .phoneCallDiscardReasonMissed
|
||||
case let .switchToConference(encryptedGroupKey):
|
||||
mappedReason = .phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer(data: encryptedGroupKey))
|
||||
case let .switchToConference(slug):
|
||||
mappedReason = .phoneCallDiscardReasonMigrateConferenceCall(slug: slug)
|
||||
}
|
||||
|
||||
var callFlags: Int32 = 0
|
||||
@ -1556,7 +1676,7 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
|
||||
switch update {
|
||||
case .updatePhoneCall(let phoneCall):
|
||||
switch phoneCall {
|
||||
case let .phoneCallDiscarded(flags, _, _, _, _):
|
||||
case let .phoneCallDiscarded(flags, _, _, _):
|
||||
reportRating = (flags & (1 << 2)) != 0
|
||||
sendDebugLogs = (flags & (1 << 3)) != 0
|
||||
default:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 201
|
||||
return 202
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
@ -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 paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64)
|
||||
case conferenceCall(callId: Int64, duration: Int32?, otherParticipants: [PeerId])
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
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))
|
||||
case 47:
|
||||
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:
|
||||
self = .unknown
|
||||
}
|
||||
@ -639,6 +642,15 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
case let .paidMessagesPriceEdited(stars):
|
||||
encoder.encodeInt32(47, forKey: "_rawValue")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,8 +33,8 @@ public extension TelegramEngine {
|
||||
return _internal_saveCompleteCallDebugLog(account: self.account, callId: callId, logPath: logPath)
|
||||
}
|
||||
|
||||
public func getCurrentGroupCall(callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||
return _internal_getCurrentGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId)
|
||||
public func getCurrentGroupCall(reference: InternalGroupCallReference, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||
return _internal_getCurrentGroupCall(account: self.account, reference: reference, peerId: peerId)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
public func toggleScheduledGroupCallSubscription(peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> {
|
||||
return _internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash, subscribe: subscribe)
|
||||
public func toggleScheduledGroupCallSubscription(peerId: PeerId, reference: InternalGroupCallReference, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> {
|
||||
return _internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, reference: reference, subscribe: subscribe)
|
||||
}
|
||||
|
||||
public func updateGroupCallJoinAsPeer(peerId: PeerId, joinAs: PeerId) -> Signal<Never, UpdateGroupCallJoinAsPeerError> {
|
||||
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> {
|
||||
return _internal_getGroupCallParticipants(account: self.account, callId: callId, accessHash: accessHash, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending)
|
||||
public func getGroupCallParticipants(reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
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> {
|
||||
return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, accessHash: accessHash, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, keyFingerprint: keyFingerprint)
|
||||
public func 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, reference: reference, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
public func groupCallInviteLinks(callId: Int64, accessHash: Int64) -> Signal<GroupCallInviteLinks?, NoError> {
|
||||
return _internal_groupCallInviteLinks(account: self.account, callId: callId, accessHash: accessHash)
|
||||
public func groupCallInviteLinks(reference: InternalGroupCallReference, isConference: Bool) -> Signal<GroupCallInviteLinks?, NoError> {
|
||||
return _internal_groupCallInviteLinks(account: self.account, reference: reference, isConference: isConference)
|
||||
}
|
||||
|
||||
public func editGroupCallTitle(callId: Int64, accessHash: Int64, title: String) -> Signal<Never, EditGroupCallTitleError> {
|
||||
return _internal_editGroupCallTitle(account: self.account, callId: callId, accessHash: accessHash, title: title)
|
||||
}
|
||||
|
||||
/*public func groupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<[FoundPeer], NoError> {
|
||||
return _internal_groupCallDisplayAsAvailablePeers(network: self.account.network, postbox: self.account.postbox, peerId: peerId)
|
||||
}*/
|
||||
public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
|
||||
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> {
|
||||
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)
|
||||
}
|
||||
|
||||
public func groupCall(peerId: PeerId?, myPeerId: PeerId, id: Int64, accessHash: Int64, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?) -> GroupCallParticipantsContext {
|
||||
return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, accessHash: accessHash, state: state, previousServiceState: previousServiceState)
|
||||
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, reference: reference, state: state, previousServiceState: previousServiceState)
|
||||
}
|
||||
|
||||
public func serverTime() -> Signal<Int64, NoError> {
|
||||
|
@ -143,11 +143,15 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
|
||||
}
|
||||
|
||||
public final class JoinCallLinkInformation {
|
||||
public let id: Int64
|
||||
public let accessHash: Int64
|
||||
public let inviter: EnginePeer?
|
||||
public let members: [EnginePeer]
|
||||
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.members = members
|
||||
self.totalMemberCount = totalMemberCount
|
||||
@ -155,29 +159,41 @@ public final class JoinCallLinkInformation {
|
||||
}
|
||||
|
||||
func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
|
||||
//TODO:release
|
||||
|
||||
let invite: Signal<Api.ChatInvite?, JoinLinkInfoError> = account.network.request(Api.functions.messages.checkChatInvite(hash: hash), automaticFloodWait: false)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.ChatInvite?, JoinLinkInfoError> in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.flood)
|
||||
} else {
|
||||
return .single(nil)
|
||||
return _internal_getCurrentGroupCall(account: account, reference: .link(slug: hash))
|
||||
|> mapError { error -> JoinLinkInfoError in
|
||||
switch error {
|
||||
case .generic:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|
||||
return invite
|
||||
|> 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)
|
||||
}
|
||||
} else {
|
||||
|> 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))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -841,6 +841,10 @@ public extension TelegramEngine {
|
||||
public func joinCallLinkInformation(_ hash: String) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
|
||||
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> {
|
||||
return _internal_updatePeerTitle(account: self.account, peerId: peerId, title: title)
|
||||
|
@ -534,6 +534,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
switch inputCall {
|
||||
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)
|
||||
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -787,6 +789,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
switch inputCall {
|
||||
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)
|
||||
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -616,6 +616,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
}
|
||||
}
|
||||
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):
|
||||
if let scheduleDate = scheduleDate {
|
||||
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
|
@ -175,7 +175,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
break
|
||||
case .ignore:
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +151,7 @@ public struct ChatMessageBubbleContentTapAction {
|
||||
case wallpaper
|
||||
case theme
|
||||
case call(peerId: PeerId, isVideo: Bool)
|
||||
case conferenceCall(message: Message)
|
||||
case openMessage
|
||||
case timecode(Double, String)
|
||||
case tooltip(String, ASDisplayNode?, CGRect?)
|
||||
|
@ -213,6 +213,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
isAction = true
|
||||
if case .phoneCall = action.action {
|
||||
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 {
|
||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else if case .giftStars = action.action {
|
||||
@ -1251,7 +1253,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
break
|
||||
case .ignore:
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1347,7 +1349,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return false
|
||||
}
|
||||
else if let media = media as? TelegramMediaAction {
|
||||
if case .phoneCall(_, _, _, _) = media.action {
|
||||
if case .phoneCall = media.action {
|
||||
} else if case .conferenceCall = media.action {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -2723,6 +2726,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
switch action.action {
|
||||
case .phoneCall:
|
||||
break
|
||||
case .conferenceCall:
|
||||
break
|
||||
default:
|
||||
centerAligned = true
|
||||
}
|
||||
@ -5034,6 +5039,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return .optionalAction({
|
||||
self.item?.controllerInteraction.callPeer(peerId, isVideo)
|
||||
})
|
||||
case let .conferenceCall(message):
|
||||
return .optionalAction({
|
||||
self.item?.controllerInteraction.openConferenceCall(message)
|
||||
})
|
||||
case .openMessage:
|
||||
if let item = self.item {
|
||||
if let type = self.backgroundNode.type, case .none = type {
|
||||
@ -5221,6 +5230,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
break
|
||||
case .call:
|
||||
break
|
||||
case .conferenceCall:
|
||||
break
|
||||
case .openMessage:
|
||||
break
|
||||
case let .timecode(timecode, text):
|
||||
@ -5516,6 +5527,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction {
|
||||
if case .phoneCall = action.action {
|
||||
} else if case .conferenceCall = action.action {
|
||||
} else {
|
||||
canHaveSelection = false
|
||||
break
|
||||
|
@ -123,6 +123,12 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action {
|
||||
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)
|
||||
@ -268,6 +277,8 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
for media in item.message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .phoneCall(_, _, _, isVideoValue) = action.action {
|
||||
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))
|
||||
|
@ -1603,8 +1603,18 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
} else {
|
||||
onlyFullSizeVideoThumbnail = isSendingUpdated
|
||||
let codecConfiguration = HLSCodecConfiguration(context: context)
|
||||
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
|
||||
return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: true)
|
||||
|
@ -590,6 +590,7 @@ public final class ChatMessageAccessibilityData {
|
||||
}
|
||||
else if let media = media as? TelegramMediaAction {
|
||||
if case .phoneCall = media.action {
|
||||
} else if case .conferenceCall = media.action {
|
||||
} else {
|
||||
canReply = false
|
||||
}
|
||||
|
@ -264,6 +264,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self?.openPeer(peer: EnginePeer(peer))
|
||||
}, callPeer: { peerId, isVideo in
|
||||
self?.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}, openConferenceCall: { message in
|
||||
self?.controllerInteraction?.openConferenceCall(message)
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in
|
||||
if let strongSelf = self {
|
||||
@ -372,7 +374,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
return self?.getNavigationController()
|
||||
}, chatControllerNode: { [weak self] in
|
||||
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 {
|
||||
switch action {
|
||||
case let .url(url):
|
||||
@ -1383,7 +1386,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
|
||||
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):
|
||||
|
@ -428,7 +428,8 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
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
|
||||
return .none
|
||||
}, canSendMessages: {
|
||||
|
@ -208,6 +208,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
public let chatControllerNode: () -> ASDisplayNode?
|
||||
public let presentGlobalOverlayController: (ViewController, Any?) -> Void
|
||||
public let callPeer: (PeerId, Bool) -> Void
|
||||
public let openConferenceCall: (Message) -> Void
|
||||
public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void
|
||||
public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void
|
||||
public let openSearch: () -> Void
|
||||
@ -367,6 +368,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
chatControllerNode: @escaping () -> ASDisplayNode?,
|
||||
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
|
||||
callPeer: @escaping (PeerId, Bool) -> Void,
|
||||
openConferenceCall: @escaping (Message) -> Void,
|
||||
longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void,
|
||||
openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void,
|
||||
openSearch: @escaping () -> Void,
|
||||
@ -482,6 +484,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
self.chatControllerNode = chatControllerNode
|
||||
self.presentGlobalOverlayController = presentGlobalOverlayController
|
||||
self.callPeer = callPeer
|
||||
self.openConferenceCall = openConferenceCall
|
||||
self.longTap = longTap
|
||||
self.openCheckoutOrReceipt = openCheckoutOrReceipt
|
||||
self.openSearch = openSearch
|
||||
|
@ -394,7 +394,20 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
self.environment?.controller()?.dismiss()
|
||||
})
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +157,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -17,7 +17,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
import EmojiStatusComponent
|
||||
import GalleryUI
|
||||
|
||||
import HierarchyTrackingLayer
|
||||
|
||||
final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
let context: AccountContext
|
||||
@ -32,6 +32,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
private var videoContent: NativeVideoContent?
|
||||
private var videoStartTimestamp: Double?
|
||||
|
||||
private let hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
||||
var isExpanded: Bool = false
|
||||
var canAttachVideo: Bool = true {
|
||||
didSet {
|
||||
@ -78,6 +80,21 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
tapGestureRecognizer.isEnabled = true
|
||||
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 {
|
||||
@ -189,9 +206,52 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: markupNode, alpha: 1.0 - fraction)
|
||||
}
|
||||
}
|
||||
|
||||
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>()
|
||||
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) {
|
||||
self.params = Params(peer: peer, threadId: threadId, threadInfo: threadInfo, item: item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: isSettings)
|
||||
|
||||
if let peer = peer {
|
||||
let previousItem = self.item
|
||||
var item = item
|
||||
@ -345,52 +405,54 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
if videoContent.id != self.videoContent?.id {
|
||||
self.videoNode?.removeFromSupernode()
|
||||
|
||||
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)
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
videoNode.isHidden = true
|
||||
|
||||
if let startTimestamp = video.representation.startTimestamp {
|
||||
self.videoStartTimestamp = startTimestamp
|
||||
self.playbackStartDisposable.set((videoNode.status
|
||||
|> map { status -> Bool in
|
||||
if let status = status, case .playing = status.status {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> filter { playing in
|
||||
return playing
|
||||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
Queue.mainQueue().after(0.15) {
|
||||
strongSelf.videoNode?.isHidden = false
|
||||
if self.hierarchyTrackingLayer.isInHierarchy {
|
||||
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)
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
videoNode.isHidden = true
|
||||
|
||||
if let startTimestamp = video.representation.startTimestamp {
|
||||
self.videoStartTimestamp = startTimestamp
|
||||
self.playbackStartDisposable.set((videoNode.status
|
||||
|> map { status -> Bool in
|
||||
if let status = status, case .playing = status.status {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.videoStartTimestamp = nil
|
||||
self.playbackStartDisposable.set(nil)
|
||||
videoNode.isHidden = false
|
||||
|> filter { playing in
|
||||
return playing
|
||||
}
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
Queue.mainQueue().after(0.15) {
|
||||
strongSelf.videoNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.videoStartTimestamp = nil
|
||||
self.playbackStartDisposable.set(nil)
|
||||
videoNode.isHidden = false
|
||||
}
|
||||
|
||||
self.videoContent = videoContent
|
||||
self.videoNode = videoNode
|
||||
|
||||
let maskPath: UIBezierPath
|
||||
if isForum {
|
||||
maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size), cornerRadius: avatarCornerRadius)
|
||||
} else {
|
||||
maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
|
||||
}
|
||||
let shape = CAShapeLayer()
|
||||
shape.path = maskPath.cgPath
|
||||
videoNode.layer.mask = shape
|
||||
|
||||
self.avatarNode.contentNode.addSubnode(videoNode)
|
||||
}
|
||||
|
||||
self.videoContent = videoContent
|
||||
self.videoNode = videoNode
|
||||
|
||||
let maskPath: UIBezierPath
|
||||
if isForum {
|
||||
maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size), cornerRadius: avatarCornerRadius)
|
||||
} else {
|
||||
maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
|
||||
}
|
||||
let shape = CAShapeLayer()
|
||||
shape.path = maskPath.cgPath
|
||||
videoNode.layer.mask = shape
|
||||
|
||||
self.avatarNode.contentNode.addSubnode(videoNode)
|
||||
}
|
||||
} else {
|
||||
if let markupNode = self.markupNode {
|
||||
|
@ -196,7 +196,9 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
||||
self.videoContent = nil
|
||||
self.videoNode = nil
|
||||
|
||||
videoNode.removeFromSupernode()
|
||||
DispatchQueue.main.async {
|
||||
videoNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let videoNode = self.videoNode {
|
||||
|
@ -3617,6 +3617,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, chatControllerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, longTap: { [weak self] content, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -5430,7 +5431,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, openPeer: { [weak self] peer, navigation in
|
||||
self?.openPeer(peerId: peer.id, navigation: navigation)
|
||||
}, callPeer: { peerId, isVideo in
|
||||
//self?.controllerInteraction?.callPeer(peerId)
|
||||
}, openConferenceCall: { _ in
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in
|
||||
if let strongSelf = self {
|
||||
|
@ -2793,7 +2793,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
let _ = self
|
||||
},
|
||||
callPeer: { _, _ in
|
||||
//self?.controllerInteraction?.callPeer(peerId)
|
||||
}, openConferenceCall: { _ in
|
||||
},
|
||||
enqueueMessage: { _ in
|
||||
},
|
||||
|
@ -307,6 +307,8 @@ extension ChatControllerImpl {
|
||||
switch action.action {
|
||||
case .phoneCall:
|
||||
break
|
||||
case .conferenceCall:
|
||||
break
|
||||
default:
|
||||
hideReactionPanelTail = true
|
||||
}
|
||||
|
@ -177,6 +177,7 @@ extension ChatControllerImpl {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ import NotificationPeerExceptionController
|
||||
import AdsReportScreen
|
||||
import AdUI
|
||||
import ChatMessagePaymentAlertController
|
||||
import TelegramCallsUI
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -1366,6 +1367,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
|
||||
}, callPeer: { [weak self] peerId, isVideo in
|
||||
self?.controllerInteraction?.callPeer(peerId, isVideo)
|
||||
}, openConferenceCall: { [weak self] message in
|
||||
self?.controllerInteraction?.openConferenceCall(message)
|
||||
}, enqueueMessage: { [weak self] message in
|
||||
self?.sendMessages([message])
|
||||
}, 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
|
||||
if let self {
|
||||
self.openLinkLongTap(action, params: params)
|
||||
|
@ -20,6 +20,7 @@ import TelegramUniversalVideoContent
|
||||
import GradientBackground
|
||||
import Svg
|
||||
import UniversalMediaPlayer
|
||||
import RangeSet
|
||||
|
||||
public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||
if let representation = representation as? CachedStickerAJpegRepresentation {
|
||||
@ -66,19 +67,49 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
/*return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
if data.complete {
|
||||
return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data)
|
||||
|> `catch` { _ -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
return .complete()
|
||||
} else if let _ = representation as? CachedVideoPrefixFirstFrameRepresentation {
|
||||
return Signal { subscriber in
|
||||
if let size = resource.size {
|
||||
let videoSource = UniversalSoftwareVideoSource(mediaBox: account.postbox.mediaBox, source: .direct(resource: resource, size: size), automaticallyFetchHeader: false, hintVP9: false)
|
||||
let disposable = videoSource.takeFrame(at: 0.0).start(next: { value in
|
||||
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()
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
} else if let size = resource.size {
|
||||
return videoFirstFrameData(account: account, resource: resource, chunkSize: min(size, 192 * 1024))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}*/
|
||||
|
||||
/*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 {
|
||||
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
|
@ -395,7 +395,12 @@ func openResolvedUrlImpl(
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
|
||||
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):
|
||||
|
@ -998,6 +998,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
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 {
|
||||
if parsedUrl.host == "importStickers" {
|
||||
|
@ -117,6 +117,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, longTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
@ -317,7 +318,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location {
|
||||
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
|
||||
}
|
||||
|
@ -2113,7 +2113,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
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
|
||||
return .none
|
||||
}, canSendMessages: {
|
||||
|
@ -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 struct AudioStreamData {
|
||||
public var engine: TelegramEngine
|
||||
@ -495,11 +500,11 @@ public final class OngoingGroupCallContext {
|
||||
preferX264: Bool,
|
||||
logPath: String,
|
||||
onMutedSpeechActivityDetected: @escaping (Bool) -> Void,
|
||||
encryptionKey: Data?,
|
||||
isConference: Bool,
|
||||
audioIsActiveByDefault: Bool,
|
||||
isStream: Bool,
|
||||
sharedAudioDevice: OngoingCallContext.AudioDevice?
|
||||
sharedAudioDevice: OngoingCallContext.AudioDevice?,
|
||||
encryptionContext: OngoingGroupCallEncryptionContext?
|
||||
) {
|
||||
self.queue = queue
|
||||
|
||||
@ -632,9 +637,17 @@ public final class OngoingGroupCallContext {
|
||||
onMutedSpeechActivityDetected(value)
|
||||
},
|
||||
audioDevice: audioDevice?.impl,
|
||||
encryptionKey: encryptionKey,
|
||||
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
|
||||
self.context = GroupCallThreadLocalContext(
|
||||
@ -733,9 +746,17 @@ public final class OngoingGroupCallContext {
|
||||
logPath: logPath,
|
||||
statsLogPath: tempStatsLogPath,
|
||||
audioDevice: nil,
|
||||
encryptionKey: encryptionKey,
|
||||
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
|
||||
|
||||
@ -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
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -172,6 +172,7 @@ objc_library(
|
||||
"//submodules/ffmpeg:ffmpeg",
|
||||
"//third-party/rnnoise:rnnoise",
|
||||
"//third-party/libyuv:libyuv",
|
||||
"//third-party/td:TdBinding",
|
||||
] + (["//third-party/libx264:libx264"] if enable_x264 else []),
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
|
@ -452,9 +452,9 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
|
||||
statsLogPath:(NSString * _Nonnull)statsLogPath
|
||||
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||
encryptionKey:(NSData * _Nullable)encryptionKey
|
||||
isConference:(bool)isConference
|
||||
isActiveByDefault:(bool)isActiveByDefault;
|
||||
isActiveByDefault:(bool)isActiveByDefault
|
||||
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryptDecrypt;
|
||||
|
||||
- (void)stop:(void (^ _Nullable)())completion;
|
||||
|
||||
|
@ -42,6 +42,8 @@
|
||||
#import "platform/darwin/TGRTCCVPixelBuffer.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
#import <TdBinding/TdBinding.h>
|
||||
|
||||
@implementation OngoingCallConnectionDescription
|
||||
|
||||
- (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
|
||||
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||
encryptionKey:(NSData * _Nullable)encryptionKey
|
||||
isConference:(bool)isConference
|
||||
isActiveByDefault:(bool)isActiveByDefault {
|
||||
isActiveByDefault:(bool)isActiveByDefault
|
||||
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryptDecrypt {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_queue = queue;
|
||||
@ -2444,16 +2446,17 @@ isActiveByDefault:(bool)isActiveByDefault {
|
||||
|
||||
std::string statsLogPathValue(statsLogPath.length == 0 ? "" : statsLogPath.UTF8String);
|
||||
|
||||
std::optional<tgcalls::EncryptionKey> mappedEncryptionKey;
|
||||
if (encryptionKey) {
|
||||
auto encryptionKeyValue = std::make_shared<std::array<uint8_t, 256>>();
|
||||
memcpy(encryptionKeyValue->data(), encryptionKey.bytes, encryptionKey.length);
|
||||
|
||||
#if DEBUG
|
||||
NSLog(@"Encryption key: %@", [encryptionKey base64EncodedStringWithOptions:0]);
|
||||
#endif
|
||||
|
||||
mappedEncryptionKey = tgcalls::EncryptionKey(encryptionKeyValue, true);
|
||||
std::function<std::vector<uint8_t>(std::vector<uint8_t> const &, bool)> mappedEncryptDecrypt;
|
||||
if (encryptDecrypt) {
|
||||
NSData * _Nullable (^encryptDecryptBlock)(NSData * _Nonnull, bool) = [encryptDecrypt copy];
|
||||
mappedEncryptDecrypt = [encryptDecryptBlock](std::vector<uint8_t> const &message, bool isEncrypt) -> std::vector<uint8_t> {
|
||||
NSData *mappedMessage = [[NSData alloc] initWithBytes:message.data() length:message.size()];
|
||||
NSData *result = encryptDecryptBlock(mappedMessage, isEncrypt);
|
||||
if (!result) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
return std::vector<uint8_t>((uint8_t *)result.bytes, ((uint8_t *)result.bytes) + result.length);
|
||||
};
|
||||
}
|
||||
|
||||
__weak GroupCallThreadLocalContext *weakSelf = self;
|
||||
@ -2681,7 +2684,7 @@ isActiveByDefault:(bool)isActiveByDefault {
|
||||
}
|
||||
}];
|
||||
},
|
||||
.encryptionKey = mappedEncryptionKey,
|
||||
.e2eEncryptDecrypt = mappedEncryptDecrypt,
|
||||
.isConference = isConference
|
||||
}));
|
||||
}
|
||||
|
4
third-party/boringssl/BUILD
vendored
4
third-party/boringssl/BUILD
vendored
@ -29,7 +29,9 @@ load(
|
||||
|
||||
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
|
||||
# with the build. This default can be flipped with -DOPENSSL_NO_ASM. If building
|
||||
|
150
third-party/td/BUILD
vendored
Normal file
150
third-party/td/BUILD
vendored
Normal 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",
|
||||
]
|
||||
)
|
56
third-party/td/TdBinding/Public/TdBinding/TdBinding.h
vendored
Normal file
56
third-party/td/TdBinding/Public/TdBinding/TdBinding.h
vendored
Normal 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
|
275
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
Normal file
275
third-party/td/TdBinding/Sources/TdBinding.mm
vendored
Normal 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
52
third-party/td/build-td-bazel.sh
vendored
Executable 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
194
third-party/td/td/.clang-format
vendored
Normal 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
39
third-party/td/td/.gitattributes
vendored
Normal 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
8
third-party/td/td/.gitignore
vendored
Normal 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
1520
third-party/td/td/CHANGELOG.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
74
third-party/td/td/CMake/AddCXXCompilerFlag.cmake
vendored
Normal file
74
third-party/td/td/CMake/AddCXXCompilerFlag.cmake
vendored
Normal 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()
|
62
third-party/td/td/CMake/FindAtomics.cmake
vendored
Normal file
62
third-party/td/td/CMake/FindAtomics.cmake
vendored
Normal 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)
|
25
third-party/td/td/CMake/FindReadline.cmake
vendored
Normal file
25
third-party/td/td/CMake/FindReadline.cmake
vendored
Normal 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)
|
99
third-party/td/td/CMake/GeneratePkgConfig.cmake
vendored
Normal file
99
third-party/td/td/CMake/GeneratePkgConfig.cmake
vendored
Normal 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()
|
127
third-party/td/td/CMake/GetGitRevisionDescription.cmake
vendored
Normal file
127
third-party/td/td/CMake/GetGitRevisionDescription.cmake
vendored
Normal 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()
|
43
third-party/td/td/CMake/GetGitRevisionDescription.cmake.in
vendored
Normal file
43
third-party/td/td/CMake/GetGitRevisionDescription.cmake.in
vendored
Normal 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()
|
14
third-party/td/td/CMake/PreventInSourceBuild.cmake
vendored
Normal file
14
third-party/td/td/CMake/PreventInSourceBuild.cmake
vendored
Normal 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()
|
185
third-party/td/td/CMake/TdSetUpCompiler.cmake
vendored
Normal file
185
third-party/td/td/CMake/TdSetUpCompiler.cmake
vendored
Normal 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
278
third-party/td/td/CMake/iOS.cmake
vendored
Normal 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
10
third-party/td/td/CMake/illumos.cmake
vendored
Normal 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
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
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
23
third-party/td/td/LICENSE_1_0.txt
vendored
Normal 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
145
third-party/td/td/README.md
vendored
Normal 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
508
third-party/td/td/SplitSource.php
vendored
Normal 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
Loading…
x
Reference in New Issue
Block a user