Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,227 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public struct ChangeAccountPhoneNumberData: Equatable {
public let type: SentAuthorizationCodeType
public let hash: String
public let timeout: Int32?
public let nextType: AuthorizationCodeNextType?
public static func ==(lhs: ChangeAccountPhoneNumberData, rhs: ChangeAccountPhoneNumberData) -> Bool {
if lhs.type != rhs.type {
return false
}
if lhs.hash != rhs.hash {
return false
}
if lhs.timeout != rhs.timeout {
return false
}
if lhs.nextType != rhs.nextType {
return false
}
return true
}
}
public enum RequestChangeAccountPhoneNumberVerificationError {
case invalidPhoneNumber
case limitExceeded
case phoneNumberOccupied
case phoneBanned
case generic
}
func _internal_requestChangeAccountPhoneNumberVerification(account: Account, apiId: Int32, apiHash: String, phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
var flags: Int32 = 0
flags |= 1 << 5 //allowMissedCall
var token: String?
var appSandbox: Api.Bool?
if let pushNotificationConfiguration = pushNotificationConfiguration {
flags |= 1 << 7
flags |= 1 << 8
token = pushNotificationConfiguration.token
appSandbox = pushNotificationConfiguration.isSandbox ? .boolTrue : .boolFalse
}
return account.network.request(Api.functions.account.sendChangePhoneCode(phoneNumber: phoneNumber, settings: .codeSettings(flags: flags, logoutTokens: nil, token: token, appSandbox: appSandbox)), automaticFloodWait: false)
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
return .invalidPhoneNumber
} else if error.errorDescription == "PHONE_NUMBER_OCCUPIED" {
return .phoneNumberOccupied
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
return .phoneBanned
} else {
return .generic
}
}
|> mapToSignal { sentCode -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, _, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
return nil
}
if let value = mapping[receipt] {
return value
}
if receipt == "" && mapping.count == 1 {
return mapping.first?.value
}
return nil
}
|> filter { $0 != nil }
|> take(1)
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|> castError(RequestChangeAccountPhoneNumberVerificationError.self)
|> mapToSignal { firebaseSecret -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> in
guard let firebaseSecret = firebaseSecret else {
return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout)
}
return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret)
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
return .single(false)
}
|> mapError { _ -> RequestChangeAccountPhoneNumberVerificationError in
return .generic
}
|> mapToSignal { success -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> in
if success {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
} else {
return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError)
}
}
}
} else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
}
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
}
private func internalResendChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>, reason: ResendAuthorizationCodeReason?) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
var flags: Int32 = 0
var mappedReason: String?
if let reason {
flags |= 1 << 0
mappedReason = reason.rawValue
}
return account.network.request(Api.functions.auth.resendCode(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: mappedReason), automaticFloodWait: false)
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
return .invalidPhoneNumber
} else if error.errorDescription == "PHONE_NUMBER_OCCUPIED" {
return .phoneNumberOccupied
} else {
return .generic
}
}
|> mapToSignal { sentCode -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if case let .sentCodeTypeFirebaseSms(_, _, _, _, receipt, pushTimeout, _) = type {
return firebaseSecretStream
|> map { mapping -> String? in
guard let receipt = receipt else {
return nil
}
if let value = mapping[receipt] {
return value
}
if receipt == "" && mapping.count == 1 {
return mapping.first?.value
}
return nil
}
|> filter { $0 != nil }
|> take(1)
|> timeout(Double(pushTimeout ?? 15), queue: .mainQueue(), alternate: .single(nil))
|> castError(RequestChangeAccountPhoneNumberVerificationError.self)
|> mapToSignal { firebaseSecret -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> in
guard let firebaseSecret = firebaseSecret else {
return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebasePushTimeout)
}
return sendFirebaseAuthorizationCode(network: account.network, phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, phoneCodeHash: phoneCodeHash, timeout: codeTimeout, firebaseSecret: firebaseSecret)
|> `catch` { _ -> Signal<Bool, SendFirebaseAuthorizationCodeError> in
return .single(false)
}
|> mapError { _ -> RequestChangeAccountPhoneNumberVerificationError in
return .generic
}
|> mapToSignal { success -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> in
if success {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
} else {
return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: .firebaseSendCodeError)
}
}
}
} else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
}
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
}
func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
return internalResendChangeAccountPhoneNumberVerification(account: account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream, reason: nil)
}
public enum ChangeAccountPhoneNumberError {
case generic
case invalidCode
case codeExpired
case limitExceeded
}
func _internal_requestChangeAccountPhoneNumber(account: Account, phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal<Void, ChangeAccountPhoneNumberError> {
let accountPeerId = account.peerId
return account.network.request(Api.functions.account.changePhone(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode), automaticFloodWait: false)
|> mapError { error -> ChangeAccountPhoneNumberError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_CODE_INVALID" {
return .invalidCode
} else if error.errorDescription == "PHONE_CODE_EXPIRED" {
return .codeExpired
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ChangeAccountPhoneNumberError> in
return account.postbox.transaction { transaction -> Void in
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(transaction: transaction, chats: [], users: [result]))
} |> mapError { _ -> ChangeAccountPhoneNumberError in }
}
}
@@ -0,0 +1,56 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public enum NotificationTokenType {
case aps(encrypt: Bool)
case voip
}
func _internal_unregisterNotificationToken(account: Account, token: Data, type: NotificationTokenType, otherAccountUserIds: [PeerId.Id]) -> Signal<Never, NoError> {
let mappedType: Int32
switch type {
case .aps:
mappedType = 1
case .voip:
mappedType = 9
}
return account.network.request(Api.functions.account.unregisterDevice(tokenType: mappedType, token: hexString(token), otherUids: otherAccountUserIds.map({ $0._internalGetInt64Value() })))
|> retryRequest
|> ignoreValues
}
func _internal_registerNotificationToken(account: Account, token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal<Bool, NoError> {
return masterNotificationsKey(account: account, ignoreDisabled: false)
|> mapToSignal { masterKey -> Signal<Bool, NoError> in
let mappedType: Int32
var keyData = Data()
switch type {
case let .aps(encrypt):
mappedType = 1
if encrypt {
keyData = masterKey.data
}
case .voip:
mappedType = 9
keyData = masterKey.data
}
var flags: Int32 = 0
if excludeMutedChats {
flags |= 1 << 0
}
return account.network.request(Api.functions.account.registerDevice(flags: flags, tokenType: mappedType, token: hexString(token), appSandbox: sandbox ? .boolTrue : .boolFalse, secret: Buffer(data: keyData), otherUids: otherAccountUserIds.map({ $0._internalGetInt64Value() })))
|> map { _ -> Bool in
return true
}
|> `catch` { error -> Signal<Bool, NoError> in
if error.errorDescription == "TOKEN_WAS_INVALIDATED" {
return .single(false)
} else {
return .single(true)
}
}
}
}
@@ -0,0 +1,301 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public extension TelegramEngine {
final class AccountData {
private let account: Account
init(account: Account) {
self.account = account
}
public func acceptTermsOfService(id: String) -> Signal<Void, NoError> {
return _internal_acceptTermsOfService(account: self.account, id: id)
}
public func requestChangeAccountPhoneNumberVerification(apiId: Int32, apiHash: String, phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
return _internal_requestChangeAccountPhoneNumberVerification(account: self.account, apiId: apiId, apiHash: apiHash, phoneNumber: phoneNumber, pushNotificationConfiguration: pushNotificationConfiguration, firebaseSecretStream: firebaseSecretStream)
}
public func requestNextChangeAccountPhoneNumberVerification(phoneNumber: String, phoneCodeHash: String, apiId: Int32, apiHash: String, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
return _internal_requestNextChangeAccountPhoneNumberVerification(account: self.account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, apiId: apiId, apiHash: apiHash, firebaseSecretStream: firebaseSecretStream)
}
public func requestChangeAccountPhoneNumber(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal<Void, ChangeAccountPhoneNumberError> {
return _internal_requestChangeAccountPhoneNumber(account: self.account, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode)
}
public func updateAccountPeerName(firstName: String, lastName: String) -> Signal<Void, NoError> {
return _internal_updateAccountPeerName(account: self.account, firstName: firstName, lastName: lastName)
}
public func updateAbout(about: String?) -> Signal<Void, UpdateAboutError> {
return _internal_updateAbout(account: self.account, about: about)
}
public func updateBirthday(birthday: TelegramBirthday?) -> Signal<Never, UpdateBirthdayError> {
return _internal_updateBirthday(account: self.account, birthday: birthday)
}
public func observeAvailableColorOptions(scope: PeerColorsScope) -> Signal<EngineAvailableColorOptions, NoError> {
return _internal_observeAvailableColorOptions(postbox: self.account.postbox, scope: scope)
}
public func updateNameColorAndEmoji(nameColor: UpdateNameColor, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal<Void, UpdateNameColorAndEmojiError> {
return _internal_updateNameColorAndEmoji(account: self.account, nameColor: nameColor, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId)
}
public func unregisterNotificationToken(token: Data, type: NotificationTokenType, otherAccountUserIds: [PeerId.Id]) -> Signal<Never, NoError> {
return _internal_unregisterNotificationToken(account: self.account, token: token, type: type, otherAccountUserIds: otherAccountUserIds)
}
public func registerNotificationToken(token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal<Bool, NoError> {
return _internal_registerNotificationToken(account: self.account, token: token, type: type, sandbox: sandbox, otherAccountUserIds: otherAccountUserIds, excludeMutedChats: excludeMutedChats)
}
public func updateAccountPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: false, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
return _internal_updatePeerPhotoExisting(network: self.account.network, reference: reference)
}
public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: false)
}
public func updateFallbackPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: true, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public func removeFallbackPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: true)
}
public func setStarGiftStatus(starGift: StarGift.UniqueGift, expirationDate: Int32?) -> Signal<Never, NoError> {
let peerId = self.account.peerId
var flags: Int32 = 0
if let _ = expirationDate {
flags |= (1 << 0)
}
var file: TelegramMediaFile?
var patternFile: TelegramMediaFile?
var innerColor: Int32?
var outerColor: Int32?
var patternColor: Int32?
var textColor: Int32?
for attribute in starGift.attributes {
switch attribute {
case let .model(_, fileValue, _):
file = fileValue
case let .pattern(_, patternFileValue, _):
patternFile = patternFileValue
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
innerColor = innerColorValue
outerColor = outerColorValue
patternColor = patternColorValue
textColor = textColorValue
default:
break
}
}
let apiEmojiStatus: Api.EmojiStatus
var emojiStatus: PeerEmojiStatus?
if let file, let patternFile, let innerColor, let outerColor, let patternColor, let textColor {
apiEmojiStatus = .inputEmojiStatusCollectible(flags: flags, collectibleId: starGift.id, until: expirationDate)
emojiStatus = PeerEmojiStatus(content: .starGift(id: starGift.id, fileId: file.fileId.id, title: starGift.title, slug: starGift.slug, patternFileId: patternFile.fileId.id, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor), expirationDate: expirationDate)
} else {
apiEmojiStatus = .emojiStatusEmpty
}
let remoteApply = self.account.network.request(Api.functions.account.updateEmojiStatus(emojiStatus: apiEmojiStatus))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
return self.account.postbox.transaction { transaction -> Void in
if let file, let patternFile {
transaction.storeMediaIfNotPresent(media: file)
transaction.storeMediaIfNotPresent(media: patternFile)
}
if let peer = transaction.getPeer(peerId) as? TelegramUser {
updatePeersCustom(transaction: transaction, peers: [
peer.withUpdatedEmojiStatus(emojiStatus)
], update: { _, updated in
updated
})
}
}
|> ignoreValues
|> then(remoteApply)
}
public func setEmojiStatus(file: TelegramMediaFile?, expirationDate: Int32?) -> Signal<Never, NoError> {
let peerId = self.account.peerId
let remoteApply = self.account.network.request(Api.functions.account.updateEmojiStatus(emojiStatus: file.flatMap({ file in
var flags: Int32 = 0
if let _ = expirationDate {
flags |= (1 << 0)
}
return Api.EmojiStatus.emojiStatus(flags: flags, documentId: file.fileId.id, until: expirationDate)
}) ?? Api.EmojiStatus.emojiStatusEmpty))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
return self.account.postbox.transaction { transaction -> Void in
if let file = file {
transaction.storeMediaIfNotPresent(media: file)
if let entry = CodableEntry(RecentMediaItem(file)) {
let itemEntry = OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: entry)
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStatusEmoji, item: itemEntry, removeTailIfCountExceeds: 32)
}
}
if let peer = transaction.getPeer(peerId) as? TelegramUser {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(file.flatMap({ PeerEmojiStatus(content: .emoji(fileId: $0.fileId.id), expirationDate: expirationDate) }))], update: { _, updated in
updated
})
}
}
|> ignoreValues
|> then(remoteApply)
}
public func updateAccountBusinessHours(businessHours: TelegramBusinessHours?) -> Signal<Never, NoError> {
let peerId = self.account.peerId
var flags: Int32 = 0
if businessHours != nil {
flags |= 1 << 0
}
let remoteApply: Signal<Never, NoError> = self.account.network.request(Api.functions.account.updateBusinessWorkHours(flags: flags, businessWorkHours: businessHours?.apiBusinessHours))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
return self.account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
let current = current as? CachedUserData ?? CachedUserData()
return current.withUpdatedBusinessHours(businessHours)
})
}
|> ignoreValues
|> then(remoteApply)
}
public func updateAccountBusinessLocation(businessLocation: TelegramBusinessLocation?) -> Signal<Never, NoError> {
let peerId = self.account.peerId
var flags: Int32 = 0
var inputGeoPoint: Api.InputGeoPoint?
var inputAddress: String?
if let businessLocation {
flags |= 1 << 0
inputAddress = businessLocation.address
inputGeoPoint = businessLocation.coordinates?.apiInputGeoPoint
if inputGeoPoint != nil {
flags |= 1 << 1
}
}
let remoteApply: Signal<Never, NoError> = self.account.network.request(Api.functions.account.updateBusinessLocation(flags: flags, geoPoint: inputGeoPoint, address: inputAddress))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
return self.account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
let current = current as? CachedUserData ?? CachedUserData()
return current.withUpdatedBusinessLocation(businessLocation)
})
}
|> ignoreValues
|> then(remoteApply)
}
public func shortcutMessageList(onlyRemote: Bool) -> Signal<ShortcutMessageList, NoError> {
return _internal_shortcutMessageList(account: self.account, onlyRemote: onlyRemote)
}
public func keepShortcutMessageListUpdated() -> Signal<Never, NoError> {
return _internal_keepShortcutMessagesUpdated(account: self.account)
}
public func editMessageShortcut(id: Int32, shortcut: String) {
let _ = _internal_editMessageShortcut(account: self.account, id: id, shortcut: shortcut).startStandalone()
}
public func deleteMessageShortcuts(ids: [Int32]) {
let _ = _internal_deleteMessageShortcuts(account: self.account, ids: ids).startStandalone()
}
public func reorderMessageShortcuts(ids: [Int32], completion: @escaping () -> Void) {
let _ = _internal_reorderMessageShortcuts(account: self.account, ids: ids, localCompletion: completion).startStandalone()
}
public func sendMessageShortcut(peerId: EnginePeer.Id, id: Int32) {
let _ = _internal_sendMessageShortcut(account: self.account, peerId: peerId, id: id).startStandalone()
}
public func cachedTimeZoneList() -> Signal<TimeZoneList?, NoError> {
return _internal_cachedTimeZoneList(account: self.account)
}
public func keepCachedTimeZoneListUpdated() -> Signal<Never, NoError> {
return _internal_keepCachedTimeZoneListUpdated(account: self.account)
}
public func updateBusinessGreetingMessage(greetingMessage: TelegramBusinessGreetingMessage?) -> Signal<Never, NoError> {
return _internal_updateBusinessGreetingMessage(account: self.account, greetingMessage: greetingMessage)
}
public func updateBusinessAwayMessage(awayMessage: TelegramBusinessAwayMessage?) -> Signal<Never, NoError> {
return _internal_updateBusinessAwayMessage(account: self.account, awayMessage: awayMessage)
}
public func setAccountConnectedBot(bot: TelegramAccountConnectedBot?) -> Signal<Never, NoError> {
return _internal_setAccountConnectedBot(account: self.account, bot: bot)
}
public func updateBusinessIntro(intro: TelegramBusinessIntro?) -> Signal<Never, NoError> {
return _internal_updateBusinessIntro(account: self.account, intro: intro)
}
public func createBusinessChatLink(message: String, entities: [MessageTextEntity], title: String?) -> Signal<TelegramBusinessChatLinks.Link, AddBusinessChatLinkError> {
return _internal_createBusinessChatLink(account: self.account, message: message, entities: entities, title: title)
}
public func editBusinessChatLink(url: String, message: String, entities: [MessageTextEntity], title: String?) -> Signal<TelegramBusinessChatLinks.Link, AddBusinessChatLinkError> {
return _internal_editBusinessChatLink(account: self.account, url: url, message: message, entities: entities, title: title)
}
public func deleteBusinessChatLink(url: String) -> Signal<Never, NoError> {
return _internal_deleteBusinessChatLink(account: self.account, url: url)
}
public func refreshBusinessChatLinks() -> Signal<Never, NoError> {
return _internal_refreshBusinessChatLinks(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
}
public func updatePersonalChannel(personalChannel: TelegramPersonalChannel?) -> Signal<Never, NoError> {
return _internal_updatePersonalChannel(account: self.account, personalChannel: personalChannel)
}
public func updateAdMessagesEnabled(enabled: Bool) -> Signal<Never, AdMessagesEnableError> {
return _internal_updateAdMessagesEnabled(account: self.account, enabled: enabled)
}
}
}
@@ -0,0 +1,66 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
import MtProtoKit
public struct TermsOfServiceUpdate: Equatable {
public let id: String
public let text: String
public let entities: [MessageTextEntity]
public let ageConfirmation: Int32?
init(id: String, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?) {
self.id = id
self.text = text
self.entities = entities
self.ageConfirmation = ageConfirmation
}
}
extension TermsOfServiceUpdate {
init?(apiTermsOfService: Api.help.TermsOfService) {
switch apiTermsOfService {
case let .termsOfService(_, id, text, entities, minAgeConfirm):
let idData: String
switch id {
case let .dataJSON(data):
idData = data
}
self.init(id: idData, text: text, entities: messageTextEntitiesFromApiEntities(entities), ageConfirmation: minAgeConfirm)
}
}
}
func _internal_acceptTermsOfService(account: Account, id: String) -> Signal<Void, NoError> {
return account.network.request(Api.functions.help.acceptTermsOfService(id: .dataJSON(data: id)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .complete()
}
|> mapToSignal { [weak account] _ -> Signal<Void, NoError> in
account?.stateManager.modifyTermsOfServiceUpdate({ _ in nil })
return .complete()
}
}
func managedTermsOfServiceUpdates(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
let poll = network.request(Api.functions.help.getTermsOfServiceUpdate())
|> retryRequest
|> mapToSignal { [weak stateManager] result -> Signal<Void, NoError> in
var updated: TermsOfServiceUpdate?
switch result {
case let .termsOfServiceUpdate(_, termsOfService):
updated = TermsOfServiceUpdate(apiTermsOfService: termsOfService)
case .termsOfServiceUpdateEmpty:
break
}
stateManager?.modifyTermsOfServiceUpdate { _ in
return updated
}
return .complete()
}
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
@@ -0,0 +1,114 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_updateAccountPeerName(account: Account, firstName: String, lastName: String) -> Signal<Void, NoError> {
let accountPeerId = account.peerId
return account.network.request(Api.functions.account.updateProfile(flags: (1 << 0) | (1 << 1), firstName: firstName, lastName: lastName, about: nil))
|> map { result -> Api.User? in
return result
}
|> `catch` { _ in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
return account.postbox.transaction { transaction -> Void in
if let result = result {
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(transaction: transaction, chats: [], users: [result]))
}
}
}
}
public enum UpdateAboutError {
case generic
}
func _internal_updateAbout(account: Account, about: String?) -> Signal<Void, UpdateAboutError> {
return account.network.request(Api.functions.account.updateProfile(flags: about == nil ? 0 : (1 << 2), firstName: nil, lastName: nil, about: about))
|> mapError { _ -> UpdateAboutError in
return .generic
}
|> mapToSignal { apiUser -> Signal<Void, UpdateAboutError> in
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedAbout(about)
} else {
return current
}
})
}
|> castError(UpdateAboutError.self)
}
}
public enum UpdateNameColor {
case preset(color: PeerNameColor, backgroundEmojiId: Int64?)
case collectible(PeerCollectibleColor)
}
public enum UpdateNameColorAndEmojiError {
case generic
}
func _internal_updateNameColorAndEmoji(account: Account, nameColor: UpdateNameColor, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal<Void, UpdateNameColorAndEmojiError> {
return account.postbox.transaction { transaction -> Signal<Peer, NoError> in
guard let peer = transaction.getPeer(account.peerId) as? TelegramUser else {
return .complete()
}
var nameColorValue: PeerColor
var backgroundEmojiIdValue: Int64?
switch nameColor {
case let .preset(color, backgroundEmojiId):
nameColorValue = .preset(color)
backgroundEmojiIdValue = backgroundEmojiId
case let .collectible(collectibleColor):
nameColorValue = .collectible(collectibleColor)
backgroundEmojiIdValue = collectibleColor.backgroundEmojiId
}
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedNameColor(nameColorValue).withUpdatedBackgroundEmojiId(backgroundEmojiIdValue).withUpdatedProfileColor(profileColor).withUpdatedProfileBackgroundEmojiId(profileBackgroundEmojiId)], update: { _, updated in
return updated
})
return .single(peer)
}
|> switchToLatest
|> castError(UpdateNameColorAndEmojiError.self)
|> mapToSignal { _ -> Signal<Void, UpdateNameColorAndEmojiError> in
let inputRepliesColor: Api.PeerColor
switch nameColor {
case let .preset(color, backgroundEmojiId):
var flags: Int32 = (1 << 0)
if let _ = backgroundEmojiId {
flags |= (1 << 1)
}
inputRepliesColor = .peerColor(flags: flags, color: color.rawValue, backgroundEmojiId: backgroundEmojiId)
case let .collectible(collectibleColor):
inputRepliesColor = .inputPeerColorCollectible(collectibleId: collectibleColor.collectibleId)
}
var flagsProfile: Int32 = 0
if let _ = profileColor {
flagsProfile |= (1 << 0)
}
if let _ = profileBackgroundEmojiId {
flagsProfile |= (1 << 1)
}
return combineLatest(
account.network.request(Api.functions.account.updateColor(flags: (1 << 2), color: inputRepliesColor)),
account.network.request(Api.functions.account.updateColor(flags: (1 << 1) | (1 << 2), color: .peerColor(flags: flagsProfile, color: profileColor?.rawValue ?? 0, backgroundEmojiId: profileBackgroundEmojiId)))
)
|> mapError { _ -> UpdateNameColorAndEmojiError in
return .generic
}
|> mapToSignal { _, _ -> Signal<Void, UpdateNameColorAndEmojiError> in
return .complete()
}
}
}