Conference calls

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

View File

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

View File

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

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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
)
}

View File

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

View File

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

View File

@ -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() {

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

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

View File

@ -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))

View File

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

View File

@ -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)

View File

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

View File

@ -52,6 +52,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1471112230] = { return $0.readInt32() }
dict[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) }

View File

@ -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
}
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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) {

View File

@ -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,

View File

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

View File

@ -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)

View File

@ -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))
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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] = []

View File

@ -175,7 +175,7 @@ extension VideoChatScreenComponent.View {
}
}
if case let .group(groupCall) = currentCall, let encryptionKey = groupCall.encryptionKeyValue {
/*if case let .group(groupCall) = currentCall, let encryptionKey = groupCall.encryptionKeyValue {
//TODO:localize
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 = ""

View File

@ -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)

View File

@ -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
)
}
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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
)
}

View File

@ -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

View File

@ -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
}
}
}
}
}
}

View File

@ -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! {

View File

@ -134,6 +134,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
case 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")
}
}

View File

@ -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> {

View File

@ -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))
}
}

View File

@ -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)

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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?)

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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
}

View File

@ -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):

View File

@ -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: {

View File

@ -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

View File

@ -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()
}
}

View File

@ -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",

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

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

View File

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

View File

@ -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)))
}
}

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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" {

View File

@ -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
}

View File

@ -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: {

View File

@ -216,6 +216,11 @@ final class OngoingGroupCallBroadcastPartTaskImpl: NSObject, OngoingGroupCallBro
}
}
public protocol OngoingGroupCallEncryptionContext: AnyObject {
func encrypt(message: Data) -> Data?
func decrypt(message: Data) -> Data?
}
public final class OngoingGroupCallContext {
public 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)
})
}

View File

@ -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",

View File

@ -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;

View File

@ -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
}));
}

View File

@ -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
View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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