Premium features

This commit is contained in:
Isaac 2024-03-13 17:56:08 +04:00
parent d51d0960b0
commit 3acc53280f
32 changed files with 1105 additions and 150 deletions

View File

@ -231,17 +231,52 @@ public enum ChatRecordedMediaPreview: Equatable {
case video(Video) case video(Video)
} }
public final class ChatManagingBot: Equatable {
public let bot: EnginePeer
public let isPaused: Bool
public let canReply: Bool
public let settingsUrl: String?
public init(bot: EnginePeer, isPaused: Bool, canReply: Bool, settingsUrl: String?) {
self.bot = bot
self.isPaused = isPaused
self.canReply = canReply
self.settingsUrl = settingsUrl
}
public static func ==(lhs: ChatManagingBot, rhs: ChatManagingBot) -> Bool {
if lhs === rhs {
return true
}
if lhs.bot != rhs.bot {
return false
}
if lhs.isPaused != rhs.isPaused {
return false
}
if lhs.canReply != rhs.canReply {
return false
}
if lhs.settingsUrl != rhs.settingsUrl {
return false
}
return true
}
}
public struct ChatContactStatus: Equatable { public struct ChatContactStatus: Equatable {
public var canAddContact: Bool public var canAddContact: Bool
public var canReportIrrelevantLocation: Bool public var canReportIrrelevantLocation: Bool
public var peerStatusSettings: PeerStatusSettings? public var peerStatusSettings: PeerStatusSettings?
public var invitedBy: Peer? public var invitedBy: Peer?
public var managingBot: ChatManagingBot?
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) { public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) {
self.canAddContact = canAddContact self.canAddContact = canAddContact
self.canReportIrrelevantLocation = canReportIrrelevantLocation self.canReportIrrelevantLocation = canReportIrrelevantLocation
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
self.invitedBy = invitedBy self.invitedBy = invitedBy
self.managingBot = managingBot
} }
public var isEmpty: Bool { public var isEmpty: Bool {
@ -270,6 +305,9 @@ public struct ChatContactStatus: Equatable {
if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) { if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) {
return false return false
} }
if lhs.managingBot != rhs.managingBot {
return false
}
return true return true
} }
} }

View File

@ -8,6 +8,7 @@
@property (nonatomic) NSUInteger internalServerErrorCount; @property (nonatomic) NSUInteger internalServerErrorCount;
@property (nonatomic) NSUInteger floodWaitSeconds; @property (nonatomic) NSUInteger floodWaitSeconds;
@property (nonatomic, strong) NSString *floodWaitErrorText;
@property (nonatomic) bool waitingForTokenExport; @property (nonatomic) bool waitingForTokenExport;
@property (nonatomic, strong) id waitingForRequestToComplete; @property (nonatomic, strong) id waitingForRequestToComplete;

View File

@ -808,7 +808,7 @@
} }
restartRequest = true; restartRequest = true;
} }
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound) { else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
if (request.errorContext == nil) if (request.errorContext == nil)
request.errorContext = [[MTRequestErrorContext alloc] init]; request.errorContext = [[MTRequestErrorContext alloc] init];
@ -821,6 +821,32 @@
if ([scanner scanInt:&errorWaitTime]) if ([scanner scanInt:&errorWaitTime])
{ {
request.errorContext.floodWaitSeconds = errorWaitTime; request.errorContext.floodWaitSeconds = errorWaitTime;
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
if (request.shouldContinueExecutionWithErrorContext != nil)
{
if (request.shouldContinueExecutionWithErrorContext(request.errorContext))
{
restartRequest = true;
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
}
}
else
{
restartRequest = true;
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
}
}
} else if ([rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
int errorWaitTime = 0;
NSScanner *scanner = [[NSScanner alloc] initWithString:rpcError.errorDescription];
[scanner scanUpToString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
[scanner scanString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
if ([scanner scanInt:&errorWaitTime])
{
request.errorContext.floodWaitSeconds = errorWaitTime;
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
if (request.shouldContinueExecutionWithErrorContext != nil) if (request.shouldContinueExecutionWithErrorContext != nil)
{ {

View File

@ -211,7 +211,7 @@ private final class ShareContentInfoView: UIView {
let accentColor = params.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0) let accentColor = params.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
if self.arrowIcon == nil { if self.arrowIcon == nil {
if let templateImage = UIImage(bundleImageName: "Settings/TextArrowRight") { if let templateImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") {
let scaleFactor: CGFloat = 0.8 let scaleFactor: CGFloat = 0.8
let imageSize = CGSize(width: floor(templateImage.size.width * scaleFactor), height: floor(templateImage.size.height * scaleFactor)) let imageSize = CGSize(width: floor(templateImage.size.width * scaleFactor), height: floor(templateImage.size.height * scaleFactor))
self.arrowIcon = generateImage(imageSize, contextGenerator: { size, context in self.arrowIcon = generateImage(imageSize, contextGenerator: { size, context in

View File

@ -671,7 +671,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) } dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) } dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) } dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
dict[-1525149427] = { return Api.PeerSettings.parse_peerSettings($0) } dict[-1395233698] = { return Api.PeerSettings.parse_peerSettings($0) }
dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) } dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) }
dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) } dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) }
dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }

View File

@ -898,26 +898,28 @@ public extension Api {
} }
public extension Api { public extension Api {
enum PeerSettings: TypeConstructorDescription { enum PeerSettings: TypeConstructorDescription {
case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?) case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate): case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl):
if boxed { if boxed {
buffer.appendInt32(-1525149427) buffer.appendInt32(-1395233698)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 9) != 0 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {serializeInt64(businessBotId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {serializeString(businessBotManageUrl!, buffer: buffer, boxed: false)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate): case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl):
return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any)]) return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any)])
} }
} }
@ -930,12 +932,18 @@ public extension Api {
if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) } if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) }
var _4: Int32? var _4: Int32?
if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() } if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() }
var _5: Int64?
if Int(_1!) & Int(1 << 13) != 0 {_5 = reader.readInt64() }
var _6: String?
if Int(_1!) & Int(1 << 13) != 0 {_6 = parseString(reader) }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4) let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6)
} }
else { else {
return nil return nil

View File

@ -221,6 +221,21 @@ public extension Api.functions.account {
}) })
} }
} }
public extension Api.functions.account {
static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1581481689)
peer.serialize(buffer, true)
return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.account { public extension Api.functions.account {
static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
@ -1254,6 +1269,22 @@ public extension Api.functions.account {
}) })
} }
} }
public extension Api.functions.account {
static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1684934807)
peer.serialize(buffer, true)
paused.serialize(buffer, true)
return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", String(describing: peer)), ("paused", String(describing: paused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.account { public extension Api.functions.account {
static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -144,13 +144,13 @@ public class UnauthorizedAccount {
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self)) return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self))
} }
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration), NoError> in
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self)) return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self), transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue)
} }
} }
|> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in |> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false) return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration)
|> map { network in |> map { network in
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
if let accountState = accountState { if let accountState = accountState {
switch accountState { switch accountState {
case let unauthorizedState as UnauthorizedAccountState: case let unauthorizedState as UnauthorizedAccountState:
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers) return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in |> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
} }
@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
} }
|> mapToSignal { phoneNumber in |> mapToSignal { phoneNumber in
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers) return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in |> map { network -> AccountResult in
return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary)) return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary))
} }
@ -267,7 +267,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
} }
} }
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers) return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in |> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
} }
@ -889,6 +889,11 @@ public func accountBackupData(postbox: Postbox) -> Signal<AccountBackupData?, No
} }
} }
public enum NetworkSpeedLimitedEvent {
case upload
case download
}
public class Account { public class Account {
static let sharedQueue = Queue(name: "Account-Shared") static let sharedQueue = Queue(name: "Account-Shared")
@ -1519,7 +1524,8 @@ public func standaloneStateManager(
proxySettings: proxySettings, proxySettings: proxySettings,
networkSettings: networkSettings, networkSettings: networkSettings,
phoneNumber: phoneNumber, phoneNumber: phoneNumber,
useRequestTimeoutTimers: false useRequestTimeoutTimers: false,
appConfiguration: .defaultValue
) )
|> map { network -> AccountStateManager? in |> map { network -> AccountStateManager? in
Logger.shared.log("StandaloneStateManager", "received network") Logger.shared.log("StandaloneStateManager", "received network")

View File

@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId) self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
} }
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> { static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
if asBigPart { if asBigPart {
let totalParts: Int32 let totalParts: Int32
@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data)) saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
} }
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, expectedResponseSize: nil) return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, onFloodWaitError: onFloodWaitError, expectedResponseSize: nil)
|> mapError { error -> UploadPartError in |> mapError { error -> UploadPartError in
if error.errorCode == 400 { if error.errorCode == 400 {
return .invalidMedia return .invalidMedia
@ -130,7 +130,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
} }
} }
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> { func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
return Signal<Void, MTRpcError> { subscriber in return Signal<Void, MTRpcError> { subscriber in
let request = MTRequest() let request = MTRequest()
@ -159,6 +159,13 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
request.dependsOnPasswordEntry = false request.dependsOnPasswordEntry = false
request.shouldContinueExecutionWithErrorContext = { errorContext in request.shouldContinueExecutionWithErrorContext = { errorContext in
guard let errorContext = errorContext else {
return true
}
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
return true return true
} }
@ -295,7 +302,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|> retryRequest |> retryRequest
} }
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> { func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
request.expectedResponseSize = expectedResponseSize ?? 0 request.expectedResponseSize = expectedResponseSize ?? 0
@ -314,6 +321,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }
@ -344,7 +354,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
} }
} }
func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> { func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
request.expectedResponseSize = expectedResponseSize ?? 0 request.expectedResponseSize = expectedResponseSize ?? 0
@ -363,6 +373,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }
@ -396,7 +409,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
} }
} }
func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> { func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> {
let requestService = self.requestService let requestService = self.requestService
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
@ -416,6 +429,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }

View File

@ -104,14 +104,14 @@ private struct DownloadWrapper {
self.useMainConnection = useMainConnection self.useMainConnection = useMainConnection
} }
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), MTRpcError> { func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?, onFloodWaitError: @escaping (String) -> Void) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
let target: MultiplexedRequestTarget let target: MultiplexedRequestTarget
if self.isCdn { if self.isCdn {
target = .cdn(Int(self.datacenterId)) target = .cdn(Int(self.datacenterId))
} else { } else {
target = .main(Int(self.datacenterId)) target = .main(Int(self.datacenterId))
} }
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, expectedResponseSize: expectedResponseSize) return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize)
|> mapError { error, _ -> MTRpcError in |> mapError { error, _ -> MTRpcError in
return error return error
} }
@ -192,7 +192,7 @@ private final class MultipartCdnHashSource {
clusterContext = ClusterContext(disposable: disposable) clusterContext = ClusterContext(disposable: disposable)
self.clusterContexts[offset] = clusterContext self.clusterContexts[offset] = clusterContext
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil) disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in })
|> map { partHashes, _ -> [Int64: Data] in |> map { partHashes, _ -> [Int64: Data] in
var parsedPartHashes: [Int64: Data] = [:] var parsedPartHashes: [Int64: Data] = [:]
for part in partHashes { for part in partHashes {
@ -322,7 +322,7 @@ private enum MultipartFetchSource {
} }
} }
func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> { func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool, onFloodWaitError: @escaping (String) -> Void) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> {
var resourceReferenceValue: MediaResourceReference? var resourceReferenceValue: MediaResourceReference?
switch resourceReference { switch resourceReference {
case .forceRevalidate: case .forceRevalidate:
@ -348,7 +348,9 @@ private enum MultipartFetchSource {
case .revalidate: case .revalidate:
return .fail(.revalidateMediaReference) return .fail(.revalidateMediaReference)
case let .location(parsedLocation): case let .location(parsedLocation):
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit)) return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in
onFloodWaitError(error)
})
|> mapError { error -> MultipartFetchDownloadError in |> mapError { error -> MultipartFetchDownloadError in
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
return .revalidateMediaReference return .revalidateMediaReference
@ -380,7 +382,9 @@ private enum MultipartFetchSource {
} }
} }
case let .web(_, location): case let .web(_, location):
return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit)) return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in
onFloodWaitError(error)
})
|> mapError { error -> MultipartFetchDownloadError in |> mapError { error -> MultipartFetchDownloadError in
if error.errorDescription == "WEBFILE_NOT_AVAILABLE" { if error.errorDescription == "WEBFILE_NOT_AVAILABLE" {
return .webfileNotAvailable return .webfileNotAvailable
@ -404,7 +408,9 @@ private enum MultipartFetchSource {
updatedLength += 1 updatedLength += 1
} }
let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength)) let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength), onFloodWaitError: { error in
onFloodWaitError(error)
})
|> mapError { _ -> MultipartFetchDownloadError in |> mapError { _ -> MultipartFetchDownloadError in
return .generic return .generic
} }
@ -723,6 +729,13 @@ private final class MultipartFetchManager {
} }
} }
private func processFloodWaitError(error: String) {
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
self.network.addNetworkSpeedLimitedEvent(event: .download)
}
}
func checkState() { func checkState() {
guard let currentIntervals = self.currentIntervals else { guard let currentIntervals = self.currentIntervals else {
return return
@ -836,7 +849,15 @@ private final class MultipartFetchManager {
} }
let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound) let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound)
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground) let queue = self.queue
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground, onFloodWaitError: { [weak self] error in
queue.async {
guard let self else {
return
}
self.processFloodWaitError(error: error)
}
})
|> deliverOn(self.queue) |> deliverOn(self.queue)
let partDisposable = MetaDisposable() let partDisposable = MetaDisposable()
self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable) self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
@ -919,7 +940,7 @@ private final class MultipartFetchManager {
case let .cdn(_, _, fileToken, _, _, _, masterDownload, _): case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
if !strongSelf.reuploadingToCdn { if !strongSelf.reuploadingToCdn {
strongSelf.reuploadingToCdn = true strongSelf.reuploadingToCdn = true
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil) let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in })
|> map { result, _ -> [Api.FileHash] in |> map { result, _ -> [Api.FileHash] in
return result return result
} }

View File

@ -470,12 +470,21 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
fetchedResource = .complete() fetchedResource = .complete()
} }
let onFloodWaitError: (String) -> Void = { [weak network] error in
guard let network else {
return
}
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
network.addNetworkSpeedLimitedEvent(event: .upload)
}
}
let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
switch uploadInterface { switch uploadInterface {
case let .download(download): case let .download(download):
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression) return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError)
case let .multiplexed(multiplexed, datacenterId, consumerId): case let .multiplexed(multiplexed, datacenterId, consumerId):
return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression) return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError)
} }
}, progress: { progress in }, progress: { progress in
subscriber.putNext(.progress(progress)) subscriber.putNext(.progress(progress))

View File

@ -33,12 +33,13 @@ private final class RequestData {
let tag: MediaResourceFetchTag? let tag: MediaResourceFetchTag?
let continueInBackground: Bool let continueInBackground: Bool
let automaticFloodWait: Bool let automaticFloodWait: Bool
let onFloodWaitError: ((String) -> Void)?
let expectedResponseSize: Int32? let expectedResponseSize: Int32?
let deserializeResponse: (Buffer) -> Any? let deserializeResponse: (Buffer) -> Any?
let completed: (Any, NetworkResponseInfo) -> Void let completed: (Any, NetworkResponseInfo) -> Void
let error: (MTRpcError, Double) -> Void let error: (MTRpcError, Double) -> Void
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) { init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)?, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
self.id = id self.id = id
self.consumerId = consumerId self.consumerId = consumerId
self.resourceId = resourceId self.resourceId = resourceId
@ -47,6 +48,7 @@ private final class RequestData {
self.tag = tag self.tag = tag
self.continueInBackground = continueInBackground self.continueInBackground = continueInBackground
self.automaticFloodWait = automaticFloodWait self.automaticFloodWait = automaticFloodWait
self.onFloodWaitError = onFloodWaitError
self.expectedResponseSize = expectedResponseSize self.expectedResponseSize = expectedResponseSize
self.payload = payload self.payload = payload
self.deserializeResponse = deserializeResponse self.deserializeResponse = deserializeResponse
@ -155,12 +157,12 @@ private final class MultiplexedRequestManagerContext {
} }
} }
func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable { func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground) let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
let requestId = self.nextId let requestId = self.nextId
self.nextId += 1 self.nextId += 1
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in
return data.2(buffer) return data.2(buffer)
}, completed: { result, info in }, completed: { result, info in
completed(result, info) completed(result, info)
@ -254,7 +256,7 @@ private final class MultiplexedRequestManagerContext {
let requestId = request.id let requestId = request.id
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable)) selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
let queue = self.queue let queue = self.queue
disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, onFloodWaitError: request.onFloodWaitError, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in
queue.async { queue.async {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -354,13 +356,13 @@ final class MultiplexedRequestManager {
return disposable return disposable
} }
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> { func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.context.with { context in self.context.with { context in
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
return data.2.parse(buffer) return data.2.parse(buffer)
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, _ in }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, _ in
if let result = result as? T { if let result = result as? T {
subscriber.putNext(result) subscriber.putNext(result)
subscriber.putCompletion() subscriber.putCompletion()
@ -375,13 +377,13 @@ final class MultiplexedRequestManager {
} }
} }
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> { func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.context.with { context in self.context.with { context in
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
return data.2.parse(buffer) return data.2.parse(buffer)
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, info in }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, info in
if let result = result as? T { if let result = result as? T {
subscriber.putNext((result, info)) subscriber.putNext((result, info))
subscriber.putCompletion() subscriber.putCompletion()

View File

@ -459,7 +459,7 @@ public struct NetworkInitializationArguments {
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil) private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
#endif #endif
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> Signal<Network, NoError> { func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool, appConfiguration: AppConfiguration) -> Signal<Network, NoError> {
return Signal { subscriber in return Signal { subscriber in
let queue = Queue() let queue = Queue()
queue.async { queue.async {
@ -612,6 +612,11 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false
let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures) let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures)
if let data = appConfiguration.data, let notifyInterval = data["upload_premium_speedup_notify_period"] as? Double {
network.updateNetworkSpeedLimitedEventNotifyInterval(value: notifyInterval)
}
appDataUpdatedImpl = { [weak network] data in appDataUpdatedImpl = { [weak network] data in
guard let data = data else { guard let data = data else {
return return
@ -734,6 +739,22 @@ public enum NetworkRequestResult<T> {
case progress(Float, Int32) case progress(Float, Int32)
} }
private final class NetworkSpeedLimitedEventState {
var notifyInterval: Double = 60.0 * 60.0
var lastNotifyTimestamp: Double = 0.0
func add(event: NetworkSpeedLimitedEvent) -> Bool {
let timestamp = CFAbsoluteTimeGetCurrent()
if self.lastNotifyTimestamp + self.notifyInterval < timestamp {
self.lastNotifyTimestamp = timestamp
return true
} else {
return false
}
}
}
public final class Network: NSObject, MTRequestMessageServiceDelegate { public final class Network: NSObject, MTRequestMessageServiceDelegate {
public let encryptionProvider: EncryptionProvider public let encryptionProvider: EncryptionProvider
@ -766,6 +787,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
return self._connectionStatus.get() |> distinctUntilChanged return self._connectionStatus.get() |> distinctUntilChanged
} }
public var networkSpeedLimitedEvents: Signal<NetworkSpeedLimitedEvent, NoError> {
return self.networkSpeedLimitedEventPipe.signal()
}
private let networkSpeedLimitedEventPipe = ValuePipe<NetworkSpeedLimitedEvent>()
private let networkSpeedLimitedEventState = Atomic<NetworkSpeedLimitedEventState>(value: NetworkSpeedLimitedEventState())
public func dropConnectionStatus() { public func dropConnectionStatus() {
_connectionStatus.set(.single(.waitingForNetwork)) _connectionStatus.set(.single(.waitingForNetwork))
} }
@ -826,18 +853,18 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
let array = NSMutableArray() let array = NSMutableArray()
if let result = result { if let result = result {
switch result { switch result {
case let .cdnConfig(publicKeys): case let .cdnConfig(publicKeys):
for key in publicKeys { for key in publicKeys {
switch key { switch key {
case let .cdnPublicKey(dcId, publicKey): case let .cdnPublicKey(dcId, publicKey):
if id == Int(dcId) { if id == Int(dcId) {
let dict = NSMutableDictionary() let dict = NSMutableDictionary()
dict["key"] = publicKey dict["key"] = publicKey
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey) dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
array.add(dict) array.add(dict)
}
} }
} }
}
} }
} }
return array return array
@ -867,12 +894,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
let isCdn: Bool let isCdn: Bool
let isMedia: Bool = true let isMedia: Bool = true
switch target { switch target {
case let .main(id): case let .main(id):
datacenterId = id datacenterId = id
isCdn = false isCdn = false
case let .cdn(id): case let .cdn(id):
datacenterId = id datacenterId = id
isCdn = true isCdn = true
} }
return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground) return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground)
} }
@ -880,7 +907,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
}) })
let shouldKeepConnectionSignal = self.shouldKeepConnection.get() let shouldKeepConnectionSignal = self.shouldKeepConnection.get()
|> distinctUntilChanged |> deliverOn(queue) |> distinctUntilChanged |> deliverOn(queue)
self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in
if let strongSelf = self { if let strongSelf = self {
if value { if value {
@ -967,11 +994,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address) self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address)
/*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false) /*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false)
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) { if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
} else { } else {
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false)
}*/ }*/
let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false) let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false)
var found = false var found = false
@ -988,7 +1015,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
} }
} }
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<NetworkRequestResult<T>, MTRpcError> { public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<NetworkRequestResult<T>, MTRpcError> {
let requestService = self.requestService let requestService = self.requestService
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
@ -1006,6 +1033,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }
@ -1056,8 +1086,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
} }
} }
} }
public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> { public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
let requestService = self.requestService let requestService = self.requestService
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
@ -1075,6 +1105,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }
@ -1113,6 +1146,21 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
} }
} }
} }
func updateNetworkSpeedLimitedEventNotifyInterval(value: Double) {
let _ = self.networkSpeedLimitedEventState.with { state in
state.notifyInterval = value
}
}
func addNetworkSpeedLimitedEvent(event: NetworkSpeedLimitedEvent) {
let notify = self.networkSpeedLimitedEventState.with { state in
return state.add(event: event)
}
if notify {
self.networkSpeedLimitedEventPipe.putNext(event)
}
}
} }
public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> { public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {

View File

@ -6,33 +6,44 @@ import SwiftSignalKit
extension PeerStatusSettings { extension PeerStatusSettings {
init(apiSettings: Api.PeerSettings) { init(apiSettings: Api.PeerSettings) {
switch apiSettings { switch apiSettings {
case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate): case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl):
var result = PeerStatusSettings.Flags() var result = PeerStatusSettings.Flags()
if (flags & (1 << 1)) != 0 { if (flags & (1 << 1)) != 0 {
result.insert(.canAddContact) result.insert(.canAddContact)
} }
if (flags & (1 << 0)) != 0 { if (flags & (1 << 0)) != 0 {
result.insert(.canReport) result.insert(.canReport)
} }
if (flags & (1 << 2)) != 0 { if (flags & (1 << 2)) != 0 {
result.insert(.canBlock) result.insert(.canBlock)
} }
if (flags & (1 << 3)) != 0 { if (flags & (1 << 3)) != 0 {
result.insert(.canShareContact) result.insert(.canShareContact)
} }
if (flags & (1 << 4)) != 0 { if (flags & (1 << 4)) != 0 {
result.insert(.addExceptionWhenAddingContact) result.insert(.addExceptionWhenAddingContact)
} }
if (flags & (1 << 5)) != 0 { if (flags & (1 << 5)) != 0 {
result.insert(.canReportIrrelevantGeoLocation) result.insert(.canReportIrrelevantGeoLocation)
} }
if (flags & (1 << 7)) != 0 { if (flags & (1 << 7)) != 0 {
result.insert(.autoArchived) result.insert(.autoArchived)
} }
if (flags & (1 << 8)) != 0 { if (flags & (1 << 8)) != 0 {
result.insert(.suggestAddMembers) result.insert(.suggestAddMembers)
} }
self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0)
var managingBot: ManagingBot?
if let businessBotId {
managingBot = ManagingBot(
id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(businessBotId)),
manageUrl: businessBotManageUrl,
isPaused: (flags & (1 << 11)) != 0,
canReply: (flags & (1 << 12)) != 0
)
}
self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0, managingBot: managingBot)
} }
} }
} }

View File

@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
#if DEBUG /*#if DEBUG
if "".isEmpty { if "".isEmpty {
return return
} }
#endif #endif*/
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = [] var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
for item in loadItems { for item in loadItems {

View File

@ -551,7 +551,7 @@ public final class CachedChannelData: CachedPeerData {
var peerIds = Set<PeerId>() var peerIds = Set<PeerId>()
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -203,7 +203,7 @@ public final class CachedGroupData: CachedPeerData {
self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i") self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i")
self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo] self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo]
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -558,7 +558,7 @@ public final class CachedUserData: CachedPeerData {
self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo
self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -16,7 +16,20 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6) public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6)
public static let autoArchived = Flags(rawValue: 1 << 7) public static let autoArchived = Flags(rawValue: 1 << 7)
public static let suggestAddMembers = Flags(rawValue: 1 << 8) public static let suggestAddMembers = Flags(rawValue: 1 << 8)
}
public struct ManagingBot: Codable, Equatable {
public var id: PeerId
public var manageUrl: String?
public var isPaused: Bool
public var canReply: Bool
public init(id: PeerId, manageUrl: String?, isPaused: Bool, canReply: Bool) {
self.id = id
self.manageUrl = manageUrl
self.isPaused = isPaused
self.canReply = canReply
}
} }
public var flags: PeerStatusSettings.Flags public var flags: PeerStatusSettings.Flags
@ -24,20 +37,23 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
public var requestChatTitle: String? public var requestChatTitle: String?
public var requestChatDate: Int32? public var requestChatDate: Int32?
public var requestChatIsChannel: Bool? public var requestChatIsChannel: Bool?
public var managingBot: ManagingBot?
public init() { public init() {
self.flags = PeerStatusSettings.Flags() self.flags = PeerStatusSettings.Flags()
self.geoDistance = nil self.geoDistance = nil
self.requestChatTitle = nil self.requestChatTitle = nil
self.requestChatDate = nil self.requestChatDate = nil
self.managingBot = nil
} }
public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil) { public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil, managingBot: ManagingBot?) {
self.flags = flags self.flags = flags
self.geoDistance = geoDistance self.geoDistance = geoDistance
self.requestChatTitle = requestChatTitle self.requestChatTitle = requestChatTitle
self.requestChatDate = requestChatDate self.requestChatDate = requestChatDate
self.requestChatIsChannel = requestChatIsChannel self.requestChatIsChannel = requestChatIsChannel
self.managingBot = managingBot
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -46,6 +62,7 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle") self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle")
self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate") self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate")
self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel") self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel")
self.managingBot = decoder.decodeCodable(ManagingBot.self, forKey: "managingBot")
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -70,6 +87,11 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "requestChatIsChannel") encoder.encodeNil(forKey: "requestChatIsChannel")
} }
if let managingBot = self.managingBot {
encoder.encodeCodable(managingBot, forKey: "managingBot")
} else {
encoder.encodeNil(forKey: "managingBot")
}
} }
public func contains(_ member: PeerStatusSettings.Flags) -> Bool { public func contains(_ member: PeerStatusSettings.Flags) -> Bool {

View File

@ -93,7 +93,7 @@ public final class CachedSecretChatData: CachedPeerData {
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -214,7 +214,7 @@ func _internal_dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Sig
if let current = current as? CachedUserData { if let current = current as? CachedUserData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = [] peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: [])) return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedGroupData { } else if let current = current as? CachedGroupData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = [] peerStatusSettings.flags = []

View File

@ -56,12 +56,12 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountP
var peerStatusSettings: PeerStatusSettings var peerStatusSettings: PeerStatusSettings
if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) { if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) {
if let peer = peer as? TelegramSecretChat, case .creator = peer.role { if let peer = peer as? TelegramSecretChat, case .creator = peer.role {
peerStatusSettings = PeerStatusSettings(flags: []) peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil)
} else { } else {
peerStatusSettings = PeerStatusSettings(flags: [.canReport]) peerStatusSettings = PeerStatusSettings(flags: [.canReport], managingBot: nil)
} }
} else { } else {
peerStatusSettings = PeerStatusSettings(flags: []) peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil)
} }
transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in

View File

@ -156,6 +156,18 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
} }
} }
if authorTitle == nil {
for attribute in message.attributes {
if let attribute = attribute as? InlineBusinessBotMessageAttribute {
if let title = attribute.title {
authorTitle = title
} else if let peerId = attribute.peerId, let peer = message.peers[peerId] {
authorTitle = peer.debugDisplayTitle
}
}
}
}
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
authorTitle = nil authorTitle = nil
} }

View File

@ -209,8 +209,31 @@ final class StoryContentCaptionComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.shadowGradientView = UIImageView() self.shadowGradientView = UIImageView()
if let image = StoryContentCaptionComponent.View.shadowImage { if let _ = StoryContentCaptionComponent.View.shadowImage {
self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0)) let height: CGFloat = 128.0
let baseGradientAlpha: CGFloat = 0.8
let numSteps = 8
let firstStep = 0
let firstLocation = 0.0
let colors = (0 ..< numSteps).map { i -> UIColor in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0)
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
}
}
let locations = (0 ..< numSteps).map { i -> CGFloat in
if i < firstStep {
return 0.0
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step)
}
}
self.shadowGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors.reversed(), locations: locations.reversed().map { 1.0 - $0 })!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(height - 1.0))
} }
self.scrollViewContainer = UIView() self.scrollViewContainer = UIView()
@ -386,7 +409,8 @@ final class StoryContentCaptionComponent: Component {
transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize())) transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize()))
let shadowOverflow: CGFloat = 58.0 let shadowHeight: CGFloat = self.shadowGradientView.image?.size.height ?? 100.0
let shadowOverflow: CGFloat = floor(shadowHeight * 0.6)
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow)) let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)) let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0))

View File

@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component {
self.bottomContentGradientLayer.colors = colors self.bottomContentGradientLayer.colors = colors
self.bottomContentGradientLayer.type = .axial self.bottomContentGradientLayer.type = .axial
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3) self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
} }
let wasPanning = self.component?.isPanning ?? false let wasPanning = self.component?.isPanning ?? false

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "more.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,79 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 1.500000 1.335754 cm
0.000000 0.000000 0.000000 scn
5.252930 4.662109 m
5.252930 4.527832 5.199219 4.409668 5.097168 4.307617 c
0.843262 0.145020 l
0.746582 0.048340 0.628418 0.000000 0.488770 0.000000 c
0.214844 0.000000 0.000000 0.209473 0.000000 0.488770 c
0.000000 0.628418 0.053711 0.746582 0.139648 0.837891 c
4.049805 4.662109 l
0.139648 8.486328 l
0.053711 8.577637 0.000000 8.701172 0.000000 8.835449 c
0.000000 9.114746 0.214844 9.324219 0.488770 9.324219 c
0.628418 9.324219 0.746582 9.275879 0.843262 9.184570 c
5.097168 5.016602 l
5.199219 4.919922 5.252930 4.796387 5.252930 4.662109 c
h
f
n
Q
endstream
endobj
3 0 obj
675
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 8.000000 12.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000765 00000 n
0000000787 00000 n
0000000959 00000 n
0000001033 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1092
%%EOF

View File

@ -585,6 +585,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)? var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
var networkSpeedEventsDisposable: Disposable?
public var alwaysShowSearchResultsAsList: Bool = false { public var alwaysShowSearchResultsAsList: Bool = false {
didSet { didSet {
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
@ -4906,6 +4908,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
} }
let managingBot: Signal<ChatManagingBot?, NoError>
if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser {
managingBot = self.context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId)
)
|> mapToSignal { result -> Signal<ChatManagingBot?, NoError> in
guard let result else {
return .single(nil)
}
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: result.id)
)
|> map { botPeer -> ChatManagingBot? in
guard let botPeer else {
return nil
}
var isPaused = false
if result.recipients.exclude {
isPaused = result.recipients.additionalPeers.contains(peerId)
} else {
isPaused = !result.recipients.additionalPeers.contains(peerId)
}
var settingsUrl: String?
if let username = botPeer.addressName {
settingsUrl = "https://t.me/\(username)"
}
return ChatManagingBot(bot: botPeer, isPaused: isPaused, canReply: result.canReply, settingsUrl: settingsUrl)
}
}
|> distinctUntilChanged
} else {
managingBot = .single(nil)
}
do { do {
let peerId = chatLocationPeerId let peerId = chatLocationPeerId
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
@ -5294,10 +5333,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
threadInfo, threadInfo,
hasSearchTags, hasSearchTags,
hasSavedChats, hasSavedChats,
isPremiumRequiredForMessaging isPremiumRequiredForMessaging,
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in managingBot
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
if let strongSelf = self { if let strongSelf = self {
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging { if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging && managingBot == strongSelf.presentationInterfaceState.contactStatus?.managingBot {
return return
} }
@ -5392,7 +5432,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var contactStatus: ChatContactStatus? var contactStatus: ChatContactStatus?
if let peer = peerView.peers[peerView.peerId] { if let peer = peerView.peers[peerView.peerId] {
if let cachedData = peerView.cachedData as? CachedUserData { if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
} else if let cachedData = peerView.cachedData as? CachedGroupData { } else if let cachedData = peerView.cachedData as? CachedGroupData {
var invitedBy: Peer? var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy { if let invitedByPeerId = cachedData.invitedBy {
@ -5400,7 +5440,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
invitedBy = peer invitedBy = peer
} }
} }
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
} else if let cachedData = peerView.cachedData as? CachedChannelData { } else if let cachedData = peerView.cachedData as? CachedChannelData {
var canReportIrrelevantLocation = true var canReportIrrelevantLocation = true
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
@ -5415,7 +5455,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
invitedBy = peer invitedBy = peer
} }
} }
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
} }
var peers = SimpleDictionary<PeerId, Peer>() var peers = SimpleDictionary<PeerId, Peer>()
@ -5518,6 +5558,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
} }
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
didDisplayActionsPanel = true
}
if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags { if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags {
didDisplayActionsPanel = true didDisplayActionsPanel = true
} }
@ -5538,6 +5581,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
} }
if let contactStatus, contactStatus.managingBot != nil {
displayActionsPanel = true
}
if strongSelf.presentationInterfaceState.search != nil && hasSearchTags { if strongSelf.presentationInterfaceState.search != nil && hasSearchTags {
displayActionsPanel = true displayActionsPanel = true
} }
@ -5871,9 +5917,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
hasScheduledMessages, hasScheduledMessages,
hasSearchTags, hasSearchTags,
hasSavedChats, hasSavedChats,
isPremiumRequiredForMessaging isPremiumRequiredForMessaging,
managingBot
) )
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
if let strongSelf = self { if let strongSelf = self {
strongSelf.hasScheduledMessages = hasScheduledMessages strongSelf.hasScheduledMessages = hasScheduledMessages
@ -5883,7 +5930,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let peer = peerView.peers[peerView.peerId] { if let peer = peerView.peers[peerView.peerId] {
copyProtectionEnabled = peer.isCopyProtectionEnabled copyProtectionEnabled = peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData { if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
} else if let cachedData = peerView.cachedData as? CachedGroupData { } else if let cachedData = peerView.cachedData as? CachedGroupData {
var invitedBy: Peer? var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy { if let invitedByPeerId = cachedData.invitedBy {
@ -5891,7 +5938,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
invitedBy = peer invitedBy = peer
} }
} }
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
} else if let cachedData = peerView.cachedData as? CachedChannelData { } else if let cachedData = peerView.cachedData as? CachedChannelData {
var canReportIrrelevantLocation = true var canReportIrrelevantLocation = true
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
@ -5904,7 +5951,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
invitedBy = peer invitedBy = peer
} }
} }
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
} }
var peers = SimpleDictionary<PeerId, Peer>() var peers = SimpleDictionary<PeerId, Peer>()
@ -6108,6 +6155,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
} }
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
didDisplayActionsPanel = true
}
var displayActionsPanel = false var displayActionsPanel = false
if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
@ -6125,6 +6175,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
} }
if let contactStatus, contactStatus.managingBot != nil {
displayActionsPanel = true
}
if displayActionsPanel != didDisplayActionsPanel { if displayActionsPanel != didDisplayActionsPanel {
animated = true animated = true
@ -6796,6 +6849,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.preloadSavedMessagesChatsDisposable?.dispose() self.preloadSavedMessagesChatsDisposable?.dispose()
self.recorderDataDisposable.dispose() self.recorderDataDisposable.dispose()
self.displaySendWhenOnlineTipDisposable.dispose() self.displaySendWhenOnlineTipDisposable.dispose()
self.networkSpeedEventsDisposable?.dispose()
} }
deallocate() deallocate()
} }
@ -11685,6 +11739,57 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
var lastEventTimestamp: Double = 0.0
self.networkSpeedEventsDisposable = (self.context.account.network.networkSpeedLimitedEvents
|> deliverOnMainQueue).start(next: { [weak self] event in
guard let self else {
return
}
let timestamp = CFAbsoluteTimeGetCurrent()
if lastEventTimestamp + 10.0 < timestamp {
lastEventTimestamp = timestamp
} else {
return
}
//TODO:localize
let title: String
let text: String
switch event {
case .download:
var speedIncreaseFactor = 10
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_download"] as? Double {
speedIncreaseFactor = Int(value)
}
title = "Download speed limited"
text = "Subscribe to [Telegram Premium]() and increase download speeds \(speedIncreaseFactor) times."
case .upload:
var speedIncreaseFactor = 10
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_upload"] as? Double {
speedIncreaseFactor = Int(value)
}
title = "Upload speed limited"
text = "Subscribe to [Telegram Premium]() and increase upload speeds \(speedIncreaseFactor) times."
}
let content: UndoOverlayContent = .universal(animation: "anim_speed_low", scale: 0.066, colors: [:], title: title, text: text, customUndoText: nil, timeout: 5.0)
self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .top, action: { [weak self] action in
guard let self else {
return false
}
switch action {
case .info:
let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .reactions, forceDark: false, dismissed: nil)
self.push(controller)
return true
default:
break
}
return false
}), in: .current)
})
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }

View File

@ -117,32 +117,44 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
} }
var displayActionsPanel = false var displayActionsPanel = false
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings { if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus {
if !peerStatusSettings.flags.isEmpty { if let peerStatusSettings = contactStatus.peerStatusSettings {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { if !peerStatusSettings.flags.isEmpty {
displayActionsPanel = true if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) { displayActionsPanel = true
displayActionsPanel = true } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
} else if peerStatusSettings.contains(.canShareContact) { displayActionsPanel = true
displayActionsPanel = true } else if peerStatusSettings.contains(.canShareContact) {
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { displayActionsPanel = true
displayActionsPanel = true } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
} else if peerStatusSettings.contains(.suggestAddMembers) { displayActionsPanel = true
} else if peerStatusSettings.contains(.suggestAddMembers) {
displayActionsPanel = true
}
}
if peerStatusSettings.requestChatTitle != nil {
displayActionsPanel = true displayActionsPanel = true
} }
} }
if peerStatusSettings.requestChatTitle != nil {
displayActionsPanel = true
}
} }
if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) { if (selectedContext == nil || selectedContext! <= .pinnedMessage) {
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode { if displayActionsPanel {
return currentPanel if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
} else if let controllerInteraction = controllerInteraction { return currentPanel
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) } else if let controllerInteraction = controllerInteraction {
panel.interfaceInteraction = interfaceInteraction let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
return panel panel.interfaceInteraction = interfaceInteraction
return panel
}
} else if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
if let currentPanel = currentPanel as? ChatManagingBotTitlePanelNode {
return currentPanel
} else {
let panel = ChatManagingBotTitlePanelNode(context: context)
panel.interfaceInteraction = interfaceInteraction
return panel
}
} }
} }

View File

@ -0,0 +1,472 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import ChatPresentationInterfaceState
import ComponentFlow
import AvatarNode
import MultilineTextComponent
import PlainButtonComponent
import ComponentDisplayAdapters
import AccountContext
import TelegramCore
import BundleIconComponent
import ContextUI
import SwiftSignalKit
private final class ChatManagingBotTitlePanelComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let insets: UIEdgeInsets
let peer: EnginePeer
let managesChat: Bool
let isPaused: Bool
let toggleIsPaused: () -> Void
let openSettings: (UIView) -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
insets: UIEdgeInsets,
peer: EnginePeer,
managesChat: Bool,
isPaused: Bool,
toggleIsPaused: @escaping () -> Void,
openSettings: @escaping (UIView) -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.insets = insets
self.peer = peer
self.managesChat = managesChat
self.isPaused = isPaused
self.toggleIsPaused = toggleIsPaused
self.openSettings = openSettings
}
static func ==(lhs: ChatManagingBotTitlePanelComponent, rhs: ChatManagingBotTitlePanelComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings != rhs.strings {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.managesChat != rhs.managesChat {
return false
}
if lhs.isPaused != rhs.isPaused {
return false
}
return true
}
final class View: UIView {
private let title = ComponentView<Empty>()
private let text = ComponentView<Empty>()
private var avatarNode: AvatarNode?
private let actionButton = ComponentView<Empty>()
private let settingsButton = ComponentView<Empty>()
private var component: ChatManagingBotTitlePanelComponent?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ChatManagingBotTitlePanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
let topInset: CGFloat = 6.0
let bottomInset: CGFloat = 6.0
let avatarDiameter: CGFloat = 36.0
let avatarTextSpacing: CGFloat = 10.0
let titleTextSpacing: CGFloat = 1.0
let leftInset: CGFloat = component.insets.left + 12.0
let rightInset: CGFloat = component.insets.right + 10.0
let actionAndSettingsButtonsSpacing: CGFloat = 8.0
//TODO:localize
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.isPaused ? "START" : "STOP", font: Font.semibold(15.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
)),
background: AnyComponent(RoundedRectangle(
color: component.theme.list.itemCheckColors.fillColor,
cornerRadius: nil
)),
effectAlignment: .center,
contentInsets: UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.toggleIsPaused()
},
animateAlpha: true,
animateScale: false,
animateContents: false
)),
environment: {},
containerSize: CGSize(width: 150.0, height: 100.0)
)
let settingsButtonSize = self.settingsButton.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(BundleIconComponent(
name: "Chat/Context Menu/Customize",
tintColor: component.theme.rootController.navigationBar.controlColor
)),
effectAlignment: .center,
minSize: CGSize(width: 1.0, height: 40.0),
contentInsets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
guard let settingsButtonView = self.settingsButton.view else {
return
}
component.openSettings(settingsButtonView)
},
animateAlpha: true,
animateScale: false,
animateContents: false
)),
environment: {},
containerSize: CGSize(width: 150.0, height: 100.0)
)
let maxTextWidth: CGFloat = availableSize.width - leftInset - avatarDiameter - avatarTextSpacing - rightInset - actionButtonSize.width - actionAndSettingsButtonsSpacing - settingsButtonSize.width - 8.0
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))
)),
environment: {},
containerSize: CGSize(width: maxTextWidth, height: 100.0)
)
//TODO:localize
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "bot has access to this chat", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: maxTextWidth, height: 100.0)
)
let size = CGSize(width: availableSize.width, height: topInset + titleSize.height + titleTextSpacing + textSize.height + bottomInset)
let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarDiameter + avatarTextSpacing, y: topInset), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.layer.anchorPoint = CGPoint()
self.addSubview(titleView)
}
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
transition.setPosition(view: titleView, position: titleFrame.origin)
}
let textFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleTextSpacing), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
textView.layer.anchorPoint = CGPoint()
self.addSubview(textView)
}
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
transition.setPosition(view: textView, position: textFrame.origin)
}
let avatarFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - avatarDiameter) * 0.5)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
let avatarNode: AvatarNode
if let current = self.avatarNode {
avatarNode = current
} else {
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
self.avatarNode = avatarNode
self.addSubview(avatarNode.view)
}
avatarNode.frame = avatarFrame
avatarNode.updateSize(size: avatarFrame.size)
avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer)
let settingsButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - settingsButtonSize.width, y: floor((size.height - settingsButtonSize.height) * 0.5)), size: settingsButtonSize)
if let settingsButtonView = self.settingsButton.view {
if settingsButtonView.superview == nil {
self.addSubview(settingsButtonView)
}
transition.setFrame(view: settingsButtonView, frame: settingsButtonFrame)
}
let actionButtonFrame = CGRect(origin: CGPoint(x: settingsButtonFrame.minX - actionAndSettingsButtonsSpacing - actionButtonSize.width, y: floor((size.height - actionButtonSize.height) * 0.5)), size: actionButtonSize)
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {
self.addSubview(actionButtonView)
}
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
}
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode {
private let context: AccountContext
private let separatorNode: ASDisplayNode
private let content = ComponentView<Empty>()
private var chatLocation: ChatLocation?
private var theme: PresentationTheme?
private var managingBot: ChatManagingBot?
init(context: AccountContext) {
self.context = context
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
super.init()
self.addSubnode(self.separatorNode)
}
private func toggleIsPaused() {
guard let chatPeerId = self.chatLocation?.peerId else {
return
}
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] bot in
guard let self else {
return
}
guard let bot else {
return
}
var recipients = bot.recipients
var additionalPeers = recipients.additionalPeers
if additionalPeers.contains(chatPeerId) {
additionalPeers.remove(chatPeerId)
} else {
additionalPeers.insert(chatPeerId)
}
recipients = TelegramBusinessRecipients(
categories: recipients.categories,
additionalPeers: additionalPeers,
exclude: recipients.exclude
)
let _ = self.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot(
id: bot.id,
recipients: recipients,
canReply: bot.canReply
)).startStandalone()
})
}
private func openSettingsMenu(sourceView: UIView) {
guard let interfaceInteraction = self.interfaceInteraction else {
return
}
guard let chatController = interfaceInteraction.chatController() else {
return
}
guard let managingBot = self.managingBot else {
return
}
let _ = managingBot
let strings = self.context.sharedContext.currentPresentationData.with { $0 }.strings
let _ = strings
var items: [ContextMenuItem] = []
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Remove bot from this chat", textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
let _ = self
})))
if let url = managingBot.settingsUrl {
items.append(.action(ContextMenuActionItem(text: "Manage Bot", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
let _ = (self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: false)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
guard let chatController = interfaceInteraction.chatController() else {
return
}
self.context.sharedContext.openResolvedUrl(
result,
context: self.context,
urlContext: .generic,
navigationController: chatController.navigationController as? NavigationController,
forceExternal: false,
openPeer: { [weak self] peer, navigation in
guard let self, let chatController = interfaceInteraction.chatController() else {
return
}
guard let navigationController = chatController.navigationController as? NavigationController else {
return
}
switch navigation {
case let .chat(_, subject, peekData):
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: subject, peekData: peekData))
case let .withBotStartPayload(botStart):
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always))
case let .withAttachBot(attachBotStart):
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
case let .withBotApp(botAppStart):
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botAppStart: botAppStart))
case .info:
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer, let chatController = interfaceInteraction.chatController() else {
return
}
guard let navigationController = chatController.navigationController as? NavigationController else {
return
}
if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
navigationController.pushViewController(controller)
}
})
default:
break
}
},
sendFile: nil,
sendSticker: nil,
requestMessageActionUrlAuth: nil,
joinVoiceChat: nil,
present: { [weak chatController] c, a in
chatController?.present(c, in: .window(.root), with: a)
},
dismissInput: {
},
contentContext: nil,
progress: nil,
completion: nil
)
})
})))
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: chatController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
interfaceInteraction.presentController(contextController, nil)
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
self.chatLocation = interfaceState.chatLocation
self.managingBot = interfaceState.contactStatus?.managingBot
if interfaceState.theme !== self.theme {
self.theme = interfaceState.theme
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
}
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
if let managingBot = interfaceState.contactStatus?.managingBot {
let contentSize = self.content.update(
transition: Transition(transition),
component: AnyComponent(ChatManagingBotTitlePanelComponent(
context: self.context,
theme: interfaceState.theme,
strings: interfaceState.strings,
insets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: 0.0, right: rightInset),
peer: managingBot.bot,
managesChat: managingBot.canReply,
isPaused: managingBot.isPaused,
toggleIsPaused: { [weak self] in
guard let self else {
return
}
self.toggleIsPaused()
},
openSettings: { [weak self] sourceView in
guard let self else {
return
}
self.openSettingsMenu(sourceView: sourceView)
}
)),
environment: {},
containerSize: CGSize(width: width, height: 1000.0)
)
if let contentView = self.content.view {
if contentView.superview == nil {
self.view.addSubview(contentView)
}
transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize))
}
return LayoutResult(backgroundHeight: contentSize.height, insetHeight: contentSize.height, hitTestSlop: 0.0)
} else {
return LayoutResult(backgroundHeight: 0.0, insetHeight: 0.0, hitTestSlop: 0.0)
}
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceView: UIView
init(controller: ViewController, sourceView: UIView) {
self.controller = controller
self.sourceView = sourceView
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -1073,8 +1073,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.requestBiometryAuth() self.requestBiometryAuth()
case "web_app_biometry_update_token": case "web_app_biometry_update_token":
var tokenData: Data? var tokenData: Data?
if let json, let tokenDataValue = json["token"] as? Data { if let json, let tokenDataValue = json["token"] as? String {
tokenData = tokenDataValue tokenData = tokenDataValue.data(using: .utf8)
} }
self.requestBiometryUpdateToken(tokenData: tokenData) self.requestBiometryUpdateToken(tokenData: tokenData)
default: default:
@ -1566,9 +1566,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
data["status"] = isAuthorized ? "authorized" : "failed" data["status"] = isAuthorized ? "authorized" : "failed"
if isAuthorized { if isAuthorized {
if let tokenData { if let tokenData {
data["token"] = tokenData data["token"] = String(data: tokenData, encoding: .utf8) ?? ""
} else { } else {
data["token"] = Data() data["token"] = ""
} }
} }