mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Add 'submodules/TelegramCore/' from commit '9561227540'
git-subtree-dir: submodules/TelegramCore git-subtree-mainline:971273e8f8git-subtree-split:9561227540
This commit is contained in:
commit
5c1613d104
419 changed files with 128415 additions and 0 deletions
25
submodules/TelegramCore/.gitignore
vendored
Normal file
25
submodules/TelegramCore/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
fastlane/README.md
|
||||
fastlane/report.xml
|
||||
fastlane/test_output/*
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
.DS_Store
|
||||
*.dSYM
|
||||
*.dSYM.zip
|
||||
*.ipa
|
||||
*/xcuserdata/*
|
||||
TelegramCore.xcodeproj/*
|
||||
44
submodules/TelegramCore/BUCK
Normal file
44
submodules/TelegramCore/BUCK
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'configs_with_config', 'combined_config')
|
||||
load('//tools:buck_defs.bzl', 'SHARED_CONFIGS', 'EXTENSION_LIB_SPECIFIC_CONFIG')
|
||||
|
||||
apple_library(
|
||||
name = 'TelegramCorePrivateModule',
|
||||
srcs = glob([
|
||||
'TelegramCore/**/*.m',
|
||||
'TelegramCore/**/*.c',
|
||||
'third-party/libphonenumber-iOS/*.m',
|
||||
]),
|
||||
headers = glob([
|
||||
'TelegramCore/**/*.h',
|
||||
'third-party/libphonenumber-iOS/*.h',
|
||||
]),
|
||||
header_namespace = 'TelegramCorePrivateModule',
|
||||
exported_headers = glob([
|
||||
'TelegramCore/**/*.h',
|
||||
'third-party/libphonenumber-iOS/*.h',
|
||||
], exclude = ['TelegramCore/TelegramCore.h']),
|
||||
modular = True,
|
||||
visibility = ['//submodules/TelegramCore:TelegramCore'],
|
||||
deps = [
|
||||
'//submodules/MtProtoKit:MtProtoKit',
|
||||
],
|
||||
)
|
||||
|
||||
apple_library(
|
||||
name = 'TelegramCore',
|
||||
srcs = glob([
|
||||
'TelegramCore/**/*.swift'
|
||||
]),
|
||||
configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])),
|
||||
swift_compiler_flags = [
|
||||
'-suppress-warnings',
|
||||
'-application-extension',
|
||||
],
|
||||
visibility = ['PUBLIC'],
|
||||
deps = [
|
||||
':TelegramCorePrivateModule',
|
||||
'//submodules/SSignalKit:SwiftSignalKit',
|
||||
'//submodules/MtProtoKit:MtProtoKit',
|
||||
'//submodules/Postbox:Postbox',
|
||||
],
|
||||
)
|
||||
194
submodules/TelegramCore/TelegramCore/AccessSecureId.swift
Normal file
194
submodules/TelegramCore/TelegramCore/AccessSecureId.swift
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import MtProtoKitMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
import TelegramCorePrivateModule
|
||||
|
||||
private enum GenerateSecureSecretError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func encryptSecureData(key: Data, iv: Data, data: Data, decrypt: Bool) -> Data? {
|
||||
if data.count % 16 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CryptoAES(!decrypt, key, iv, data)
|
||||
}
|
||||
|
||||
func verifySecureSecret(_ data: Data) -> Bool {
|
||||
guard data.withUnsafeBytes({ (bytes: UnsafePointer<UInt8>) -> Bool in
|
||||
var checksum: UInt32 = 0
|
||||
for i in 0 ..< data.count {
|
||||
checksum += UInt32(bytes.advanced(by: i).pointee)
|
||||
checksum = checksum % 255
|
||||
}
|
||||
if checksum == 239 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func decryptedSecureSecret(encryptedSecretData: Data, password: String, derivation: TwoStepSecurePasswordDerivation, id: Int64) -> Data? {
|
||||
guard let passwordHash = securePasswordKDF(password: password, derivation: derivation) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let secretKey = passwordHash.subdata(in: 0 ..< 32)
|
||||
let iv = passwordHash.subdata(in: 32 ..< (32 + 16))
|
||||
|
||||
guard let decryptedSecret = CryptoAES(false, secretKey, iv, encryptedSecretData) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !verifySecureSecret(decryptedSecret) {
|
||||
return nil
|
||||
}
|
||||
|
||||
let secretHashData = sha256Digest(decryptedSecret)
|
||||
var secretId: Int64 = 0
|
||||
secretHashData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
memcpy(&secretId, bytes, 8)
|
||||
}
|
||||
|
||||
if secretId != id {
|
||||
return nil
|
||||
}
|
||||
|
||||
return decryptedSecret
|
||||
}
|
||||
|
||||
func encryptedSecureSecret(secretData: Data, password: String, inputDerivation: TwoStepSecurePasswordDerivation) -> (data: Data, salt: TwoStepSecurePasswordDerivation, id: Int64)? {
|
||||
let secretHashData = sha256Digest(secretData)
|
||||
var secretId: Int64 = 0
|
||||
secretHashData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
memcpy(&secretId, bytes, 8)
|
||||
}
|
||||
|
||||
guard let (passwordHash, updatedDerivation) = securePasswordUpdateKDF(password: password, derivation: inputDerivation) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let secretKey = passwordHash.subdata(in: 0 ..< 32)
|
||||
let iv = passwordHash.subdata(in: 32 ..< (32 + 16))
|
||||
|
||||
guard let encryptedSecret = CryptoAES(true, secretKey, iv, secretData) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if decryptedSecureSecret(encryptedSecretData: encryptedSecret, password: password, derivation: updatedDerivation, id: secretId) != secretData {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (encryptedSecret, updatedDerivation, secretId)
|
||||
}
|
||||
|
||||
func generateSecureSecretData() -> Data? {
|
||||
var secretData = Data(count: 32)
|
||||
let secretDataCount = secretData.count
|
||||
|
||||
guard secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<Int8>) -> Bool in
|
||||
let copyResult = SecRandomCopyBytes(nil, 32, bytes)
|
||||
return copyResult == errSecSuccess
|
||||
}) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) in
|
||||
while true {
|
||||
var checksum: UInt32 = 0
|
||||
for i in 0 ..< secretDataCount {
|
||||
checksum += UInt32(bytes.advanced(by: i).pointee)
|
||||
checksum = checksum % 255
|
||||
}
|
||||
if checksum == 239 {
|
||||
break
|
||||
} else {
|
||||
var i = secretDataCount - 1
|
||||
inner: while i >= 0 {
|
||||
var byte = bytes.advanced(by: i).pointee
|
||||
if byte != 0xff {
|
||||
byte += 1
|
||||
bytes.advanced(by: i).pointee = byte
|
||||
break inner
|
||||
} else {
|
||||
byte = 0
|
||||
bytes.advanced(by: i).pointee = byte
|
||||
}
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return secretData
|
||||
}
|
||||
|
||||
private func generateSecureSecret(network: Network, password: String) -> Signal<Data, GenerateSecureSecretError> {
|
||||
guard let secretData = generateSecureSecretData() else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return updateTwoStepVerificationSecureSecret(network: network, password: password, secret: secretData)
|
||||
|> mapError { _ -> GenerateSecureSecretError in
|
||||
return .generic
|
||||
}
|
||||
|> map { _ -> Data in
|
||||
return secretData
|
||||
}
|
||||
}
|
||||
|
||||
public struct SecureIdAccessContext: Equatable {
|
||||
let secret: Data
|
||||
let id: Int64
|
||||
}
|
||||
|
||||
public enum SecureIdAccessError {
|
||||
case generic
|
||||
case passwordError(AuthorizationPasswordVerificationError)
|
||||
case secretPasswordMismatch
|
||||
}
|
||||
|
||||
public func accessSecureId(network: Network, password: String) -> Signal<(context: SecureIdAccessContext, settings: TwoStepVerificationSettings), SecureIdAccessError> {
|
||||
return requestTwoStepVerifiationSettings(network: network, password: password)
|
||||
|> mapError { error -> SecureIdAccessError in
|
||||
return .passwordError(error)
|
||||
}
|
||||
|> mapToSignal { settings -> Signal<(context: SecureIdAccessContext, settings: TwoStepVerificationSettings), SecureIdAccessError> in
|
||||
if let secureSecret = settings.secureSecret {
|
||||
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: secureSecret.data, password: password, derivation: secureSecret.derivation, id: secureSecret.id) {
|
||||
return .single((SecureIdAccessContext(secret: decryptedSecret, id: secureSecret.id), settings))
|
||||
} else {
|
||||
return .fail(.secretPasswordMismatch)
|
||||
}
|
||||
} else {
|
||||
return generateSecureSecret(network: network, password: password)
|
||||
|> mapError { _ -> SecureIdAccessError in
|
||||
return SecureIdAccessError.generic
|
||||
}
|
||||
|> map { decryptedSecret in
|
||||
let secretHashData = sha256Digest(decryptedSecret)
|
||||
var secretId: Int64 = 0
|
||||
secretHashData.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
memcpy(&secretId, bytes, 8)
|
||||
}
|
||||
return (SecureIdAccessContext(secret: decryptedSecret, id: secretId), settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1445
submodules/TelegramCore/TelegramCore/Account.swift
Normal file
1445
submodules/TelegramCore/TelegramCore/Account.swift
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,37 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public enum AccountEnvironment: Int32 {
|
||||
case production = 0
|
||||
case test = 1
|
||||
}
|
||||
|
||||
public final class AccountEnvironmentAttribute: AccountRecordAttribute {
|
||||
public let environment: AccountEnvironment
|
||||
|
||||
public init(environment: AccountEnvironment) {
|
||||
self.environment = environment
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.environment = AccountEnvironment(rawValue: decoder.decodeInt32ForKey("environment", orElse: 0)) ?? .production
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.environment.rawValue, forKey: "environment")
|
||||
}
|
||||
|
||||
public func isEqual(to: AccountRecordAttribute) -> Bool {
|
||||
guard let to = to as? AccountEnvironmentAttribute else {
|
||||
return false
|
||||
}
|
||||
if self.environment != to.environment {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,553 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct PeerChatInfo {
|
||||
var notificationSettings: PeerNotificationSettings
|
||||
}
|
||||
|
||||
final class AccountInitialState {
|
||||
let state: AuthorizedAccountState.State
|
||||
let peerIds: Set<PeerId>
|
||||
let chatStates: [PeerId: PeerChatState]
|
||||
let peerChatInfos: [PeerId: PeerChatInfo]
|
||||
let peerIdsRequiringLocalChatState: Set<PeerId>
|
||||
let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]
|
||||
let cloudReadStates: [PeerId: PeerReadState]
|
||||
let channelsToPollExplicitely: Set<PeerId>
|
||||
|
||||
init(state: AuthorizedAccountState.State, peerIds: Set<PeerId>, peerIdsRequiringLocalChatState: Set<PeerId>, chatStates: [PeerId: PeerChatState], peerChatInfos: [PeerId: PeerChatInfo], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState], channelsToPollExplicitely: Set<PeerId>) {
|
||||
self.state = state
|
||||
self.peerIds = peerIds
|
||||
self.chatStates = chatStates
|
||||
self.peerIdsRequiringLocalChatState = peerIdsRequiringLocalChatState
|
||||
self.peerChatInfos = peerChatInfos
|
||||
self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps
|
||||
self.cloudReadStates = cloudReadStates
|
||||
self.channelsToPollExplicitely = channelsToPollExplicitely
|
||||
}
|
||||
}
|
||||
|
||||
enum AccountStateUpdatePinnedItemIdsOperation {
|
||||
case pin(PinnedItemId)
|
||||
case unpin(PinnedItemId)
|
||||
case reorder([PinnedItemId])
|
||||
case sync
|
||||
}
|
||||
|
||||
enum AccountStateUpdateStickerPacksOperation {
|
||||
case add(Api.messages.StickerSet)
|
||||
case reorder(SynchronizeInstalledStickerPacksOperationNamespace, [Int64])
|
||||
case sync
|
||||
}
|
||||
|
||||
enum AccountStateNotificationSettingsSubject {
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
enum AccountStateGlobalNotificationSettingsSubject {
|
||||
case privateChats
|
||||
case groups
|
||||
case channels
|
||||
}
|
||||
|
||||
enum AccountStateMutationOperation {
|
||||
case AddMessages([StoreMessage], AddMessagesLocation)
|
||||
case DeleteMessagesWithGlobalIds([Int32])
|
||||
case DeleteMessages([MessageId])
|
||||
case EditMessage(MessageId, StoreMessage)
|
||||
case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults)
|
||||
case UpdateMedia(MediaId, Media?)
|
||||
case ReadInbox(MessageId)
|
||||
case ReadOutbox(MessageId, Int32?)
|
||||
case ResetReadState(peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32, markedUnread: Bool?)
|
||||
case ResetIncomingReadState(groupId: PeerGroupId, peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, count: Int32, pts: Int32)
|
||||
case UpdatePeerChatUnreadMark(PeerId, MessageId.Namespace, Bool)
|
||||
case ResetMessageTagSummary(PeerId, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange)
|
||||
case ReadGroupFeedInbox(PeerGroupId, MessageIndex)
|
||||
case UpdateState(AuthorizedAccountState.State)
|
||||
case UpdateChannelState(PeerId, ChannelState)
|
||||
case UpdateNotificationSettings(AccountStateNotificationSettingsSubject, PeerNotificationSettings)
|
||||
case UpdateGlobalNotificationSettings(AccountStateGlobalNotificationSettingsSubject, MessageNotificationSettings)
|
||||
case MergeApiChats([Api.Chat])
|
||||
case UpdatePeer(PeerId, (Peer?) -> Peer?)
|
||||
case UpdateIsContact(PeerId, Bool)
|
||||
case UpdateCachedPeerData(PeerId, (CachedPeerData?) -> CachedPeerData?)
|
||||
case MergeApiUsers([Api.User])
|
||||
case MergePeerPresences([PeerId: Api.UserStatus], Bool)
|
||||
case UpdateSecretChat(chat: Api.EncryptedChat, timestamp: Int32)
|
||||
case AddSecretMessages([Api.EncryptedMessage])
|
||||
case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32)
|
||||
case AddPeerInputActivity(chatPeerId: PeerId, peerId: PeerId?, activity: PeerInputActivity?)
|
||||
case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation)
|
||||
case ReadMessageContents((PeerId?, [Int32]))
|
||||
case UpdateMessageImpressionCount(MessageId, Int32)
|
||||
case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation)
|
||||
case UpdateRecentGifs
|
||||
case UpdateChatInputState(PeerId, SynchronizeableChatInputState?)
|
||||
case UpdateCall(Api.PhoneCall)
|
||||
case UpdateLangPack(String, Api.LangPackDifference?)
|
||||
case UpdateMinAvailableMessage(MessageId)
|
||||
case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool)
|
||||
case UpdatePeersNearby([PeerNearby])
|
||||
}
|
||||
|
||||
struct AccountMutableState {
|
||||
let initialState: AccountInitialState
|
||||
let branchOperationIndex: Int
|
||||
|
||||
var operations: [AccountStateMutationOperation] = []
|
||||
|
||||
var state: AuthorizedAccountState.State
|
||||
var peers: [PeerId: Peer]
|
||||
var chatStates: [PeerId: PeerChatState]
|
||||
var peerChatInfos: [PeerId: PeerChatInfo]
|
||||
var referencedMessageIds: Set<MessageId>
|
||||
var storedMessages: Set<MessageId>
|
||||
var readInboxMaxIds: [PeerId: MessageId]
|
||||
var namespacesWithHolesFromPreviousState: [PeerId: Set<MessageId.Namespace>]
|
||||
|
||||
var storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]
|
||||
var displayAlerts: [(text: String, isDropAuth: Bool)] = []
|
||||
|
||||
var insertedPeers: [PeerId: Peer] = [:]
|
||||
|
||||
var preCachedResources: [(MediaResource, Data)] = []
|
||||
|
||||
var updatedMaxMessageId: Int32?
|
||||
var updatedQts: Int32?
|
||||
|
||||
var externallyUpdatedPeerId = Set<PeerId>()
|
||||
|
||||
init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedMessageIds: Set<MessageId>, initialStoredMessages: Set<MessageId>, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]) {
|
||||
self.initialState = initialState
|
||||
self.state = initialState.state
|
||||
self.peers = initialPeers
|
||||
self.referencedMessageIds = initialReferencedMessageIds
|
||||
self.storedMessages = initialStoredMessages
|
||||
self.readInboxMaxIds = initialReadInboxMaxIds
|
||||
self.chatStates = initialState.chatStates
|
||||
self.peerChatInfos = initialState.peerChatInfos
|
||||
self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp
|
||||
self.branchOperationIndex = 0
|
||||
self.namespacesWithHolesFromPreviousState = [:]
|
||||
}
|
||||
|
||||
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], chatStates: [PeerId: PeerChatState], peerChatInfos: [PeerId: PeerChatInfo], referencedMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: Set<MessageId.Namespace>], displayAlerts: [(text: String, isDropAuth: Bool)], branchOperationIndex: Int) {
|
||||
self.initialState = initialState
|
||||
self.operations = operations
|
||||
self.state = state
|
||||
self.peers = peers
|
||||
self.chatStates = chatStates
|
||||
self.referencedMessageIds = referencedMessageIds
|
||||
self.storedMessages = storedMessages
|
||||
self.peerChatInfos = peerChatInfos
|
||||
self.readInboxMaxIds = readInboxMaxIds
|
||||
self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp
|
||||
self.namespacesWithHolesFromPreviousState = namespacesWithHolesFromPreviousState
|
||||
self.displayAlerts = displayAlerts
|
||||
self.branchOperationIndex = branchOperationIndex
|
||||
}
|
||||
|
||||
func branch() -> AccountMutableState {
|
||||
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, chatStates: self.chatStates, peerChatInfos: self.peerChatInfos, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count)
|
||||
}
|
||||
|
||||
mutating func merge(_ other: AccountMutableState) {
|
||||
self.referencedMessageIds.formUnion(other.referencedMessageIds)
|
||||
for i in other.branchOperationIndex ..< other.operations.count {
|
||||
self.addOperation(other.operations[i])
|
||||
}
|
||||
for (_, peer) in other.insertedPeers {
|
||||
self.peers[peer.id] = peer
|
||||
}
|
||||
self.preCachedResources.append(contentsOf: other.preCachedResources)
|
||||
self.externallyUpdatedPeerId.formUnion(other.externallyUpdatedPeerId)
|
||||
for (peerId, namespaces) in other.namespacesWithHolesFromPreviousState {
|
||||
if self.namespacesWithHolesFromPreviousState[peerId] == nil {
|
||||
self.namespacesWithHolesFromPreviousState[peerId] = Set()
|
||||
}
|
||||
for namespace in namespaces {
|
||||
self.namespacesWithHolesFromPreviousState[peerId]!.insert(namespace)
|
||||
}
|
||||
}
|
||||
self.displayAlerts.append(contentsOf: other.displayAlerts)
|
||||
}
|
||||
|
||||
mutating func addPreCachedResource(_ resource: MediaResource, data: Data) {
|
||||
self.preCachedResources.append((resource, data))
|
||||
}
|
||||
|
||||
mutating func addExternallyUpdatedPeerId(_ peerId: PeerId) {
|
||||
self.externallyUpdatedPeerId.insert(peerId)
|
||||
}
|
||||
|
||||
mutating func addMessages(_ messages: [StoreMessage], location: AddMessagesLocation) {
|
||||
self.addOperation(.AddMessages(messages, location))
|
||||
}
|
||||
|
||||
mutating func addDisplayAlert(_ text: String, isDropAuth: Bool) {
|
||||
self.displayAlerts.append((text: text, isDropAuth: isDropAuth))
|
||||
}
|
||||
|
||||
mutating func deleteMessagesWithGlobalIds(_ globalIds: [Int32]) {
|
||||
self.addOperation(.DeleteMessagesWithGlobalIds(globalIds))
|
||||
}
|
||||
|
||||
mutating func deleteMessages(_ messageIds: [MessageId]) {
|
||||
self.addOperation(.DeleteMessages(messageIds))
|
||||
}
|
||||
|
||||
mutating func editMessage(_ id: MessageId, message: StoreMessage) {
|
||||
self.addOperation(.EditMessage(id, message))
|
||||
}
|
||||
|
||||
mutating func updateMessagePoll(_ id: MediaId, poll: Api.Poll?, results: Api.PollResults) {
|
||||
self.addOperation(.UpdateMessagePoll(id, poll, results))
|
||||
}
|
||||
|
||||
mutating func updateMedia(_ id: MediaId, media: Media?) {
|
||||
self.addOperation(.UpdateMedia(id, media))
|
||||
}
|
||||
|
||||
mutating func readInbox(_ messageId: MessageId) {
|
||||
self.addOperation(.ReadInbox(messageId))
|
||||
}
|
||||
|
||||
mutating func readOutbox(_ messageId: MessageId, timestamp: Int32?) {
|
||||
self.addOperation(.ReadOutbox(messageId, timestamp))
|
||||
}
|
||||
|
||||
mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) {
|
||||
self.addOperation(.ReadGroupFeedInbox(groupId, index))
|
||||
}
|
||||
|
||||
mutating func resetReadState(_ peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32, markedUnread: Bool?) {
|
||||
self.addOperation(.ResetReadState(peerId: peerId, namespace: namespace, maxIncomingReadId: maxIncomingReadId, maxOutgoingReadId: maxOutgoingReadId, maxKnownId: maxKnownId, count: count, markedUnread: markedUnread))
|
||||
}
|
||||
|
||||
mutating func resetIncomingReadState(groupId: PeerGroupId, peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, count: Int32, pts: Int32) {
|
||||
self.addOperation(.ResetIncomingReadState(groupId: groupId, peerId: peerId, namespace: namespace, maxIncomingReadId: maxIncomingReadId, count: count, pts: pts))
|
||||
}
|
||||
|
||||
mutating func updatePeerChatUnreadMark(_ peerId: PeerId, namespace: MessageId.Namespace, value: Bool) {
|
||||
self.addOperation(.UpdatePeerChatUnreadMark(peerId, namespace, value))
|
||||
}
|
||||
|
||||
mutating func resetMessageTagSummary(_ peerId: PeerId, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) {
|
||||
self.addOperation(.ResetMessageTagSummary(peerId, namespace, count, range))
|
||||
}
|
||||
|
||||
mutating func updateState(_ state: AuthorizedAccountState.State) {
|
||||
if self.initialState.state.seq != state.qts {
|
||||
self.updatedQts = state.qts
|
||||
}
|
||||
self.addOperation(.UpdateState(state))
|
||||
}
|
||||
|
||||
mutating func updateChannelState(_ peerId: PeerId, state: ChannelState) {
|
||||
self.addOperation(.UpdateChannelState(peerId, state))
|
||||
}
|
||||
|
||||
mutating func updateNotificationSettings(_ subject: AccountStateNotificationSettingsSubject, notificationSettings: PeerNotificationSettings) {
|
||||
self.addOperation(.UpdateNotificationSettings(subject, notificationSettings))
|
||||
}
|
||||
|
||||
mutating func updateGlobalNotificationSettings(_ subject: AccountStateGlobalNotificationSettingsSubject, notificationSettings: MessageNotificationSettings) {
|
||||
self.addOperation(.UpdateGlobalNotificationSettings(subject, notificationSettings))
|
||||
}
|
||||
|
||||
mutating func setNeedsHoleFromPreviousState(peerId: PeerId, namespace: MessageId.Namespace) {
|
||||
if self.namespacesWithHolesFromPreviousState[peerId] == nil {
|
||||
self.namespacesWithHolesFromPreviousState[peerId] = Set()
|
||||
}
|
||||
self.namespacesWithHolesFromPreviousState[peerId]!.insert(namespace)
|
||||
}
|
||||
|
||||
mutating func mergeChats(_ chats: [Api.Chat]) {
|
||||
self.addOperation(.MergeApiChats(chats))
|
||||
}
|
||||
|
||||
mutating func updatePeer(_ id: PeerId, _ f: @escaping (Peer?) -> Peer?) {
|
||||
self.addOperation(.UpdatePeer(id, f))
|
||||
}
|
||||
|
||||
mutating func updatePeerIsContact(_ id: PeerId, isContact: Bool) {
|
||||
self.addOperation(.UpdateIsContact(id, isContact))
|
||||
}
|
||||
|
||||
mutating func updateCachedPeerData(_ id: PeerId, _ f: @escaping (CachedPeerData?) -> CachedPeerData?) {
|
||||
self.addOperation(.UpdateCachedPeerData(id, f))
|
||||
}
|
||||
|
||||
mutating func updateLangPack(langCode: String, difference: Api.LangPackDifference?) {
|
||||
self.addOperation(.UpdateLangPack(langCode, difference))
|
||||
}
|
||||
|
||||
mutating func updateMinAvailableMessage(_ id: MessageId) {
|
||||
self.addOperation(.UpdateMinAvailableMessage(id))
|
||||
}
|
||||
|
||||
mutating func updatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool) {
|
||||
self.addOperation(.UpdatePeerChatInclusion(peerId: peerId, groupId: groupId, changedGroup: changedGroup))
|
||||
}
|
||||
|
||||
mutating func updatePeersNearby(_ peersNearby: [PeerNearby]) {
|
||||
self.addOperation(.UpdatePeersNearby(peersNearby))
|
||||
}
|
||||
|
||||
mutating func mergeUsers(_ users: [Api.User]) {
|
||||
self.addOperation(.MergeApiUsers(users))
|
||||
|
||||
var presences: [PeerId: Api.UserStatus] = [:]
|
||||
for user in users {
|
||||
switch user {
|
||||
case let .user(_, id, _, _, _, _, _, _, status, _, _, _, _):
|
||||
if let status = status {
|
||||
presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: id)] = status
|
||||
}
|
||||
break
|
||||
case .userEmpty:
|
||||
break
|
||||
}
|
||||
}
|
||||
if !presences.isEmpty {
|
||||
self.addOperation(.MergePeerPresences(presences, false))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func mergePeerPresences(_ presences: [PeerId: Api.UserStatus], explicit: Bool) {
|
||||
self.addOperation(.MergePeerPresences(presences, explicit))
|
||||
}
|
||||
|
||||
mutating func updateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) {
|
||||
self.addOperation(.UpdateSecretChat(chat: chat, timestamp: timestamp))
|
||||
}
|
||||
|
||||
mutating func addSecretMessages(_ messages: [Api.EncryptedMessage]) {
|
||||
self.addOperation(.AddSecretMessages(messages))
|
||||
}
|
||||
|
||||
mutating func readSecretOutbox(peerId: PeerId, timestamp: Int32, actionTimestamp: Int32) {
|
||||
self.addOperation(.ReadSecretOutbox(peerId: peerId, maxTimestamp: timestamp, actionTimestamp: actionTimestamp))
|
||||
}
|
||||
|
||||
mutating func addPeerInputActivity(chatPeerId: PeerId, peerId: PeerId?, activity: PeerInputActivity?) {
|
||||
self.addOperation(.AddPeerInputActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity))
|
||||
}
|
||||
|
||||
mutating func addUpdatePinnedItemIds(groupId: PeerGroupId, operation: AccountStateUpdatePinnedItemIdsOperation) {
|
||||
self.addOperation(.UpdatePinnedItemIds(groupId, operation))
|
||||
}
|
||||
|
||||
mutating func addReadMessagesContents(_ peerIdsAndMessageIds: (PeerId?, [Int32])) {
|
||||
self.addOperation(.ReadMessageContents(peerIdsAndMessageIds))
|
||||
}
|
||||
|
||||
mutating func addUpdateMessageImpressionCount(id: MessageId, count: Int32) {
|
||||
self.addOperation(.UpdateMessageImpressionCount(id, count))
|
||||
}
|
||||
|
||||
mutating func addUpdateInstalledStickerPacks(_ operation: AccountStateUpdateStickerPacksOperation) {
|
||||
self.addOperation(.UpdateInstalledStickerPacks(operation))
|
||||
}
|
||||
|
||||
mutating func addUpdateRecentGifs() {
|
||||
self.addOperation(.UpdateRecentGifs)
|
||||
}
|
||||
|
||||
mutating func addUpdateChatInputState(peerId: PeerId, state: SynchronizeableChatInputState?) {
|
||||
self.addOperation(.UpdateChatInputState(peerId, state))
|
||||
}
|
||||
|
||||
mutating func addUpdateCall(_ call: Api.PhoneCall) {
|
||||
self.addOperation(.UpdateCall(call))
|
||||
}
|
||||
|
||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||
switch operation {
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby:
|
||||
break
|
||||
case let .AddMessages(messages, location):
|
||||
for message in messages {
|
||||
if case let .Id(id) = message.id {
|
||||
self.storedMessages.insert(id)
|
||||
if case .UpperHistoryBlock = location {
|
||||
if (id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup) && id.namespace == Namespaces.Message.Cloud {
|
||||
if let updatedMaxMessageId = self.updatedMaxMessageId {
|
||||
if updatedMaxMessageId < id.id {
|
||||
self.updatedMaxMessageId = id.id
|
||||
}
|
||||
} else {
|
||||
self.updatedMaxMessageId = id.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
self.referencedMessageIds.insert(attribute.messageId)
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
case let .UpdateState(state):
|
||||
self.state = state
|
||||
case let .UpdateChannelState(peerId, channelState):
|
||||
self.chatStates[peerId] = channelState
|
||||
case let .UpdateNotificationSettings(subject, notificationSettings):
|
||||
if case let .peer(peerId) = subject {
|
||||
if var currentInfo = self.peerChatInfos[peerId] {
|
||||
currentInfo.notificationSettings = notificationSettings
|
||||
self.peerChatInfos[peerId] = currentInfo
|
||||
}
|
||||
}
|
||||
case .UpdateGlobalNotificationSettings:
|
||||
break
|
||||
case let .MergeApiChats(chats):
|
||||
for chat in chats {
|
||||
if let groupOrChannel = mergeGroupOrChannel(lhs: peers[chat.peerId], rhs: chat) {
|
||||
peers[groupOrChannel.id] = groupOrChannel
|
||||
insertedPeers[groupOrChannel.id] = groupOrChannel
|
||||
}
|
||||
}
|
||||
case let .MergeApiUsers(users):
|
||||
for apiUser in users {
|
||||
if let user = TelegramUser.merge(peers[apiUser.peerId] as? TelegramUser, rhs: apiUser) {
|
||||
peers[user.id] = user
|
||||
insertedPeers[user.id] = user
|
||||
}
|
||||
}
|
||||
case let .UpdatePeer(id, f):
|
||||
let peer = self.peers[id]
|
||||
if let updatedPeer = f(peer) {
|
||||
peers[id] = updatedPeer
|
||||
insertedPeers[id] = updatedPeer
|
||||
}
|
||||
case let .ReadInbox(messageId):
|
||||
let current = self.readInboxMaxIds[messageId.peerId]
|
||||
if current == nil || current! < messageId {
|
||||
self.readInboxMaxIds[messageId.peerId] = messageId
|
||||
}
|
||||
case let .ResetReadState(peerId, namespace, maxIncomingReadId, _, _, _, _):
|
||||
let current = self.readInboxMaxIds[peerId]
|
||||
if namespace == Namespaces.Message.Cloud {
|
||||
if current == nil || current!.id < maxIncomingReadId {
|
||||
self.readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: namespace, id: maxIncomingReadId)
|
||||
}
|
||||
}
|
||||
case let .ResetIncomingReadState(_, peerId, namespace, maxIncomingReadId, _, _):
|
||||
let current = self.readInboxMaxIds[peerId]
|
||||
if namespace == Namespaces.Message.Cloud {
|
||||
if current == nil || current!.id < maxIncomingReadId {
|
||||
self.readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: namespace, id: maxIncomingReadId)
|
||||
}
|
||||
}
|
||||
case let .ResetMessageTagSummary(peerId, namespace, count, range):
|
||||
break
|
||||
}
|
||||
|
||||
self.operations.append(operation)
|
||||
}
|
||||
}
|
||||
|
||||
struct AccountFinalState {
|
||||
let state: AccountMutableState
|
||||
let shouldPoll: Bool
|
||||
let incomplete: Bool
|
||||
}
|
||||
|
||||
struct AccountReplayedFinalState {
|
||||
let state: AccountFinalState
|
||||
let addedIncomingMessageIds: [MessageId]
|
||||
let addedSecretMessageIds: [MessageId]
|
||||
let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]]
|
||||
let updatedWebpages: [MediaId: TelegramMediaWebpage]
|
||||
let updatedCalls: [Api.PhoneCall]
|
||||
let updatedPeersNearby: [PeerNearby]?
|
||||
let isContactUpdates: [(PeerId, Bool)]
|
||||
let delayNotificatonsUntil: Int32?
|
||||
}
|
||||
|
||||
struct AccountFinalStateEvents {
|
||||
let addedIncomingMessageIds: [MessageId]
|
||||
let updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]]
|
||||
let updatedWebpages: [MediaId: TelegramMediaWebpage]
|
||||
let updatedCalls: [Api.PhoneCall]
|
||||
let updatedPeersNearby: [PeerNearby]?
|
||||
let isContactUpdates: [(PeerId, Bool)]
|
||||
let displayAlerts: [(text: String, isDropAuth: Bool)]
|
||||
let delayNotificatonsUntil: Int32?
|
||||
let updatedMaxMessageId: Int32?
|
||||
let updatedQts: Int32?
|
||||
let externallyUpdatedPeerId: Set<PeerId>
|
||||
|
||||
var isEmpty: Bool {
|
||||
return self.addedIncomingMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty
|
||||
}
|
||||
|
||||
init(addedIncomingMessageIds: [MessageId] = [], updatedTypingActivities: [PeerId: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set()) {
|
||||
self.addedIncomingMessageIds = addedIncomingMessageIds
|
||||
self.updatedTypingActivities = updatedTypingActivities
|
||||
self.updatedWebpages = updatedWebpages
|
||||
self.updatedCalls = updatedCalls
|
||||
self.updatedPeersNearby = updatedPeersNearby
|
||||
self.isContactUpdates = isContactUpdates
|
||||
self.displayAlerts = displayAlerts
|
||||
self.delayNotificatonsUntil = delayNotificatonsUntil
|
||||
self.updatedMaxMessageId = updatedMaxMessageId
|
||||
self.updatedQts = updatedQts
|
||||
self.externallyUpdatedPeerId = externallyUpdatedPeerId
|
||||
}
|
||||
|
||||
init(state: AccountReplayedFinalState) {
|
||||
self.addedIncomingMessageIds = state.addedIncomingMessageIds
|
||||
self.updatedTypingActivities = state.updatedTypingActivities
|
||||
self.updatedWebpages = state.updatedWebpages
|
||||
self.updatedCalls = state.updatedCalls
|
||||
self.updatedPeersNearby = state.updatedPeersNearby
|
||||
self.isContactUpdates = state.isContactUpdates
|
||||
self.displayAlerts = state.state.state.displayAlerts
|
||||
self.delayNotificatonsUntil = state.delayNotificatonsUntil
|
||||
self.updatedMaxMessageId = state.state.state.updatedMaxMessageId
|
||||
self.updatedQts = state.state.state.updatedQts
|
||||
self.externallyUpdatedPeerId = state.state.state.externallyUpdatedPeerId
|
||||
}
|
||||
|
||||
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
|
||||
var delayNotificatonsUntil = self.delayNotificatonsUntil
|
||||
if let other = self.delayNotificatonsUntil {
|
||||
if delayNotificatonsUntil == nil || other > delayNotificatonsUntil! {
|
||||
delayNotificatonsUntil = other
|
||||
}
|
||||
}
|
||||
var updatedMaxMessageId: Int32?
|
||||
var updatedQts: Int32?
|
||||
if let lhsMaxMessageId = self.updatedMaxMessageId, let rhsMaxMessageId = other.updatedMaxMessageId {
|
||||
updatedMaxMessageId = max(lhsMaxMessageId, rhsMaxMessageId)
|
||||
} else {
|
||||
updatedMaxMessageId = self.updatedMaxMessageId ?? other.updatedMaxMessageId
|
||||
}
|
||||
if let lhsQts = self.updatedQts, let rhsQts = other.updatedQts {
|
||||
updatedQts = max(lhsQts, rhsQts)
|
||||
} else {
|
||||
updatedQts = self.updatedQts ?? other.updatedQts
|
||||
}
|
||||
|
||||
let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId)
|
||||
|
||||
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId)
|
||||
}
|
||||
}
|
||||
418
submodules/TelegramCore/TelegramCore/AccountManager.swift
Normal file
418
submodules/TelegramCore/TelegramCore/AccountManager.swift
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private enum AccountKind {
|
||||
case authorized
|
||||
case unauthorized
|
||||
}
|
||||
|
||||
private var declaredEncodables: Void = {
|
||||
declareEncodable(AuthAccountRecord.self, f: { AuthAccountRecord(decoder: $0) })
|
||||
declareEncodable(UnauthorizedAccountState.self, f: { UnauthorizedAccountState(decoder: $0) })
|
||||
declareEncodable(AuthorizedAccountState.self, f: { AuthorizedAccountState(decoder: $0) })
|
||||
declareEncodable(TelegramUser.self, f: { TelegramUser(decoder: $0) })
|
||||
declareEncodable(TelegramGroup.self, f: { TelegramGroup(decoder: $0) })
|
||||
declareEncodable(TelegramChannel.self, f: { TelegramChannel(decoder: $0) })
|
||||
declareEncodable(TelegramMediaImage.self, f: { TelegramMediaImage(decoder: $0) })
|
||||
declareEncodable(TelegramMediaImageRepresentation.self, f: { TelegramMediaImageRepresentation(decoder: $0) })
|
||||
declareEncodable(TelegramMediaContact.self, f: { TelegramMediaContact(decoder: $0) })
|
||||
declareEncodable(TelegramMediaMap.self, f: { TelegramMediaMap(decoder: $0) })
|
||||
declareEncodable(TelegramMediaFile.self, f: { TelegramMediaFile(decoder: $0) })
|
||||
declareEncodable(TelegramMediaFileAttribute.self, f: { TelegramMediaFileAttribute(decoder: $0) })
|
||||
declareEncodable(CloudFileMediaResource.self, f: { CloudFileMediaResource(decoder: $0) })
|
||||
declareEncodable(ChannelState.self, f: { ChannelState(decoder: $0) })
|
||||
declareEncodable(RegularChatState.self, f: { RegularChatState(decoder: $0) })
|
||||
declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) })
|
||||
declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) })
|
||||
declareEncodable(TelegramMediaWebpage.self, f: { TelegramMediaWebpage(decoder: $0) })
|
||||
declareEncodable(ViewCountMessageAttribute.self, f: { ViewCountMessageAttribute(decoder: $0) })
|
||||
declareEncodable(NotificationInfoMessageAttribute.self, f: { NotificationInfoMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramMediaAction.self, f: { TelegramMediaAction(decoder: $0) })
|
||||
declareEncodable(TelegramPeerNotificationSettings.self, f: { TelegramPeerNotificationSettings(decoder: $0) })
|
||||
declareEncodable(CachedUserData.self, f: { CachedUserData(decoder: $0) })
|
||||
declareEncodable(BotInfo.self, f: { BotInfo(decoder: $0) })
|
||||
declareEncodable(CachedGroupData.self, f: { CachedGroupData(decoder: $0) })
|
||||
declareEncodable(CachedChannelData.self, f: { CachedChannelData(decoder: $0) })
|
||||
declareEncodable(TelegramUserPresence.self, f: { TelegramUserPresence(decoder: $0) })
|
||||
declareEncodable(LocalFileMediaResource.self, f: { LocalFileMediaResource(decoder: $0) })
|
||||
declareEncodable(StickerPackCollectionInfo.self, f: { StickerPackCollectionInfo(decoder: $0) })
|
||||
declareEncodable(StickerPackItem.self, f: { StickerPackItem(decoder: $0) })
|
||||
declareEncodable(LocalFileReferenceMediaResource.self, f: { LocalFileReferenceMediaResource(decoder: $0) })
|
||||
declareEncodable(OutgoingMessageInfoAttribute.self, f: { OutgoingMessageInfoAttribute(decoder: $0) })
|
||||
declareEncodable(ForwardSourceInfoAttribute.self, f: { ForwardSourceInfoAttribute(decoder: $0) })
|
||||
declareEncodable(SourceReferenceMessageAttribute.self, f: { SourceReferenceMessageAttribute(decoder: $0) })
|
||||
declareEncodable(EditedMessageAttribute.self, f: { EditedMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReplyMarkupMessageAttribute.self, f: { ReplyMarkupMessageAttribute(decoder: $0) })
|
||||
declareEncodable(CachedResolvedByNamePeer.self, f: { CachedResolvedByNamePeer(decoder: $0) })
|
||||
declareEncodable(OutgoingChatContextResultMessageAttribute.self, f: { OutgoingChatContextResultMessageAttribute(decoder: $0) })
|
||||
declareEncodable(HttpReferenceMediaResource.self, f: { HttpReferenceMediaResource(decoder: $0) })
|
||||
declareEncodable(WebFileReferenceMediaResource.self, f: { WebFileReferenceMediaResource(decoder: $0) })
|
||||
declareEncodable(EmptyMediaResource.self, f: { EmptyMediaResource(decoder: $0) })
|
||||
declareEncodable(TelegramSecretChat.self, f: { TelegramSecretChat(decoder: $0) })
|
||||
declareEncodable(SecretChatState.self, f: { SecretChatState(decoder: $0) })
|
||||
declareEncodable(SecretChatIncomingEncryptedOperation.self, f: { SecretChatIncomingEncryptedOperation(decoder: $0) })
|
||||
declareEncodable(SecretChatIncomingDecryptedOperation.self, f: { SecretChatIncomingDecryptedOperation(decoder: $0) })
|
||||
declareEncodable(SecretChatOutgoingOperation.self, f: { SecretChatOutgoingOperation(decoder: $0) })
|
||||
declareEncodable(SecretFileMediaResource.self, f: { SecretFileMediaResource(decoder: $0) })
|
||||
declareEncodable(CloudChatRemoveMessagesOperation.self, f: { CloudChatRemoveMessagesOperation(decoder: $0) })
|
||||
declareEncodable(AutoremoveTimeoutMessageAttribute.self, f: { AutoremoveTimeoutMessageAttribute(decoder: $0) })
|
||||
declareEncodable(GlobalNotificationSettings.self, f: { GlobalNotificationSettings(decoder: $0) })
|
||||
declareEncodable(CloudChatRemoveChatOperation.self, f: { CloudChatRemoveChatOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizePinnedChatsOperation.self, f: { SynchronizePinnedChatsOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeConsumeMessageContentsOperation.self, f: { SynchronizeConsumeMessageContentsOperation(decoder: $0) })
|
||||
declareEncodable(RecentMediaItem.self, f: { RecentMediaItem(decoder: $0) })
|
||||
declareEncodable(RecentPeerItem.self, f: { RecentPeerItem(decoder: $0) })
|
||||
declareEncodable(RecentHashtagItem.self, f: { RecentHashtagItem(decoder: $0) })
|
||||
declareEncodable(LoggedOutAccountAttribute.self, f: { LoggedOutAccountAttribute(decoder: $0) })
|
||||
declareEncodable(AccountEnvironmentAttribute.self, f: { AccountEnvironmentAttribute(decoder: $0) })
|
||||
declareEncodable(AccountSortOrderAttribute.self, f: { AccountSortOrderAttribute(decoder: $0) })
|
||||
declareEncodable(CloudChatClearHistoryOperation.self, f: { CloudChatClearHistoryOperation(decoder: $0) })
|
||||
declareEncodable(OutgoingContentInfoMessageAttribute.self, f: { OutgoingContentInfoMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ConsumableContentMessageAttribute.self, f: { ConsumableContentMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramMediaGame.self, f: { TelegramMediaGame(decoder: $0) })
|
||||
declareEncodable(TelegramMediaInvoice.self, f: { TelegramMediaInvoice(decoder: $0) })
|
||||
declareEncodable(TelegramMediaWebFile.self, f: { TelegramMediaWebFile(decoder: $0) })
|
||||
declareEncodable(SynchronizeInstalledStickerPacksOperation.self, f: { SynchronizeInstalledStickerPacksOperation(decoder: $0) })
|
||||
declareEncodable(FeaturedStickerPackItem.self, f: { FeaturedStickerPackItem(decoder: $0) })
|
||||
declareEncodable(SynchronizeMarkFeaturedStickerPacksAsSeenOperation.self, f: { SynchronizeMarkFeaturedStickerPacksAsSeenOperation(decoder: $0) })
|
||||
declareEncodable(ArchivedStickerPacksInfo.self, f: { ArchivedStickerPacksInfo(decoder: $0) })
|
||||
declareEncodable(SynchronizeChatInputStateOperation.self, f: { SynchronizeChatInputStateOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeSavedGifsOperation.self, f: { SynchronizeSavedGifsOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeSavedStickersOperation.self, f: { SynchronizeSavedStickersOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeRecentlyUsedMediaOperation.self, f: { SynchronizeRecentlyUsedMediaOperation(decoder: $0) })
|
||||
declareEncodable(CacheStorageSettings.self, f: { CacheStorageSettings(decoder: $0) })
|
||||
declareEncodable(LocalizationSettings.self, f: { LocalizationSettings(decoder: $0) })
|
||||
declareEncodable(LocalizationListState.self, f: { LocalizationListState(decoder: $0) })
|
||||
declareEncodable(ProxySettings.self, f: { ProxySettings(decoder: $0) })
|
||||
declareEncodable(NetworkSettings.self, f: { NetworkSettings(decoder: $0) })
|
||||
declareEncodable(RemoteStorageConfiguration.self, f: { RemoteStorageConfiguration(decoder: $0) })
|
||||
declareEncodable(LimitsConfiguration.self, f: { LimitsConfiguration(decoder: $0) })
|
||||
declareEncodable(VoipConfiguration.self, f: { VoipConfiguration(decoder: $0) })
|
||||
declareEncodable(SuggestedLocalizationEntry.self, f: { SuggestedLocalizationEntry(decoder: $0) })
|
||||
declareEncodable(SynchronizeLocalizationUpdatesOperation.self, f: { SynchronizeLocalizationUpdatesOperation(decoder: $0) })
|
||||
declareEncodable(ChannelMessageStateVersionAttribute.self, f: { ChannelMessageStateVersionAttribute(decoder: $0) })
|
||||
declareEncodable(PeerGroupMessageStateVersionAttribute.self, f: { PeerGroupMessageStateVersionAttribute(decoder: $0) })
|
||||
declareEncodable(CachedSecretChatData.self, f: { CachedSecretChatData(decoder: $0) })
|
||||
declareEncodable(TemporaryTwoStepPasswordToken.self, f: { TemporaryTwoStepPasswordToken(decoder: $0) })
|
||||
declareEncodable(AuthorSignatureMessageAttribute.self, f: { AuthorSignatureMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramMediaExpiredContent.self, f: { TelegramMediaExpiredContent(decoder: $0) })
|
||||
declareEncodable(SavedStickerItem.self, f: { SavedStickerItem(decoder: $0) })
|
||||
declareEncodable(ConsumablePersonalMentionMessageAttribute.self, f: { ConsumablePersonalMentionMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ConsumePersonalMessageAction.self, f: { ConsumePersonalMessageAction(decoder: $0) })
|
||||
declareEncodable(CachedStickerPack.self, f: { CachedStickerPack(decoder: $0) })
|
||||
declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) })
|
||||
declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) })
|
||||
declareEncodable(CachedSecureIdConfiguration.self, f: { CachedSecureIdConfiguration(decoder: $0) })
|
||||
declareEncodable(CachedWallpapersConfiguration.self, f: { CachedWallpapersConfiguration(decoder: $0) })
|
||||
declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) })
|
||||
declareEncodable(ContentPrivacySettings.self, f: { ContentPrivacySettings(decoder: $0) })
|
||||
declareEncodable(TelegramDeviceContactImportedData.self, f: { TelegramDeviceContactImportedData(decoder: $0) })
|
||||
declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) })
|
||||
declareEncodable(CachedStickerQueryResult.self, f: { CachedStickerQueryResult(decoder: $0) })
|
||||
declareEncodable(TelegramWallpaper.self, f: { TelegramWallpaper(decoder: $0) })
|
||||
declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) })
|
||||
declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) })
|
||||
declareEncodable(CachedRecentPeers.self, f: { CachedRecentPeers(decoder: $0) })
|
||||
declareEncodable(AppChangelogState.self, f: { AppChangelogState(decoder: $0) })
|
||||
declareEncodable(AppConfiguration.self, f: { AppConfiguration(decoder: $0) })
|
||||
declareEncodable(JSON.self, f: { JSON(decoder: $0) })
|
||||
declareEncodable(SearchBotsConfiguration.self, f: { SearchBotsConfiguration(decoder: $0) })
|
||||
declareEncodable(AutodownloadSettings.self, f: { AutodownloadSettings(decoder: $0 )})
|
||||
declareEncodable(TelegramMediaPoll.self, f: { TelegramMediaPoll(decoder: $0) })
|
||||
declareEncodable(TelegramMediaUnsupported.self, f: { TelegramMediaUnsupported(decoder: $0) })
|
||||
declareEncodable(ContactsSettings.self, f: { ContactsSettings(decoder: $0) })
|
||||
declareEncodable(EmojiKeywordCollectionInfo.self, f: { EmojiKeywordCollectionInfo(decoder: $0) })
|
||||
declareEncodable(EmojiKeywordItem.self, f: { EmojiKeywordItem(decoder: $0) })
|
||||
declareEncodable(SynchronizeEmojiKeywordsOperation.self, f: { SynchronizeEmojiKeywordsOperation(decoder: $0) })
|
||||
|
||||
declareEncodable(CloudPhotoSizeMediaResource.self, f: { CloudPhotoSizeMediaResource(decoder: $0) })
|
||||
declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) })
|
||||
declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) })
|
||||
declareEncodable(CloudStickerPackThumbnailMediaResource.self, f: { CloudStickerPackThumbnailMediaResource(decoder: $0) })
|
||||
declareEncodable(AccountBackupDataAttribute.self, f: { AccountBackupDataAttribute(decoder: $0) })
|
||||
declareEncodable(ContentRequiresValidationMessageAttribute.self, f: { ContentRequiresValidationMessageAttribute(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
public func initializeAccountManagement() {
|
||||
let _ = declaredEncodables
|
||||
}
|
||||
|
||||
public func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return appGroupPath + "/telegram-data"
|
||||
}
|
||||
|
||||
public func performAppGroupUpgrades(appGroupPath: String, rootPath: String) {
|
||||
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: rootPath), withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
do {
|
||||
var resourceValues = URLResourceValues()
|
||||
resourceValues.isExcludedFromBackup = true
|
||||
var mutableUrl = URL(fileURLWithPath: rootPath)
|
||||
try mutableUrl.setResourceValues(resourceValues)
|
||||
} catch let e {
|
||||
print("\(e)")
|
||||
}
|
||||
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: appGroupPath), includingPropertiesForKeys: [], options: []) {
|
||||
for url in files {
|
||||
if url.lastPathComponent == "accounts-metadata" ||
|
||||
url.lastPathComponent.hasSuffix("logs") ||
|
||||
url.lastPathComponent.hasPrefix("account-") {
|
||||
let _ = try? FileManager.default.moveItem(at: url, to: URL(fileURLWithPath: rootPath + "/" + url.lastPathComponent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class TemporaryAccount {
|
||||
public let id: AccountRecordId
|
||||
public let basePath: String
|
||||
public let postbox: Postbox
|
||||
|
||||
init(id: AccountRecordId, basePath: String, postbox: Postbox) {
|
||||
self.id = id
|
||||
self.basePath = basePath
|
||||
self.postbox = postbox
|
||||
}
|
||||
}
|
||||
|
||||
public func temporaryAccount(manager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<TemporaryAccount, NoError> {
|
||||
return manager.allocatedTemporaryAccountId()
|
||||
|> mapToSignal { id -> Signal<TemporaryAccount, NoError> in
|
||||
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
|
||||
return openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters)
|
||||
|> mapToSignal { result -> Signal<TemporaryAccount, NoError> in
|
||||
switch result {
|
||||
case .upgrading:
|
||||
return .complete()
|
||||
case let .postbox(postbox):
|
||||
return .single(TemporaryAccount(id: id, basePath: path, postbox: postbox))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkInitializationArguments, supplementary: Bool, manager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<AccountResult?, NoError> {
|
||||
return manager.currentAccountRecord(allocateIfNotExists: allocateIfNotExists)
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
return lhs?.0 == rhs?.0
|
||||
})
|
||||
|> mapToSignal { record -> Signal<AccountResult?, NoError> in
|
||||
if let record = record {
|
||||
let reload = ValuePromise<Bool>(true, ignoreRepeated: false)
|
||||
return reload.get()
|
||||
|> mapToSignal { _ -> Signal<AccountResult?, NoError> in
|
||||
let beginWithTestingEnvironment = record.1.contains(where: { attribute in
|
||||
if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
return accountWithId(accountManager: manager, networkArguments: networkArguments, id: record.0, encryptionParameters: encryptionParameters, supplementary: supplementary, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, backupData: nil, auxiliaryMethods: auxiliaryMethods)
|
||||
|> mapToSignal { accountResult -> Signal<AccountResult?, NoError> in
|
||||
let postbox: Postbox
|
||||
let initialKind: AccountKind
|
||||
switch accountResult {
|
||||
case .upgrading:
|
||||
return .complete()
|
||||
case let .unauthorized(account):
|
||||
postbox = account.postbox
|
||||
initialKind = .unauthorized
|
||||
case let .authorized(account):
|
||||
postbox = account.postbox
|
||||
initialKind = .authorized
|
||||
}
|
||||
let updatedKind = postbox.stateView()
|
||||
|> map { view -> Bool in
|
||||
let kind: AccountKind
|
||||
if view.state is AuthorizedAccountState {
|
||||
kind = .authorized
|
||||
} else {
|
||||
kind = .unauthorized
|
||||
}
|
||||
if kind != initialKind {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
return Signal { subscriber in
|
||||
subscriber.putNext(accountResult)
|
||||
|
||||
return updatedKind.start(next: { value in
|
||||
if value {
|
||||
reload.set(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManager, alreadyLoggedOutRemotely: Bool) -> Signal<Void, NoError> {
|
||||
Logger.shared.log("AccountManager", "logoutFromAccount \(id)")
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateRecord(id, { current in
|
||||
if alreadyLoggedOutRemotely {
|
||||
return nil
|
||||
} else if let current = current {
|
||||
var found = false
|
||||
for attribute in current.attributes {
|
||||
if attribute is LoggedOutAccountAttribute {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return current
|
||||
} else {
|
||||
return AccountRecord(id: current.id, attributes: current.attributes + [LoggedOutAccountAttribute()], temporarySessionId: nil)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func managedCleanupAccounts(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<Void, NoError> {
|
||||
let currentTemporarySessionId = accountManager.temporarySessionId
|
||||
return Signal { subscriber in
|
||||
let loggedOutAccounts = Atomic<[AccountRecordId: MetaDisposable]>(value: [:])
|
||||
let _ = (accountManager.transaction { transaction -> Void in
|
||||
for record in transaction.getRecords() {
|
||||
if let temporarySessionId = record.temporarySessionId, temporarySessionId != currentTemporarySessionId {
|
||||
transaction.updateRecord(record.id, { _ in
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}).start()
|
||||
let disposable = accountManager.accountRecords().start(next: { view in
|
||||
var disposeList: [(AccountRecordId, MetaDisposable)] = []
|
||||
var beginList: [(AccountRecordId, [AccountRecordAttribute], MetaDisposable)] = []
|
||||
let _ = loggedOutAccounts.modify { disposables in
|
||||
var validIds: [AccountRecordId: [AccountRecordAttribute]] = [:]
|
||||
outer: for record in view.records {
|
||||
for attribute in record.attributes {
|
||||
if attribute is LoggedOutAccountAttribute {
|
||||
validIds[record.id] = record.attributes
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var disposables = disposables
|
||||
|
||||
for id in disposables.keys {
|
||||
if validIds[id] == nil {
|
||||
disposeList.append((id, disposables[id]!))
|
||||
}
|
||||
}
|
||||
|
||||
for (id, _) in disposeList {
|
||||
disposables.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
for (id, attributes) in validIds {
|
||||
if disposables[id] == nil {
|
||||
let disposable = MetaDisposable()
|
||||
beginList.append((id, attributes, disposable))
|
||||
disposables[id] = disposable
|
||||
}
|
||||
}
|
||||
|
||||
return disposables
|
||||
}
|
||||
for (_, disposable) in disposeList {
|
||||
disposable.dispose()
|
||||
}
|
||||
for (id, attributes, disposable) in beginList {
|
||||
Logger.shared.log("managedCleanupAccounts", "cleanup \(id), current is \(String(describing: view.currentRecord?.id))")
|
||||
disposable.set(cleanupAccount(networkArguments: networkArguments, accountManager: accountManager, id: id, encryptionParameters: encryptionParameters, attributes: attributes, rootPath: rootPath, auxiliaryMethods: auxiliaryMethods).start())
|
||||
}
|
||||
|
||||
var validPaths = Set<String>()
|
||||
for record in view.records {
|
||||
if let temporarySessionId = record.temporarySessionId, temporarySessionId != currentTemporarySessionId {
|
||||
continue
|
||||
}
|
||||
validPaths.insert("\(accountRecordIdPathName(record.id))")
|
||||
}
|
||||
if let record = view.currentAuthAccount {
|
||||
validPaths.insert("\(accountRecordIdPathName(record.id))")
|
||||
}
|
||||
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: rootPath), includingPropertiesForKeys: [], options: []) {
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("account-") {
|
||||
if !validPaths.contains(url.lastPathComponent) {
|
||||
try? FileManager.default.removeItem(at: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanupAccount(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, attributes: [AccountRecordAttribute], rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal<Void, NoError> {
|
||||
let beginWithTestingEnvironment = attributes.contains(where: { attribute in
|
||||
if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
return accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: id, encryptionParameters: encryptionParameters, supplementary: true, rootPath: rootPath, beginWithTestingEnvironment: beginWithTestingEnvironment, backupData: nil, auxiliaryMethods: auxiliaryMethods)
|
||||
|> mapToSignal { account -> Signal<Void, NoError> in
|
||||
switch account {
|
||||
case .upgrading:
|
||||
return .complete()
|
||||
case .unauthorized:
|
||||
return .complete()
|
||||
case let .authorized(account):
|
||||
account.shouldBeServiceTaskMaster.set(.single(.always))
|
||||
return account.network.request(Api.functions.auth.logOut())
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateRecord(id, { _ in
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public final class AccountSortOrderAttribute: AccountRecordAttribute {
|
||||
public let order: Int32
|
||||
|
||||
public init(order: Int32) {
|
||||
self.order = order
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.order = decoder.decodeInt32ForKey("order", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.order, forKey: "order")
|
||||
}
|
||||
|
||||
public func isEqual(to: AccountRecordAttribute) -> Bool {
|
||||
guard let to = to as? AccountSortOrderAttribute else {
|
||||
return false
|
||||
}
|
||||
if self.order != to.order {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
407
submodules/TelegramCore/TelegramCore/AccountState.swift
Normal file
407
submodules/TelegramCore/TelegramCore/AccountState.swift
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
private enum SentAuthorizationCodeTypeValue: Int32 {
|
||||
case otherSession = 0
|
||||
case sms = 1
|
||||
case call = 2
|
||||
case flashCall = 3
|
||||
}
|
||||
|
||||
public enum SentAuthorizationCodeType: PostboxCoding, Equatable {
|
||||
case otherSession(length: Int32)
|
||||
case sms(length: Int32)
|
||||
case call(length: Int32)
|
||||
case flashCall(pattern: String)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
||||
case SentAuthorizationCodeTypeValue.otherSession.rawValue:
|
||||
self = .otherSession(length: decoder.decodeInt32ForKey("l", orElse: 0))
|
||||
case SentAuthorizationCodeTypeValue.sms.rawValue:
|
||||
self = .sms(length: decoder.decodeInt32ForKey("l", orElse: 0))
|
||||
case SentAuthorizationCodeTypeValue.call.rawValue:
|
||||
self = .call(length: decoder.decodeInt32ForKey("l", orElse: 0))
|
||||
case SentAuthorizationCodeTypeValue.flashCall.rawValue:
|
||||
self = .flashCall(pattern: decoder.decodeStringForKey("p", orElse: ""))
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case let .otherSession(length):
|
||||
encoder.encodeInt32(SentAuthorizationCodeTypeValue.otherSession.rawValue, forKey: "v")
|
||||
encoder.encodeInt32(length, forKey: "l")
|
||||
case let .sms(length):
|
||||
encoder.encodeInt32(SentAuthorizationCodeTypeValue.sms.rawValue, forKey: "v")
|
||||
encoder.encodeInt32(length, forKey: "l")
|
||||
case let .call(length):
|
||||
encoder.encodeInt32(SentAuthorizationCodeTypeValue.call.rawValue, forKey: "v")
|
||||
encoder.encodeInt32(length, forKey: "l")
|
||||
case let .flashCall(pattern):
|
||||
encoder.encodeInt32(SentAuthorizationCodeTypeValue.flashCall.rawValue, forKey: "v")
|
||||
encoder.encodeString(pattern, forKey: "p")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: SentAuthorizationCodeType, rhs: SentAuthorizationCodeType) -> Bool {
|
||||
switch lhs {
|
||||
case let .otherSession(length):
|
||||
if case .otherSession(length) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sms(length):
|
||||
if case .sms(length) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .call(length):
|
||||
if case .call(length) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .flashCall(pattern):
|
||||
if case .flashCall(pattern) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum AuthorizationCodeNextType: Int32 {
|
||||
case sms = 0
|
||||
case call = 1
|
||||
case flashCall = 2
|
||||
}
|
||||
|
||||
private enum UnauthorizedAccountStateContentsValue: Int32 {
|
||||
case empty = 0
|
||||
case phoneEntry = 1
|
||||
case confirmationCodeEntry = 2
|
||||
case passwordEntry = 3
|
||||
case signUp = 5
|
||||
case passwordRecovery = 6
|
||||
case awaitingAccountReset = 7
|
||||
}
|
||||
|
||||
public struct UnauthorizedAccountTermsOfService: PostboxCoding, 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
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = decoder.decodeStringForKey("id", orElse: "")
|
||||
self.text = decoder.decodeStringForKey("text", orElse: "")
|
||||
self.entities = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("entities", decoder: { MessageTextEntity(decoder: $0) })) ?? []
|
||||
self.ageConfirmation = decoder.decodeOptionalInt32ForKey("ageConfirmation")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.id, forKey: "id")
|
||||
encoder.encodeString(self.text, forKey: "text")
|
||||
encoder.encodeObjectArray(self.entities, forKey: "entities")
|
||||
if let ageConfirmation = self.ageConfirmation {
|
||||
encoder.encodeInt32(ageConfirmation, forKey: "ageConfirmation")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "ageConfirmation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UnauthorizedAccountTermsOfService {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnauthorizedAccountStateContents: PostboxCoding, Equatable {
|
||||
case empty
|
||||
case phoneEntry(countryCode: Int32, number: String)
|
||||
case confirmationCodeEntry(number: String, type: SentAuthorizationCodeType, hash: String, timeout: Int32?, nextType: AuthorizationCodeNextType?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?, syncContacts: Bool)
|
||||
case passwordEntry(hint: String, number: String?, code: String?, suggestReset: Bool, syncContacts: Bool)
|
||||
case passwordRecovery(hint: String, number: String?, code: String?, emailPattern: String, syncContacts: Bool)
|
||||
case awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool)
|
||||
case signUp(number: String, codeHash: String, code: String, firstName: String, lastName: String, termsOfService: UnauthorizedAccountTermsOfService?, syncContacts: Bool)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
||||
case UnauthorizedAccountStateContentsValue.empty.rawValue:
|
||||
self = .empty
|
||||
case UnauthorizedAccountStateContentsValue.phoneEntry.rawValue:
|
||||
self = .phoneEntry(countryCode: decoder.decodeInt32ForKey("cc", orElse: 1), number: decoder.decodeStringForKey("n", orElse: ""))
|
||||
case UnauthorizedAccountStateContentsValue.confirmationCodeEntry.rawValue:
|
||||
var nextType: AuthorizationCodeNextType?
|
||||
if let value = decoder.decodeOptionalInt32ForKey("nt") {
|
||||
nextType = AuthorizationCodeNextType(rawValue: value)
|
||||
}
|
||||
var termsOfService: (UnauthorizedAccountTermsOfService, Bool)?
|
||||
if let termsValue = decoder.decodeObjectForKey("tos", decoder: { UnauthorizedAccountTermsOfService(decoder: $0) }) as? UnauthorizedAccountTermsOfService {
|
||||
termsOfService = (termsValue, decoder.decodeInt32ForKey("tose", orElse: 0) != 0)
|
||||
}
|
||||
self = .confirmationCodeEntry(number: decoder.decodeStringForKey("num", orElse: ""), type: decoder.decodeObjectForKey("t", decoder: { SentAuthorizationCodeType(decoder: $0) }) as! SentAuthorizationCodeType, hash: decoder.decodeStringForKey("h", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("tm"), nextType: nextType, termsOfService: termsOfService, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
|
||||
case UnauthorizedAccountStateContentsValue.passwordEntry.rawValue:
|
||||
self = .passwordEntry(hint: decoder.decodeStringForKey("h", orElse: ""), number: decoder.decodeOptionalStringForKey("n"), code: decoder.decodeOptionalStringForKey("c"), suggestReset: decoder.decodeInt32ForKey("suggestReset", orElse: 0) != 0, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
|
||||
case UnauthorizedAccountStateContentsValue.passwordRecovery.rawValue:
|
||||
self = .passwordRecovery(hint: decoder.decodeStringForKey("hint", orElse: ""), number: decoder.decodeOptionalStringForKey("number"), code: decoder.decodeOptionalStringForKey("code"), emailPattern: decoder.decodeStringForKey("emailPattern", orElse: ""), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
|
||||
case UnauthorizedAccountStateContentsValue.awaitingAccountReset.rawValue:
|
||||
self = .awaitingAccountReset(protectedUntil: decoder.decodeInt32ForKey("protectedUntil", orElse: 0), number: decoder.decodeOptionalStringForKey("number"), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
|
||||
case UnauthorizedAccountStateContentsValue.signUp.rawValue:
|
||||
self = .signUp(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), code: decoder.decodeStringForKey("c", orElse: ""), firstName: decoder.decodeStringForKey("f", orElse: ""), lastName: decoder.decodeStringForKey("l", orElse: ""), termsOfService: decoder.decodeObjectForKey("tos", decoder: { UnauthorizedAccountTermsOfService(decoder: $0) }) as? UnauthorizedAccountTermsOfService, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
|
||||
default:
|
||||
assertionFailure()
|
||||
self = .empty
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case .empty:
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.empty.rawValue, forKey: "v")
|
||||
case let .phoneEntry(countryCode, number):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.phoneEntry.rawValue, forKey: "v")
|
||||
encoder.encodeInt32(countryCode, forKey: "cc")
|
||||
encoder.encodeString(number, forKey: "n")
|
||||
case let .confirmationCodeEntry(number, type, hash, timeout, nextType, termsOfService, syncContacts):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.confirmationCodeEntry.rawValue, forKey: "v")
|
||||
encoder.encodeString(number, forKey: "num")
|
||||
encoder.encodeObject(type, forKey: "t")
|
||||
encoder.encodeString(hash, forKey: "h")
|
||||
if let timeout = timeout {
|
||||
encoder.encodeInt32(timeout, forKey: "tm")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "tm")
|
||||
}
|
||||
if let nextType = nextType {
|
||||
encoder.encodeInt32(nextType.rawValue, forKey: "nt")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "nt")
|
||||
}
|
||||
if let (termsOfService, exclusive) = termsOfService {
|
||||
encoder.encodeObject(termsOfService, forKey: "tos")
|
||||
encoder.encodeInt32(exclusive ? 1 : 0, forKey: "tose")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "tos")
|
||||
}
|
||||
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
|
||||
case let .passwordEntry(hint, number, code, suggestReset, syncContacts):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.passwordEntry.rawValue, forKey: "v")
|
||||
encoder.encodeString(hint, forKey: "h")
|
||||
if let number = number {
|
||||
encoder.encodeString(number, forKey: "n")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "n")
|
||||
}
|
||||
if let code = code {
|
||||
encoder.encodeString(code, forKey: "c")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "c")
|
||||
}
|
||||
encoder.encodeInt32(suggestReset ? 1 : 0, forKey: "suggestReset")
|
||||
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
|
||||
case let .passwordRecovery(hint, number, code, emailPattern, syncContacts):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.passwordRecovery.rawValue, forKey: "v")
|
||||
encoder.encodeString(hint, forKey: "hint")
|
||||
if let number = number {
|
||||
encoder.encodeString(number, forKey: "number")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "number")
|
||||
}
|
||||
if let code = code {
|
||||
encoder.encodeString(code, forKey: "code")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "code")
|
||||
}
|
||||
encoder.encodeString(emailPattern, forKey: "emailPattern")
|
||||
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
|
||||
case let .awaitingAccountReset(protectedUntil, number, syncContacts):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.awaitingAccountReset.rawValue, forKey: "v")
|
||||
encoder.encodeInt32(protectedUntil, forKey: "protectedUntil")
|
||||
if let number = number {
|
||||
encoder.encodeString(number, forKey: "number")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "number")
|
||||
}
|
||||
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
|
||||
case let .signUp(number, codeHash, code, firstName, lastName, termsOfService, syncContacts):
|
||||
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.signUp.rawValue, forKey: "v")
|
||||
encoder.encodeString(number, forKey: "n")
|
||||
encoder.encodeString(codeHash, forKey: "h")
|
||||
encoder.encodeString(code, forKey: "c")
|
||||
encoder.encodeString(firstName, forKey: "f")
|
||||
encoder.encodeString(lastName, forKey: "l")
|
||||
if let termsOfService = termsOfService {
|
||||
encoder.encodeObject(termsOfService, forKey: "tos")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "tos")
|
||||
}
|
||||
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: UnauthorizedAccountStateContents, rhs: UnauthorizedAccountStateContents) -> Bool {
|
||||
switch lhs {
|
||||
case .empty:
|
||||
if case .empty = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .phoneEntry(countryCode, number):
|
||||
if case .phoneEntry(countryCode, number) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .confirmationCodeEntry(lhsNumber, lhsType, lhsHash, lhsTimeout, lhsNextType, lhsTermsOfService, lhsSyncContacts):
|
||||
if case let .confirmationCodeEntry(rhsNumber, rhsType, rhsHash, rhsTimeout, rhsNextType, rhsTermsOfService, rhsSyncContacts) = rhs {
|
||||
if lhsNumber != rhsNumber {
|
||||
return false
|
||||
}
|
||||
if lhsType != rhsType {
|
||||
return false
|
||||
}
|
||||
if lhsHash != rhsHash {
|
||||
return false
|
||||
}
|
||||
if lhsTimeout != rhsTimeout {
|
||||
return false
|
||||
}
|
||||
if lhsNextType != rhsNextType {
|
||||
return false
|
||||
}
|
||||
if lhsTermsOfService?.0 != rhsTermsOfService?.0 {
|
||||
return false
|
||||
}
|
||||
if lhsTermsOfService?.1 != rhsTermsOfService?.1 {
|
||||
return false
|
||||
}
|
||||
if lhsSyncContacts != rhsSyncContacts {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .passwordEntry(lhsHint, lhsNumber, lhsCode, lhsSuggestReset, lhsSyncContacts):
|
||||
if case let .passwordEntry(rhsHint, rhsNumber, rhsCode, rhsSuggestReset, rhsSyncContacts) = rhs {
|
||||
return lhsHint == rhsHint && lhsNumber == rhsNumber && lhsCode == rhsCode && lhsSuggestReset == rhsSuggestReset && lhsSyncContacts == rhsSyncContacts
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .passwordRecovery(lhsHint, lhsNumber, lhsCode, lhsEmailPattern, lhsSyncContacts):
|
||||
if case let .passwordRecovery(rhsHint, rhsNumber, rhsCode, rhsEmailPattern, rhsSyncContacts) = rhs {
|
||||
return lhsHint == rhsHint && lhsNumber == rhsNumber && lhsCode == rhsCode && lhsEmailPattern == rhsEmailPattern && lhsSyncContacts == rhsSyncContacts
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .awaitingAccountReset(lhsProtectedUntil, lhsNumber, lhsSyncContacts):
|
||||
if case let .awaitingAccountReset(rhsProtectedUntil, rhsNumber, rhsSyncContacts) = rhs {
|
||||
return lhsProtectedUntil == rhsProtectedUntil && lhsNumber == rhsNumber && lhsSyncContacts == rhsSyncContacts
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .signUp(number, codeHash, code, firstName, lastName, termsOfService, syncContacts):
|
||||
if case .signUp(number, codeHash, code, firstName, lastName, termsOfService, syncContacts) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class UnauthorizedAccountState: AccountState {
|
||||
public let isTestingEnvironment: Bool
|
||||
public let masterDatacenterId: Int32
|
||||
public let contents: UnauthorizedAccountStateContents
|
||||
|
||||
public init(isTestingEnvironment: Bool, masterDatacenterId: Int32, contents: UnauthorizedAccountStateContents) {
|
||||
self.isTestingEnvironment = isTestingEnvironment
|
||||
self.masterDatacenterId = masterDatacenterId
|
||||
self.contents = contents
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.isTestingEnvironment = decoder.decodeInt32ForKey("isTestingEnvironment", orElse: 0) != 0
|
||||
self.masterDatacenterId = decoder.decodeInt32ForKey("dc", orElse: 0)
|
||||
self.contents = decoder.decodeObjectForKey("c", decoder: { UnauthorizedAccountStateContents(decoder: $0) }) as! UnauthorizedAccountStateContents
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.isTestingEnvironment ? 1 : 0, forKey: "isTestingEnvironment")
|
||||
encoder.encodeInt32(self.masterDatacenterId, forKey: "dc")
|
||||
encoder.encodeObject(self.contents, forKey: "c")
|
||||
}
|
||||
|
||||
public func equalsTo(_ other: AccountState) -> Bool {
|
||||
guard let other = other as? UnauthorizedAccountState else {
|
||||
return false
|
||||
}
|
||||
if self.isTestingEnvironment != other.isTestingEnvironment {
|
||||
return false
|
||||
}
|
||||
if self.masterDatacenterId != other.masterDatacenterId {
|
||||
return false
|
||||
}
|
||||
if self.contents != other.contents {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension SentAuthorizationCodeType {
|
||||
init(apiType: Api.auth.SentCodeType) {
|
||||
switch apiType {
|
||||
case let .sentCodeTypeApp(length):
|
||||
self = .otherSession(length: length)
|
||||
case let .sentCodeTypeSms(length):
|
||||
self = .sms(length: length)
|
||||
case let .sentCodeTypeCall(length):
|
||||
self = .call(length: length)
|
||||
case let .sentCodeTypeFlashCall(pattern):
|
||||
self = .flashCall(pattern: pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AuthorizationCodeNextType {
|
||||
init(apiType: Api.auth.CodeType) {
|
||||
switch apiType {
|
||||
case .codeTypeSms:
|
||||
self = .sms
|
||||
case .codeTypeCall:
|
||||
self = .call
|
||||
case .codeTypeFlashCall:
|
||||
self = .flashCall
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
1089
submodules/TelegramCore/TelegramCore/AccountStateManager.swift
Normal file
1089
submodules/TelegramCore/TelegramCore/AccountStateManager.swift
Normal file
File diff suppressed because it is too large
Load diff
299
submodules/TelegramCore/TelegramCore/AccountStateReset.swift
Normal file
299
submodules/TelegramCore/TelegramCore/AccountStateReset.swift
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private struct LocalChatListEntryRange {
|
||||
var entries: [ChatListNamespaceEntry]
|
||||
var upperBound: ChatListIndex?
|
||||
var lowerBound: ChatListIndex
|
||||
var count: Int32
|
||||
var hash: UInt32
|
||||
|
||||
var apiHash: Int32 {
|
||||
return Int32(bitPattern: self.hash & UInt32(0x7FFFFFFF))
|
||||
}
|
||||
}
|
||||
|
||||
private func combineHash(_ value: Int32, into hash: inout UInt32) {
|
||||
let low = UInt32(bitPattern: value)
|
||||
hash = (hash &* 20261) &+ low
|
||||
}
|
||||
|
||||
private func combineChatListNamespaceEntryHash(index: ChatListIndex, readState: PeerReadState?, topMessageAttributes: [MessageAttribute], tagSummary: MessageHistoryTagNamespaceSummary?, interfaceState: PeerChatInterfaceState?, into hash: inout UInt32) {
|
||||
/*
|
||||
dialog.pinned ? 1 : 0,
|
||||
dialog.unread_mark ? 1 : 0,
|
||||
dialog.peer.channel_id || dialog.peer.chat_id || dialog.peer.user_id,
|
||||
dialog.top_message.id,
|
||||
top_message.edit_date || top_message.date,
|
||||
dialog.read_inbox_max_id,
|
||||
dialog.read_outbox_max_id,
|
||||
dialog.unread_count,
|
||||
dialog.unread_mentions_count,
|
||||
draft.draft.date || 0
|
||||
|
||||
*/
|
||||
|
||||
combineHash(index.pinningIndex != nil ? 1 : 0, into: &hash)
|
||||
if let readState = readState, readState.markedUnread {
|
||||
combineHash(1, into: &hash)
|
||||
} else {
|
||||
combineHash(0, into: &hash)
|
||||
}
|
||||
combineHash(index.messageIndex.id.peerId.id, into: &hash)
|
||||
combineHash(index.messageIndex.id.id, into: &hash)
|
||||
var timestamp = index.messageIndex.timestamp
|
||||
for attribute in topMessageAttributes {
|
||||
if let attribute = attribute as? EditedMessageAttribute {
|
||||
timestamp = max(timestamp, attribute.date)
|
||||
}
|
||||
}
|
||||
combineHash(timestamp, into: &hash)
|
||||
if let readState = readState, case let .idBased(maxIncomingReadId, maxOutgoingReadId, _, count, _) = readState {
|
||||
combineHash(maxIncomingReadId, into: &hash)
|
||||
combineHash(maxOutgoingReadId, into: &hash)
|
||||
combineHash(count, into: &hash)
|
||||
} else {
|
||||
combineHash(0, into: &hash)
|
||||
combineHash(0, into: &hash)
|
||||
combineHash(0, into: &hash)
|
||||
}
|
||||
|
||||
if let tagSummary = tagSummary {
|
||||
combineHash(tagSummary.count, into: &hash)
|
||||
} else {
|
||||
combineHash(0, into: &hash)
|
||||
}
|
||||
|
||||
if let embeddedState = interfaceState?.chatListEmbeddedState {
|
||||
combineHash(embeddedState.timestamp, into: &hash)
|
||||
} else {
|
||||
combineHash(0, into: &hash)
|
||||
}
|
||||
}
|
||||
|
||||
private func localChatListEntryRanges(_ entries: [ChatListNamespaceEntry], limit: Int) -> [LocalChatListEntryRange] {
|
||||
var result: [LocalChatListEntryRange] = []
|
||||
var currentRange: LocalChatListEntryRange?
|
||||
for i in 0 ..< entries.count {
|
||||
switch entries[i] {
|
||||
case let .peer(index, readState, topMessageAttributes, tagSummary, interfaceState):
|
||||
var updatedRange: LocalChatListEntryRange
|
||||
if let current = currentRange {
|
||||
updatedRange = current
|
||||
} else {
|
||||
updatedRange = LocalChatListEntryRange(entries: [], upperBound: result.last?.lowerBound, lowerBound: index, count: 0, hash: 0)
|
||||
}
|
||||
updatedRange.entries.append(entries[i])
|
||||
updatedRange.lowerBound = index
|
||||
updatedRange.count += 1
|
||||
|
||||
combineChatListNamespaceEntryHash(index: index, readState: readState, topMessageAttributes: topMessageAttributes, tagSummary: tagSummary, interfaceState: interfaceState, into: &updatedRange.hash)
|
||||
|
||||
if Int(updatedRange.count) >= limit {
|
||||
result.append(updatedRange)
|
||||
currentRange = nil
|
||||
} else {
|
||||
currentRange = updatedRange
|
||||
}
|
||||
case .hole:
|
||||
if let currentRangeValue = currentRange {
|
||||
result.append(currentRangeValue)
|
||||
currentRange = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if let currentRangeValue = currentRange {
|
||||
result.append(currentRangeValue)
|
||||
currentRange = nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private struct ResolvedChatListResetRange {
|
||||
let head: Bool
|
||||
let local: LocalChatListEntryRange
|
||||
let remote: FetchedChatList
|
||||
}
|
||||
|
||||
/*func accountStateReset(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
let pinnedChats: Signal<Api.messages.PeerDialogs, NoError> = network.request(Api.functions.messages.getPinnedDialogs(folderId: 0))
|
||||
|> retryRequest
|
||||
let state: Signal<Api.updates.State, NoError> = network.request(Api.functions.updates.getState())
|
||||
|> retryRequest
|
||||
|
||||
return postbox.transaction { transaction -> [ChatListNamespaceEntry] in
|
||||
return transaction.getChatListNamespaceEntries(groupId: .root, namespace: Namespaces.Message.Cloud, summaryTag: MessageTags.unseenPersonalMessage)
|
||||
}
|
||||
|> mapToSignal { localChatListEntries -> Signal<Void, NoError> in
|
||||
let localRanges = localChatListEntryRanges(localChatListEntries, limit: 100)
|
||||
var signal: Signal<ResolvedChatListResetRange?, NoError> = .complete()
|
||||
for i in 0 ..< localRanges.count {
|
||||
let upperBound: MessageIndex
|
||||
let head = i == 0
|
||||
let localRange = localRanges[i]
|
||||
if let rangeUpperBound = localRange.upperBound {
|
||||
upperBound = rangeUpperBound.messageIndex.predecessor()
|
||||
} else {
|
||||
upperBound = MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 0), timestamp: 0)
|
||||
}
|
||||
|
||||
let rangeSignal: Signal<ResolvedChatListResetRange?, NoError> = fetchChatList(postbox: postbox, network: network, location: .general, upperBound: upperBound, hash: localRange.apiHash, limit: localRange.count)
|
||||
|> map { remote -> ResolvedChatListResetRange? in
|
||||
if let remote = remote {
|
||||
return ResolvedChatListResetRange(head: head, local: localRange, remote: remote)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
signal = signal
|
||||
|> then(rangeSignal)
|
||||
}
|
||||
let collectedResolvedRanges: Signal<[ResolvedChatListResetRange], NoError> = signal
|
||||
|> map { next -> [ResolvedChatListResetRange] in
|
||||
if let next = next {
|
||||
return [next]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|> reduceLeft(value: [], f: { list, next in
|
||||
var list = list
|
||||
list.append(contentsOf: next)
|
||||
return list
|
||||
})
|
||||
|
||||
return combineLatest(collectedResolvedRanges, state)
|
||||
|> mapToSignal { collectedRanges, state -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
for range in collectedRanges {
|
||||
let previousPeerIds = transaction.resetChatList(keepPeerNamespaces: [Namespaces.Peer.SecretChat], upperBound: range.local.upperBound ?? ChatListIndex.absoluteUpperBound, lowerBound: range.local.lowerBound)
|
||||
#if DEBUG
|
||||
for peerId in previousPeerIds {
|
||||
print("pre \(peerId) [\(transaction.getPeer(peerId)?.displayTitle ?? "nil")]")
|
||||
}
|
||||
print("pre hash \(range.local.hash)")
|
||||
print("")
|
||||
|
||||
var preRecalculatedHash: UInt32 = 0
|
||||
for entry in range.local.entries {
|
||||
switch entry {
|
||||
case let .peer(index, readState, topMessageAttributes, tagSummary, interfaceState):
|
||||
print("val \(index.messageIndex.id.peerId) [\(transaction.getPeer(index.messageIndex.id.peerId)?.displayTitle ?? "nil")]")
|
||||
combineChatListNamespaceEntryHash(index: index, readState: readState, topMessageAttributes: topMessageAttributes, tagSummary: nil, interfaceState: nil, into: &preRecalculatedHash)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
print("pre recalculated hash \(preRecalculatedHash)")
|
||||
print("")
|
||||
|
||||
var hash: UInt32 = 0
|
||||
range.remote.storeMessages.compactMap({ message -> MessageIndex? in
|
||||
if case let .Id(id) = message.id {
|
||||
if range.remote.topMessageIds[id.peerId] == id {
|
||||
return message.index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).sorted(by: { lhs, rhs in
|
||||
return lhs > rhs
|
||||
}).forEach({ index in
|
||||
var topMessageAttributes: [MessageAttribute] = []
|
||||
for message in range.remote.storeMessages {
|
||||
if case let .Id(id) = message.id, id == index.id {
|
||||
topMessageAttributes = message.attributes
|
||||
}
|
||||
}
|
||||
combineChatListNamespaceEntryHash(index: ChatListIndex(pinningIndex: nil, messageIndex: index), readState: range.remote.readStates[index.id.peerId]?[Namespaces.Message.Cloud], topMessageAttributes: topMessageAttributes, tagSummary: nil, interfaceState: nil, into: &hash)
|
||||
print("upd \(index.id.peerId) [\(transaction.getPeer(index.id.peerId)?.displayTitle ?? "nil")]")
|
||||
})
|
||||
print("upd hash \(hash)")
|
||||
#endif
|
||||
|
||||
updatePeers(transaction: transaction, peers: range.remote.peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: range.remote.peerPresences)
|
||||
|
||||
transaction.updateCurrentPeerNotificationSettings(range.remote.notificationSettings)
|
||||
|
||||
var allPeersWithMessages = Set<PeerId>()
|
||||
for message in range.remote.storeMessages {
|
||||
allPeersWithMessages.insert(message.id.peerId)
|
||||
}
|
||||
|
||||
for (_, messageId) in range.remote.topMessageIds {
|
||||
if messageId.id > 1 {
|
||||
var skipHole = false
|
||||
if let localTopId = transaction.getTopPeerMessageIndex(peerId: messageId.peerId, namespace: messageId.namespace)?.id {
|
||||
if localTopId >= messageId {
|
||||
skipHole = true
|
||||
}
|
||||
}
|
||||
if !skipHole {
|
||||
//transaction.addHole(MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: messageId.id - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = transaction.addMessages(range.remote.storeMessages, location: .UpperHistoryBlock)
|
||||
|
||||
transaction.resetIncomingReadStates(range.remote.readStates)
|
||||
|
||||
for (peerId, chatState) in range.remote.chatStates {
|
||||
if let chatState = chatState as? ChannelState {
|
||||
if let current = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
transaction.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts))
|
||||
} else {
|
||||
transaction.setPeerChatState(peerId, state: chatState)
|
||||
}
|
||||
} else {
|
||||
transaction.setPeerChatState(peerId, state: chatState)
|
||||
}
|
||||
}
|
||||
|
||||
for (peerId, summary) in range.remote.mentionTagSummaries {
|
||||
transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId)
|
||||
}
|
||||
|
||||
let namespacesWithHoles: [PeerId.Namespace: [MessageId.Namespace]] = [
|
||||
Namespaces.Peer.CloudUser: [Namespaces.Message.Cloud],
|
||||
Namespaces.Peer.CloudGroup: [Namespaces.Message.Cloud],
|
||||
Namespaces.Peer.CloudChannel: [Namespaces.Message.Cloud]
|
||||
]
|
||||
for peerId in previousPeerIds {
|
||||
if !allPeersWithMessages.contains(peerId), let namespaces = namespacesWithHoles[peerId.namespace] {
|
||||
for namespace in namespaces {
|
||||
//transaction.addHole(MessageId(peerId: peerId, namespace: namespace, id: Int32.max - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if range.head {
|
||||
transaction.setPinnedItemIds(groupId: nil, itemIds: range.remote.pinnedItemIds ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
if let currentState = transaction.getState() as? AuthorizedAccountState, let embeddedState = currentState.state {
|
||||
switch state {
|
||||
case let .state(pts, _, _, seq, _):
|
||||
transaction.setState(currentState.changedState(AuthorizedAccountState.State(pts: pts, qts: embeddedState.qts, date: embeddedState.date, seq: seq)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
1218
submodules/TelegramCore/TelegramCore/AccountViewTracker.swift
Normal file
1218
submodules/TelegramCore/TelegramCore/AccountViewTracker.swift
Normal file
File diff suppressed because it is too large
Load diff
115
submodules/TelegramCore/TelegramCore/ActiveSessionsContext.swift
Normal file
115
submodules/TelegramCore/TelegramCore/ActiveSessionsContext.swift
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public struct ActiveSessionsContextState: Equatable {
|
||||
public var isLoadingMore: Bool
|
||||
public var sessions: [RecentAccountSession]
|
||||
}
|
||||
|
||||
public final class ActiveSessionsContext {
|
||||
private let account: Account
|
||||
private var _state: ActiveSessionsContextState {
|
||||
didSet {
|
||||
if self._state != oldValue {
|
||||
self._statePromise.set(.single(self._state))
|
||||
}
|
||||
}
|
||||
}
|
||||
private let _statePromise = Promise<ActiveSessionsContextState>()
|
||||
public var state: Signal<ActiveSessionsContextState, NoError> {
|
||||
return self._statePromise.get()
|
||||
}
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
public init(account: Account) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.account = account
|
||||
self._state = ActiveSessionsContextState(isLoadingMore: false, sessions: [])
|
||||
self._statePromise.set(.single(self._state))
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
if self._state.isLoadingMore {
|
||||
return
|
||||
}
|
||||
self._state = ActiveSessionsContextState(isLoadingMore: true, sessions: self._state.sessions)
|
||||
self.disposable.set((requestRecentAccountSessions(account: account)
|
||||
|> map { result -> (sessions: [RecentAccountSession], canLoadMore: Bool) in
|
||||
return (result, false)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] (sessions, canLoadMore) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf._state = ActiveSessionsContextState(isLoadingMore: false, sessions: sessions)
|
||||
}))
|
||||
}
|
||||
|
||||
public func remove(hash: Int64) -> Signal<Never, TerminateSessionError> {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
return terminateAccountSession(account: self.account, hash: hash)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { [weak self] _ -> Signal<Never, TerminateSessionError> in
|
||||
guard let strongSelf = self else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var mergedSessions = strongSelf._state.sessions
|
||||
for i in 0 ..< mergedSessions.count {
|
||||
if mergedSessions[i].hash == hash {
|
||||
mergedSessions.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions)
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
public func removeOther() -> Signal<Never, NoError> {
|
||||
return terminateOtherAccountSessions(account: self.account)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { [weak self] _ -> Signal<Never, NoError> in
|
||||
guard let strongSelf = self else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var mergedSessions = strongSelf._state.sessions
|
||||
for i in (0 ..< mergedSessions.count).reversed() {
|
||||
if mergedSessions[i].hash != 0 {
|
||||
mergedSessions.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions)
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
214
submodules/TelegramCore/TelegramCore/AddPeerMember.swift
Normal file
214
submodules/TelegramCore/TelegramCore/AddPeerMember.swift
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum AddGroupMemberError {
|
||||
case generic
|
||||
case groupFull
|
||||
case privacy
|
||||
}
|
||||
|
||||
public func addGroupMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<Void, AddGroupMemberError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, AddGroupMemberError> in
|
||||
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
|
||||
if let group = peer as? TelegramGroup {
|
||||
return account.network.request(Api.functions.messages.addChatUser(chatId: group.id.id, userId: inputUser, fwdLimit: 100))
|
||||
|> mapError { error -> AddGroupMemberError in
|
||||
switch error.errorDescription {
|
||||
case "USERS_TOO_MUCH":
|
||||
return .groupFull
|
||||
case "USER_PRIVACY_RESTRICTED":
|
||||
return .privacy
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let message = result.messages.first, let timestamp = message.timestamp {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants {
|
||||
var updatedParticipants = participants.participants
|
||||
var found = false
|
||||
for participant in participants.participants {
|
||||
if participant.peerId == memberId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
updatedParticipants.append(.member(id: memberId, invitedBy: account.peerId, invitedAt: timestamp))
|
||||
}
|
||||
return cachedData.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version))
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AddGroupMemberError in return .generic }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} |> mapError { _ -> AddGroupMemberError in return .generic } |> switchToLatest
|
||||
}
|
||||
|
||||
public enum AddChannelMemberError {
|
||||
case generic
|
||||
case restricted
|
||||
case limitExceeded
|
||||
case bot(PeerId)
|
||||
}
|
||||
|
||||
public func addChannelMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> {
|
||||
return fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId)
|
||||
|> mapError { error -> AddChannelMemberError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in
|
||||
return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in
|
||||
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
|
||||
if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
|
||||
let updatedParticipant: ChannelParticipant
|
||||
if let currentParticipant = currentParticipant, case let .member(_, invitedAt, adminInfo, _) = currentParticipant {
|
||||
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: nil)
|
||||
} else {
|
||||
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: nil, banInfo: nil)
|
||||
}
|
||||
return account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: [inputUser]))
|
||||
|> map { [$0] }
|
||||
|> `catch` { error -> Signal<[Api.Updates], AddChannelMemberError> in
|
||||
switch error.errorDescription {
|
||||
case "USERS_TOO_MUCH":
|
||||
return .fail(.limitExceeded)
|
||||
case "USER_PRIVACY_RESTRICTED":
|
||||
return .fail(.restricted)
|
||||
case "USER_BOT":
|
||||
return .fail(.bot(memberId))
|
||||
default:
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in
|
||||
for updates in result {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount, let kickedCount = cachedData.participantsSummary.kickedCount {
|
||||
var updatedMemberCount = memberCount
|
||||
var updatedKickedCount = kickedCount
|
||||
var wasMember = false
|
||||
var wasBanned = false
|
||||
if let currentParticipant = currentParticipant {
|
||||
switch currentParticipant {
|
||||
case .creator:
|
||||
break
|
||||
case let .member(_, _, _, banInfo):
|
||||
if let banInfo = banInfo {
|
||||
wasBanned = true
|
||||
wasMember = !banInfo.rights.flags.contains(.banReadMessages)
|
||||
} else {
|
||||
wasMember = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !wasMember {
|
||||
updatedMemberCount = updatedMemberCount + 1
|
||||
}
|
||||
if wasBanned {
|
||||
updatedKickedCount = max(0, updatedKickedCount - 1)
|
||||
}
|
||||
|
||||
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedMemberCount(updatedMemberCount).withUpdatedKickedCount(updatedKickedCount))
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var presences: [PeerId: PeerPresence] = [:]
|
||||
peers[memberPeer.id] = memberPeer
|
||||
if let presence = transaction.getPeerPresence(peerId: memberPeer.id) {
|
||||
presences[memberPeer.id] = presence
|
||||
}
|
||||
if case let .member(_, _, maybeAdminInfo, maybeBannedInfo) = updatedParticipant {
|
||||
if let adminInfo = maybeAdminInfo {
|
||||
if let peer = transaction.getPeer(adminInfo.promotedBy) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences))
|
||||
}
|
||||
|> mapError { _ -> AddChannelMemberError in return .generic }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AddChannelMemberError in return .generic }
|
||||
|> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
public func addChannelMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, AddChannelMemberError> {
|
||||
let signal = account.postbox.transaction { transaction -> Signal<Void, AddChannelMemberError> in
|
||||
var memberPeerIds: [PeerId:Peer] = [:]
|
||||
var inputUsers: [Api.InputUser] = []
|
||||
for memberId in memberIds {
|
||||
if let peer = transaction.getPeer(memberId) {
|
||||
memberPeerIds[peerId] = peer
|
||||
if let inputUser = apiInputUser(peer) {
|
||||
inputUsers.append(inputUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let peer = transaction.getPeer(peerId), let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
|
||||
|
||||
let signal = account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: inputUsers))
|
||||
|> mapError { error -> AddChannelMemberError in
|
||||
switch error.errorDescription {
|
||||
case "USER_PRIVACY_RESTRICTED":
|
||||
return .restricted
|
||||
case "USERS_TOO_MUCH":
|
||||
return .limitExceeded
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> map { result in
|
||||
account.stateManager.addUpdates(result)
|
||||
account.viewTracker.forceUpdateCachedPeerData(peerId: peerId)
|
||||
}
|
||||
|
||||
return signal
|
||||
} else {
|
||||
return .single(Void())
|
||||
}
|
||||
|
||||
}
|
||||
|> introduceError(AddChannelMemberError.self)
|
||||
|
||||
return signal
|
||||
|> switchToLatest
|
||||
}
|
||||
215
submodules/TelegramCore/TelegramCore/AddressNames.swift
Normal file
215
submodules/TelegramCore/TelegramCore/AddressNames.swift
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum AddressNameFormatError {
|
||||
case startsWithUnderscore
|
||||
case endsWithUnderscore
|
||||
case startsWithDigit
|
||||
case tooShort
|
||||
case invalidCharacters
|
||||
}
|
||||
|
||||
public enum AddressNameAvailability: Equatable {
|
||||
case available
|
||||
case invalid
|
||||
case taken
|
||||
}
|
||||
|
||||
public enum AddressNameDomain {
|
||||
case account
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
public func checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -> AddressNameFormatError? {
|
||||
var index = 0
|
||||
let length = value.count
|
||||
for char in value {
|
||||
if char == "_" {
|
||||
if index == 0 {
|
||||
return .startsWithUnderscore
|
||||
} else if index == length - 1 {
|
||||
return length < 5 ? .tooShort : .endsWithUnderscore
|
||||
}
|
||||
}
|
||||
if index == 0 && char >= "0" && char <= "9" {
|
||||
return .startsWithDigit
|
||||
}
|
||||
if (!((char >= "a" && char <= "z") || (char >= "A" && char <= "Z") || (char >= "0" && char <= "9") || char == "_")) {
|
||||
return .invalidCharacters
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
if length < 5 && (!canEmpty || length != 0) {
|
||||
return .tooShort
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func addressNameAvailability(account: Account, domain: AddressNameDomain, name: String) -> Signal<AddressNameAvailability, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<AddressNameAvailability, NoError> in
|
||||
switch domain {
|
||||
case .account:
|
||||
return account.network.request(Api.functions.account.checkUsername(username: name))
|
||||
|> map { result -> AddressNameAvailability in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
return .available
|
||||
case .boolFalse:
|
||||
return .taken
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
return .single(.invalid)
|
||||
}
|
||||
case let .peer(peerId):
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: name))
|
||||
|> map { result -> AddressNameAvailability in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
return .available
|
||||
case .boolFalse:
|
||||
return .taken
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
return .single(.invalid)
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name))
|
||||
|> map { result -> AddressNameAvailability in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
return .available
|
||||
case .boolFalse:
|
||||
return .taken
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
return .single(.invalid)
|
||||
}
|
||||
} else {
|
||||
return .single(.invalid)
|
||||
}
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public enum UpdateAddressNameError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func updateAddressName(account: Account, domain: AddressNameDomain, name: String?) -> Signal<Void, UpdateAddressNameError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, UpdateAddressNameError> in
|
||||
switch domain {
|
||||
case .account:
|
||||
return account.network.request(Api.functions.account.updateUsername(username: name ?? ""), automaticFloodWait: false)
|
||||
|> mapError { _ -> UpdateAddressNameError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, UpdateAddressNameError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
let user = TelegramUser(user: result)
|
||||
updatePeers(transaction: transaction, peers: [user], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
} |> mapError { _ -> UpdateAddressNameError in return .generic }
|
||||
}
|
||||
case let .peer(peerId):
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.updateUsername(channel: inputChannel, username: name ?? ""), automaticFloodWait: false)
|
||||
|> mapError { _ -> UpdateAddressNameError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, UpdateAddressNameError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if case .boolTrue = result {
|
||||
if let peer = transaction.getPeer(peerId) as? TelegramChannel {
|
||||
var updatedPeer = peer.withUpdatedAddressName(name)
|
||||
if name != nil, let defaultBannedRights = updatedPeer.defaultBannedRights {
|
||||
updatedPeer = updatedPeer.withUpdatedDefaultBannedRights(TelegramChatBannedRights(flags: defaultBannedRights.flags.union([.banPinMessages, .banChangeInfo]), untilDate: Int32.max))
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
}
|
||||
} |> mapError { _ -> UpdateAddressNameError in return .generic }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
} |> mapError { _ -> UpdateAddressNameError in return .generic } |> switchToLatest
|
||||
}
|
||||
|
||||
public func adminedPublicChannels(account: Account) -> Signal<[Peer], NoError> {
|
||||
return account.network.request(Api.functions.channels.getAdminedPublicChannels())
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
||||
var peers: [Peer] = []
|
||||
switch result {
|
||||
case let .chats(apiChats):
|
||||
for chat in apiChats {
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
case let .chatsSlice(_, apiChats):
|
||||
for chat in apiChats {
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
return account.postbox.transaction { transaction -> [Peer] in
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
return peers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChannelAddressNameAssignmentAvailability {
|
||||
case available
|
||||
case unknown
|
||||
case addressNameLimitReached
|
||||
}
|
||||
|
||||
public func channelAddressNameAssignmentAvailability(account: Account, peerId: PeerId?) -> Signal<ChannelAddressNameAssignmentAvailability, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ChannelAddressNameAssignmentAvailability, NoError> in
|
||||
var inputChannel: Api.InputChannel?
|
||||
if let peerId = peerId {
|
||||
if let peer = transaction.getPeer(peerId), let channel = apiInputChannel(peer) {
|
||||
inputChannel = channel
|
||||
}
|
||||
} else {
|
||||
inputChannel = .inputChannelEmpty
|
||||
}
|
||||
if let inputChannel = inputChannel {
|
||||
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: "username"))
|
||||
|> map { _ -> ChannelAddressNameAssignmentAvailability in
|
||||
return .available
|
||||
}
|
||||
|> `catch` { error -> Signal<ChannelAddressNameAssignmentAvailability, NoError> in
|
||||
return .single(.addressNameLimitReached)
|
||||
}
|
||||
} else {
|
||||
return .single(.unknown)
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
2973
submodules/TelegramCore/TelegramCore/Api0.swift
Normal file
2973
submodules/TelegramCore/TelegramCore/Api0.swift
Normal file
File diff suppressed because it is too large
Load diff
19296
submodules/TelegramCore/TelegramCore/Api1.swift
Normal file
19296
submodules/TelegramCore/TelegramCore/Api1.swift
Normal file
File diff suppressed because it is too large
Load diff
2424
submodules/TelegramCore/TelegramCore/Api2.swift
Normal file
2424
submodules/TelegramCore/TelegramCore/Api2.swift
Normal file
File diff suppressed because it is too large
Load diff
5560
submodules/TelegramCore/TelegramCore/Api3.swift
Normal file
5560
submodules/TelegramCore/TelegramCore/Api3.swift
Normal file
File diff suppressed because it is too large
Load diff
143
submodules/TelegramCore/TelegramCore/ApiGroupOrChannel.swift
Normal file
143
submodules/TelegramCore/TelegramCore/ApiGroupOrChannel.swift
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func imageRepresentationsForApiChatPhoto(_ photo: Api.ChatPhoto) -> [TelegramMediaImageRepresentation] {
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
switch photo {
|
||||
case let .chatPhoto(photoSmall, photoBig, dcId):
|
||||
|
||||
let smallResource: TelegramMediaResource
|
||||
let fullSizeResource: TelegramMediaResource
|
||||
switch photoSmall {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
smallResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, sizeSpec: .small, volumeId: volumeId, localId: localId)
|
||||
}
|
||||
switch photoBig {
|
||||
case let .fileLocationToBeDeprecated(volumeId, localId):
|
||||
fullSizeResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, sizeSpec: .fullSize, volumeId: volumeId, localId: localId)
|
||||
}
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: 80.0, height: 80.0), resource: smallResource))
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: fullSizeResource))
|
||||
case .chatPhotoEmpty:
|
||||
break
|
||||
}
|
||||
return representations
|
||||
}
|
||||
|
||||
func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
|
||||
switch chat {
|
||||
case let .chat(flags, id, title, photo, participantsCount, date, version, migratedTo, adminRights, defaultBannedRights):
|
||||
let left = (flags & ((1 << 1) | (1 << 2))) != 0
|
||||
var migrationReference: TelegramGroupToChannelMigrationReference?
|
||||
if let migratedTo = migratedTo {
|
||||
switch migratedTo {
|
||||
case let .inputChannel(channelId, accessHash):
|
||||
migrationReference = TelegramGroupToChannelMigrationReference(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), accessHash: accessHash)
|
||||
case .inputChannelEmpty:
|
||||
break
|
||||
}
|
||||
}
|
||||
var groupFlags = TelegramGroupFlags()
|
||||
var role: TelegramGroupRole = .member
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
role = .creator
|
||||
} else if let adminRights = adminRights {
|
||||
role = .admin(TelegramChatAdminRights(apiAdminRights: adminRights))
|
||||
}
|
||||
if (flags & (1 << 5)) != 0 {
|
||||
groupFlags.insert(.deactivated)
|
||||
}
|
||||
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: imageRepresentationsForApiChatPhoto(photo), participantCount: Int(participantsCount), role: role, membership: left ? .Left : .Member, flags: groupFlags, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init(apiBannedRights:)), migrationReference: migrationReference, creationDate: date, version: Int(version))
|
||||
case let .chatEmpty(id):
|
||||
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
||||
case let .chatForbidden(id, title):
|
||||
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
||||
case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, defaultBannedRights, _/*feed*//*, feedId*/):
|
||||
let participationStatus: TelegramChannelParticipationStatus
|
||||
if (flags & Int32(1 << 1)) != 0 {
|
||||
participationStatus = .kicked
|
||||
} else if (flags & Int32(1 << 2)) != 0 {
|
||||
participationStatus = .left
|
||||
} else {
|
||||
participationStatus = .member
|
||||
}
|
||||
|
||||
let info: TelegramChannelInfo
|
||||
if (flags & Int32(1 << 8)) != 0 {
|
||||
let infoFlags = TelegramChannelGroupFlags()
|
||||
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
||||
} else {
|
||||
var infoFlags = TelegramChannelBroadcastFlags()
|
||||
if (flags & Int32(1 << 11)) != 0 {
|
||||
infoFlags.insert(.messagesShouldHaveSignatures)
|
||||
}
|
||||
if (flags & Int32(1 << 20)) != 0 {
|
||||
infoFlags.insert(.hasDiscussionGroup)
|
||||
}
|
||||
info = .broadcast(TelegramChannelBroadcastInfo(flags: infoFlags))
|
||||
}
|
||||
|
||||
var channelFlags = TelegramChannelFlags()
|
||||
if (flags & Int32(1 << 0)) != 0 {
|
||||
channelFlags.insert(.isCreator)
|
||||
}
|
||||
if (flags & Int32(1 << 7)) != 0 {
|
||||
channelFlags.insert(.isVerified)
|
||||
}
|
||||
if (flags & Int32(1 << 19)) != 0 {
|
||||
channelFlags.insert(.isScam)
|
||||
}
|
||||
|
||||
let restrictionInfo: PeerAccessRestrictionInfo?
|
||||
if let restrictionReason = restrictionReason {
|
||||
restrictionInfo = PeerAccessRestrictionInfo(reason: restrictionReason)
|
||||
} else {
|
||||
restrictionInfo = nil
|
||||
}
|
||||
|
||||
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: version, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
|
||||
case let .channelForbidden(flags, id, accessHash, title, untilDate):
|
||||
let info: TelegramChannelInfo
|
||||
if (flags & Int32(1 << 8)) != 0 {
|
||||
info = .group(TelegramChannelGroupInfo(flags: []))
|
||||
} else {
|
||||
info = .broadcast(TelegramChannelBroadcastInfo(flags: []))
|
||||
}
|
||||
|
||||
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
||||
switch rhs {
|
||||
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
|
||||
return parseTelegramGroupOrChannel(chat: rhs)
|
||||
case let .channel(flags, _, accessHash, title, username, photo, _, _, _, _, _, defaultBannedRights, _/*feed*//*, feedId*/):
|
||||
if accessHash != nil && (flags & (1 << 12)) == 0 {
|
||||
return parseTelegramGroupOrChannel(chat: rhs)
|
||||
} else if let lhs = lhs as? TelegramChannel {
|
||||
var channelFlags = lhs.flags
|
||||
if (flags & Int32(1 << 7)) != 0 {
|
||||
channelFlags.insert(.isVerified)
|
||||
} else {
|
||||
let _ = channelFlags.remove(.isVerified)
|
||||
}
|
||||
var info = lhs.info
|
||||
switch info {
|
||||
case .broadcast:
|
||||
break
|
||||
case .group:
|
||||
let infoFlags = TelegramChannelGroupFlags()
|
||||
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
||||
}
|
||||
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
159
submodules/TelegramCore/TelegramCore/ApiUtils.swift
Normal file
159
submodules/TelegramCore/TelegramCore/ApiUtils.swift
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public enum PeerReference: PostboxCoding, Hashable, Equatable {
|
||||
case user(id: Int32, accessHash: Int64)
|
||||
case group(id: Int32)
|
||||
case channel(id: Int32, accessHash: Int64)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("_r", orElse: 0) {
|
||||
case 0:
|
||||
self = .user(id: decoder.decodeInt32ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0))
|
||||
case 1:
|
||||
self = .group(id: decoder.decodeInt32ForKey("i", orElse: 0))
|
||||
case 2:
|
||||
self = .channel(id: decoder.decodeInt32ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0))
|
||||
default:
|
||||
assertionFailure()
|
||||
self = .user(id: 0, accessHash: 0)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case let .user(id, accessHash):
|
||||
encoder.encodeInt32(0, forKey: "_r")
|
||||
encoder.encodeInt32(id, forKey: "i")
|
||||
encoder.encodeInt64(accessHash, forKey: "h")
|
||||
case let .group(id):
|
||||
encoder.encodeInt32(1, forKey: "_r")
|
||||
encoder.encodeInt32(id, forKey: "i")
|
||||
case let .channel(id, accessHash):
|
||||
encoder.encodeInt32(2, forKey: "_r")
|
||||
encoder.encodeInt32(id, forKey: "i")
|
||||
encoder.encodeInt64(accessHash, forKey: "h")
|
||||
}
|
||||
}
|
||||
|
||||
var id: PeerId {
|
||||
switch self {
|
||||
case let .user(id, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: id)
|
||||
case let .group(id):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: id)
|
||||
case let .channel(id, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: id)
|
||||
}
|
||||
}
|
||||
|
||||
public init?(_ peer: Peer) {
|
||||
switch peer {
|
||||
case let user as TelegramUser:
|
||||
if let accessHash = user.accessHash {
|
||||
self = .user(id: user.id.id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let group as TelegramGroup:
|
||||
self = .group(id: group.id.id)
|
||||
case let channel as TelegramChannel:
|
||||
if let accessHash = channel.accessHash {
|
||||
self = .channel(id: channel.id.id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var inputPeer: Api.InputPeer {
|
||||
switch self {
|
||||
case let .user(id, accessHash):
|
||||
return .inputPeerUser(userId: id, accessHash: accessHash)
|
||||
case let .group(id):
|
||||
return .inputPeerChat(chatId: id)
|
||||
case let .channel(id, accessHash):
|
||||
return .inputPeerChannel(channelId: id, accessHash: accessHash)
|
||||
}
|
||||
}
|
||||
|
||||
var inputUser: Api.InputUser? {
|
||||
if case let .user(id, accessHash) = self {
|
||||
return .inputUser(userId: id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var inputChannel: Api.InputChannel? {
|
||||
if case let .channel(id, accessHash) = self {
|
||||
return .inputChannel(channelId: id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func forceApiInputPeer(_ peer: Peer) -> Api.InputPeer? {
|
||||
switch peer {
|
||||
case let user as TelegramUser:
|
||||
return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash ?? 0)
|
||||
case let group as TelegramGroup:
|
||||
return Api.InputPeer.inputPeerChat(chatId: group.id.id)
|
||||
case let channel as TelegramChannel:
|
||||
if let accessHash = channel.accessHash {
|
||||
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func apiInputPeer(_ peer: Peer) -> Api.InputPeer? {
|
||||
switch peer {
|
||||
case let user as TelegramUser where user.accessHash != nil:
|
||||
return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash!)
|
||||
case let group as TelegramGroup:
|
||||
return Api.InputPeer.inputPeerChat(chatId: group.id.id)
|
||||
case let channel as TelegramChannel:
|
||||
if let accessHash = channel.accessHash {
|
||||
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func apiInputChannel(_ peer: Peer) -> Api.InputChannel? {
|
||||
if let channel = peer as? TelegramChannel, let accessHash = channel.accessHash {
|
||||
return Api.InputChannel.inputChannel(channelId: channel.id.id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func apiInputUser(_ peer: Peer) -> Api.InputUser? {
|
||||
if let user = peer as? TelegramUser, let accessHash = user.accessHash {
|
||||
return Api.InputUser.inputUser(userId: user.id.id, accessHash: accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func apiInputSecretChat(_ peer: Peer) -> Api.InputEncryptedChat? {
|
||||
if let chat = peer as? TelegramSecretChat {
|
||||
return Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: chat.accessHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
51
submodules/TelegramCore/TelegramCore/AppChangelog.swift
Normal file
51
submodules/TelegramCore/TelegramCore/AppChangelog.swift
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
func managedAppChangelog(postbox: Postbox, network: Network, stateManager: AccountStateManager, appVersion: String) -> Signal<Void, NoError> {
|
||||
return stateManager.pollStateUpdateCompletion()
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> AppChangelogState in
|
||||
return transaction.getPreferencesEntry(key: PreferencesKeys.appChangelogState) as? AppChangelogState ?? AppChangelogState.default
|
||||
}
|
||||
|> mapToSignal { appChangelogState -> Signal<Void, NoError> in
|
||||
let appChangelogState = appChangelogState
|
||||
if appChangelogState.checkedVersion == appVersion {
|
||||
return .complete()
|
||||
}
|
||||
let previousVersion = appChangelogState.previousVersion
|
||||
return network.request(Api.functions.help.getAppChangelog(prevAppVersion: previousVersion))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates = updates {
|
||||
stateManager.addUpdates(updates)
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction in
|
||||
updateAppChangelogState(transaction: transaction, { state in
|
||||
var state = state
|
||||
state.checkedVersion = appVersion
|
||||
state.previousVersion = appVersion
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
submodules/TelegramCore/TelegramCore/AppChangelogState.swift
Normal file
50
submodules/TelegramCore/TelegramCore/AppChangelogState.swift
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct AppChangelogState: PreferencesEntry, Equatable {
|
||||
var checkedVersion: String
|
||||
var previousVersion: String
|
||||
|
||||
static var `default` = AppChangelogState(checkedVersion: "", previousVersion: "5.0.8")
|
||||
|
||||
init(checkedVersion: String, previousVersion: String) {
|
||||
self.checkedVersion = checkedVersion
|
||||
self.previousVersion = previousVersion
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.checkedVersion = decoder.decodeStringForKey("checkedVersion", orElse: "")
|
||||
self.previousVersion = decoder.decodeStringForKey("previousVersion", orElse: "")
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.checkedVersion, forKey: "checkedVersion")
|
||||
encoder.encodeString(self.previousVersion, forKey: "previousVersion")
|
||||
}
|
||||
|
||||
func isEqual(to: PreferencesEntry) -> Bool {
|
||||
guard let to = to as? AppChangelogState else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self == to
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppChangelogState(transaction: Transaction, _ f: @escaping (AppChangelogState) -> AppChangelogState) {
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.appChangelogState, { current in
|
||||
return f((current as? AppChangelogState) ?? AppChangelogState.default)
|
||||
})
|
||||
}
|
||||
53
submodules/TelegramCore/TelegramCore/AppConfiguration.swift
Normal file
53
submodules/TelegramCore/TelegramCore/AppConfiguration.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public struct AppConfiguration: PreferencesEntry, Equatable {
|
||||
public var data: JSON?
|
||||
|
||||
public static var defaultValue: AppConfiguration {
|
||||
return AppConfiguration(data: nil)
|
||||
}
|
||||
|
||||
init(data: JSON?) {
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.data = decoder.decodeObjectForKey("data", decoder: { JSON(decoder: $0) }) as? JSON
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let data = self.data {
|
||||
encoder.encodeObject(data, forKey: "data")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "data")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
guard let to = to as? AppConfiguration else {
|
||||
return false
|
||||
}
|
||||
return self == to
|
||||
}
|
||||
}
|
||||
|
||||
public func currentAppConfiguration(transaction: Transaction) -> AppConfiguration {
|
||||
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration {
|
||||
return entry
|
||||
} else {
|
||||
return AppConfiguration.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppConfiguration(transaction: Transaction, _ f: (AppConfiguration) -> AppConfiguration) {
|
||||
let current = currentAppConfiguration(transaction: transaction)
|
||||
let updated = f(current)
|
||||
if updated != current {
|
||||
transaction.setPreferencesEntry(key: PreferencesKeys.appConfiguration, value: updated)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public func applyMaxReadIndexInteractively(postbox: Postbox, stateManager: AccountStateManager, index: MessageIndex) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
applyMaxReadIndexInteractively(transaction: transaction, stateManager: stateManager, index: index)
|
||||
}
|
||||
}
|
||||
|
||||
func applyMaxReadIndexInteractively(transaction: Transaction, stateManager: AccountStateManager, index: MessageIndex) {
|
||||
let messageIds = transaction.applyInteractiveReadMaxIndex(index)
|
||||
if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
for id in messageIds {
|
||||
if let message = transaction.getMessage(id) {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia {
|
||||
transaction.updateMessage(message.id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in
|
||||
if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute {
|
||||
return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp)
|
||||
} else {
|
||||
return currentAttribute
|
||||
}
|
||||
})
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
|
||||
})
|
||||
transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if index.id.peerId.namespace == Namespaces.Peer.CloudUser || index.id.peerId.namespace == Namespaces.Peer.CloudGroup || index.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
stateManager.notifyAppliedIncomingReadMessages([index.id])
|
||||
}
|
||||
}
|
||||
|
||||
func applyOutgoingReadMaxIndex(transaction: Transaction, index: MessageIndex, beginCountdownAt timestamp: Int32) {
|
||||
let messageIds = transaction.applyOutgoingReadMaxIndex(index)
|
||||
if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
for id in messageIds {
|
||||
applySecretOutgoingMessageReadActions(transaction: transaction, id: id, beginCountdownAt: timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func maybeReadSecretOutgoingMessage(transaction: Transaction, index: MessageIndex) {
|
||||
guard index.id.peerId.namespace == Namespaces.Peer.SecretChat else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard index.id.namespace == Namespaces.Message.Local else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
guard let combinedState = transaction.getCombinedPeerReadState(index.id.peerId) else {
|
||||
return
|
||||
}
|
||||
|
||||
if combinedState.isOutgoingMessageIndexRead(index) {
|
||||
applySecretOutgoingMessageReadActions(transaction: transaction, id: index.id, beginCountdownAt: index.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func applySecretOutgoingMessageReadActions(transaction: Transaction, id: MessageId, beginCountdownAt timestamp: Int32) {
|
||||
guard id.peerId.namespace == Namespaces.Peer.SecretChat else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard id.namespace == Namespaces.Message.Local else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
if let message = transaction.getMessage(id), !message.flags.contains(.Incoming) {
|
||||
if message.flags.intersection([.Unsent, .Sending, .Failed]).isEmpty {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia {
|
||||
transaction.updateMessage(message.id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in
|
||||
if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute {
|
||||
return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp)
|
||||
} else {
|
||||
return currentAttribute
|
||||
}
|
||||
})
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
|
||||
})
|
||||
transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func togglePeerUnreadMarkInteractively(postbox: Postbox, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: setToValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func togglePeerUnreadMarkInteractively(transaction: Transaction, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) {
|
||||
let namespace: MessageId.Namespace
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
namespace = Namespaces.Message.SecretIncoming
|
||||
} else {
|
||||
namespace = Namespaces.Message.Cloud
|
||||
}
|
||||
if let states = transaction.getPeerReadStates(peerId) {
|
||||
for i in 0 ..< states.count {
|
||||
if states[i].0 == namespace {
|
||||
if states[i].1.isUnread {
|
||||
if setToValue == nil || !(setToValue!) {
|
||||
if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: namespace) {
|
||||
let _ = transaction.applyInteractiveReadMaxIndex(index)
|
||||
} else {
|
||||
transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: false, interactive: true)
|
||||
}
|
||||
viewTracker.updateMarkAllMentionsSeen(peerId: peerId)
|
||||
}
|
||||
} else if namespace == Namespaces.Message.Cloud || namespace == Namespaces.Message.SecretIncoming {
|
||||
if setToValue == nil || setToValue! {
|
||||
transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: true, interactive: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func clearPeerUnseenPersonalMessagesInteractively(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return
|
||||
}
|
||||
account.viewTracker.updateMarkAllMentionsSeen(peerId: peerId)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func markAllChatsAsReadInteractively(transaction: Transaction, viewTracker: AccountViewTracker, groupId: PeerGroupId) {
|
||||
for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId) {
|
||||
togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: false)
|
||||
}
|
||||
}
|
||||
299
submodules/TelegramCore/TelegramCore/ApplyUpdateMessage.swift
Normal file
299
submodules/TelegramCore/TelegramCore/ApplyUpdateMessage.swift
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) {
|
||||
if let fromImage = from as? TelegramMediaImage, let toImage = to as? TelegramMediaImage {
|
||||
let fromSmallestRepresentation = smallestImageRepresentation(fromImage.representations)
|
||||
if let fromSmallestRepresentation = fromSmallestRepresentation, let toSmallestRepresentation = smallestImageRepresentation(toImage.representations) {
|
||||
let leeway: CGFloat = 4.0
|
||||
let widthDifference = fromSmallestRepresentation.dimensions.width - toSmallestRepresentation.dimensions.width
|
||||
let heightDifference = fromSmallestRepresentation.dimensions.height - toSmallestRepresentation.dimensions.height
|
||||
if abs(widthDifference) < leeway && abs(heightDifference) < leeway {
|
||||
postbox.mediaBox.moveResourceData(from: fromSmallestRepresentation.resource.id, to: toSmallestRepresentation.resource.id)
|
||||
}
|
||||
}
|
||||
if let fromLargestRepresentation = largestImageRepresentation(fromImage.representations), let toLargestRepresentation = largestImageRepresentation(toImage.representations) {
|
||||
postbox.mediaBox.moveResourceData(from: fromLargestRepresentation.resource.id, to: toLargestRepresentation.resource.id)
|
||||
}
|
||||
} else if let fromFile = from as? TelegramMediaFile, let toFile = to as? TelegramMediaFile {
|
||||
if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) {
|
||||
postbox.mediaBox.moveResourceData(from: fromPreview.resource.id, to: toPreview.resource.id)
|
||||
}
|
||||
if (fromFile.size == toFile.size || fromFile.resource.size == toFile.resource.size) && fromFile.mimeType == toFile.mimeType {
|
||||
postbox.mediaBox.moveResourceData(from: fromFile.resource.id, to: toFile.resource.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
let messageId: Int32?
|
||||
var apiMessage: Api.Message?
|
||||
|
||||
for resultMessage in result.messages {
|
||||
if let id = resultMessage.id {
|
||||
if id.peerId == message.id.peerId {
|
||||
apiMessage = resultMessage
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let apiMessage = apiMessage, let id = apiMessage.id {
|
||||
messageId = id.id
|
||||
} else {
|
||||
messageId = result.rawMessageIds.first
|
||||
}
|
||||
|
||||
var updatedTimestamp: Int32?
|
||||
if let apiMessage = apiMessage {
|
||||
switch apiMessage {
|
||||
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _):
|
||||
updatedTimestamp = date
|
||||
case .messageEmpty:
|
||||
break
|
||||
case let .messageService(_, _, _, _, _, date, _):
|
||||
updatedTimestamp = date
|
||||
}
|
||||
} else {
|
||||
switch result {
|
||||
case let .updateShortSentMessage(_, _, _, _, date, _, _):
|
||||
updatedTimestamp = date
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let channelPts = result.channelPts
|
||||
|
||||
var sentStickers: [TelegramMediaFile] = []
|
||||
var sentGifs: [TelegramMediaFile] = []
|
||||
|
||||
if let updatedTimestamp = updatedTimestamp {
|
||||
transaction.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp)
|
||||
}
|
||||
|
||||
transaction.updateMessage(message.id, update: { currentMessage in
|
||||
let updatedId: MessageId
|
||||
if let messageId = messageId {
|
||||
updatedId = MessageId(peerId: currentMessage.id.peerId, namespace: Namespaces.Message.Cloud, id: messageId)
|
||||
} else {
|
||||
updatedId = currentMessage.id
|
||||
}
|
||||
|
||||
let media: [Media]
|
||||
var attributes: [MessageAttribute]
|
||||
let text: String
|
||||
let forwardInfo: StoreMessageForwardInfo?
|
||||
if let apiMessage = apiMessage, let updatedMessage = StoreMessage(apiMessage: apiMessage) {
|
||||
media = updatedMessage.media
|
||||
attributes = updatedMessage.attributes
|
||||
text = updatedMessage.text
|
||||
forwardInfo = updatedMessage.forwardInfo
|
||||
} else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities) = result {
|
||||
let (mediaValue, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
|
||||
if let mediaValue = mediaValue {
|
||||
media = [mediaValue]
|
||||
} else {
|
||||
media = []
|
||||
}
|
||||
|
||||
var updatedAttributes: [MessageAttribute] = currentMessage.attributes
|
||||
if let entities = entities, !entities.isEmpty {
|
||||
for i in 0 ..< updatedAttributes.count {
|
||||
if updatedAttributes[i] is TextEntitiesMessageAttribute {
|
||||
updatedAttributes.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
updatedAttributes.append(TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)))
|
||||
}
|
||||
|
||||
attributes = updatedAttributes
|
||||
text = currentMessage.text
|
||||
|
||||
forwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
} else {
|
||||
media = currentMessage.media
|
||||
attributes = currentMessage.attributes
|
||||
text = currentMessage.text
|
||||
forwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
}
|
||||
|
||||
if let channelPts = channelPts {
|
||||
for i in 0 ..< attributes.count {
|
||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||
attributes.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
}
|
||||
|
||||
if let fromMedia = currentMessage.media.first, let toMedia = media.first {
|
||||
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
|
||||
}
|
||||
|
||||
if forwardInfo == nil {
|
||||
for media in media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.isSticker {
|
||||
sentStickers.append(file)
|
||||
} else if file.isVideo && file.isAnimated {
|
||||
sentGifs.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entitiesAttribute = attribute
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities)
|
||||
|
||||
return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media))
|
||||
})
|
||||
for file in sentStickers {
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
||||
}
|
||||
for file in sentGifs {
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200)
|
||||
}
|
||||
stateManager.addUpdates(result)
|
||||
}
|
||||
}
|
||||
|
||||
func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManager, messages: [Message], result: Api.Updates) -> Signal<Void, NoError> {
|
||||
guard !messages.isEmpty else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> Void in
|
||||
let updatedRawMessageIds = result.updatedRawMessageIds
|
||||
|
||||
var resultMessages: [MessageId: StoreMessage] = [:]
|
||||
for apiMessage in result.messages {
|
||||
if let resultMessage = StoreMessage(apiMessage: apiMessage), case let .Id(id) = resultMessage.id {
|
||||
resultMessages[id] = resultMessage
|
||||
}
|
||||
}
|
||||
|
||||
var mapping: [(Message, MessageIndex, StoreMessage)] = []
|
||||
|
||||
for message in messages {
|
||||
var uniqueId: Int64?
|
||||
inner: for attribute in message.attributes {
|
||||
if let outgoingInfo = attribute as? OutgoingMessageInfoAttribute {
|
||||
uniqueId = outgoingInfo.uniqueId
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if let uniqueId = uniqueId {
|
||||
if let updatedId = updatedRawMessageIds[uniqueId] {
|
||||
if let storeMessage = resultMessages[MessageId(peerId: message.id.peerId, namespace: Namespaces.Message.Cloud, id: updatedId)], case let .Id(id) = storeMessage.id {
|
||||
mapping.append((message, MessageIndex(id: id, timestamp: storeMessage.timestamp), storeMessage))
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
mapping.sort { $0.1 < $1.1 }
|
||||
|
||||
let latestPreviousId = mapping.map({ $0.0.id }).max()
|
||||
|
||||
var sentStickers: [TelegramMediaFile] = []
|
||||
var sentGifs: [TelegramMediaFile] = []
|
||||
|
||||
var updatedGroupingKey: Int64?
|
||||
for (_, _, updatedMessage) in mapping {
|
||||
if let updatedGroupingKey = updatedGroupingKey {
|
||||
assert(updatedGroupingKey == updatedMessage.groupingKey)
|
||||
}
|
||||
updatedGroupingKey = updatedMessage.groupingKey
|
||||
}
|
||||
|
||||
if let latestPreviousId = latestPreviousId, let latestIndex = mapping.last?.1 {
|
||||
transaction.offsetPendingMessagesTimestamps(lowerBound: latestPreviousId, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp)
|
||||
}
|
||||
|
||||
if let updatedGroupingKey = updatedGroupingKey {
|
||||
transaction.updateMessageGroupingKeysAtomically(mapping.map { $0.0.id }, groupingKey: updatedGroupingKey)
|
||||
}
|
||||
|
||||
for (message, _, updatedMessage) in mapping {
|
||||
transaction.updateMessage(message.id, update: { currentMessage in
|
||||
let updatedId: MessageId
|
||||
if case let .Id(id) = updatedMessage.id {
|
||||
updatedId = id
|
||||
} else {
|
||||
updatedId = currentMessage.id
|
||||
}
|
||||
|
||||
let media: [Media]
|
||||
let attributes: [MessageAttribute]
|
||||
let text: String
|
||||
|
||||
media = updatedMessage.media
|
||||
attributes = updatedMessage.attributes
|
||||
text = updatedMessage.text
|
||||
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
|
||||
if let fromMedia = currentMessage.media.first, let toMedia = media.first {
|
||||
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
|
||||
}
|
||||
|
||||
if storeForwardInfo == nil {
|
||||
for media in media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.isSticker {
|
||||
sentStickers.append(file)
|
||||
} else if file.isVideo && file.isAnimated {
|
||||
sentGifs.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entitiesAttribute = attribute
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities)
|
||||
|
||||
return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media))
|
||||
})
|
||||
}
|
||||
|
||||
for file in sentStickers {
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
||||
}
|
||||
for file in sentGifs {
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200)
|
||||
}
|
||||
stateManager.addUpdates(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public enum ArchivedStickerPacksNamespace: Int32 {
|
||||
case stickers = 0
|
||||
case masks = 1
|
||||
}
|
||||
|
||||
public final class ArchivedStickerPackItem {
|
||||
public let info: StickerPackCollectionInfo
|
||||
public let topItems: [StickerPackItem]
|
||||
|
||||
public init(info: StickerPackCollectionInfo, topItems: [StickerPackItem]) {
|
||||
self.info = info
|
||||
self.topItems = topItems
|
||||
}
|
||||
}
|
||||
|
||||
public func archivedStickerPacks(account: Account, namespace: ArchivedStickerPacksNamespace = .stickers) -> Signal<[ArchivedStickerPackItem], NoError> {
|
||||
var flags: Int32 = 0
|
||||
if case .masks = namespace {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getArchivedStickers(flags: flags, offsetId: 0, limit: 100))
|
||||
|> map { result -> [ArchivedStickerPackItem] in
|
||||
var archivedItems: [ArchivedStickerPackItem] = []
|
||||
switch result {
|
||||
case let .archivedStickers(_, sets):
|
||||
for set in sets {
|
||||
let (info, items) = parsePreviewStickerSet(set)
|
||||
archivedItems.append(ArchivedStickerPackItem(info: info, topItems: items))
|
||||
}
|
||||
}
|
||||
return archivedItems
|
||||
} |> `catch` { _ in
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
|
||||
public func removeArchivedStickerPack(account: Account, info: StickerPackCollectionInfo) -> Signal<Void, NoError> {
|
||||
return account.network.request(Api.functions.messages.uninstallStickerSet(stickerset: Api.InputStickerSet.inputStickerSetID(id: info.id.id, accessHash: info.accessHash)))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public struct ArchivedStickerPacksInfoId {
|
||||
public let rawValue: MemoryBuffer
|
||||
public let id: Int32
|
||||
|
||||
init(_ rawValue: MemoryBuffer) {
|
||||
self.rawValue = rawValue
|
||||
assert(rawValue.length == 4)
|
||||
var idValue: Int32 = 0
|
||||
memcpy(&idValue, rawValue.memory, 4)
|
||||
self.id = idValue
|
||||
}
|
||||
|
||||
init(_ id: Int32) {
|
||||
self.id = id
|
||||
var idValue: Int32 = id
|
||||
self.rawValue = MemoryBuffer(memory: malloc(4)!, capacity: 4, length: 4, freeWhenDone: true)
|
||||
memcpy(self.rawValue.memory, &idValue, 4)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ArchivedStickerPacksInfo: OrderedItemListEntryContents {
|
||||
public let count: Int32
|
||||
|
||||
init(count: Int32) {
|
||||
self.count = count
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.count = decoder.decodeInt32ForKey("c", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.count, forKey: "c")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public class AuthorSignatureMessageAttribute: MessageAttribute {
|
||||
public let signature: String
|
||||
|
||||
public let associatedPeerIds: [PeerId] = []
|
||||
|
||||
init(signature: String) {
|
||||
self.signature = signature
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.signature = decoder.decodeStringForKey("s", orElse: "")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.signature, forKey: "s")
|
||||
}
|
||||
}
|
||||
533
submodules/TelegramCore/TelegramCore/Authorization.swift
Normal file
533
submodules/TelegramCore/TelegramCore/Authorization.swift
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum AuthorizationCodeRequestError {
|
||||
case invalidPhoneNumber
|
||||
case limitExceeded
|
||||
case generic(info: (Int, String)?)
|
||||
case phoneLimitExceeded
|
||||
case phoneBanned
|
||||
case timeout
|
||||
}
|
||||
|
||||
private func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) {
|
||||
let nextSortOrder = (transaction.getRecords().map({ record -> Int32 in
|
||||
for attribute in record.attributes {
|
||||
if let attribute = attribute as? AccountSortOrderAttribute {
|
||||
return attribute.order
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}).max() ?? 0) + 1
|
||||
transaction.updateRecord(account.id, { _ in
|
||||
return AccountRecord(id: account.id, attributes: [AccountEnvironmentAttribute(environment: account.testingEnvironment ? .test : .production), AccountSortOrderAttribute(order: nextSortOrder)], temporarySessionId: nil)
|
||||
})
|
||||
transaction.setCurrentId(account.id)
|
||||
transaction.removeAuth()
|
||||
}
|
||||
|
||||
public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> {
|
||||
let sendCode = Api.functions.auth.sendCode(flags: 0, phoneNumber: phoneNumber, currentNumber: nil, apiId: apiId, apiHash: apiHash)
|
||||
|
||||
let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false)
|
||||
|> map { result in
|
||||
return (result, account)
|
||||
}
|
||||
|> `catch` { error -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
|
||||
switch (error.errorDescription ?? "") {
|
||||
case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"):
|
||||
let range = error.errorDescription.range(of: "MIGRATE_")!
|
||||
let updatedMasterDatacenterId = Int32(error.errorDescription[range.upperBound ..< error.errorDescription.endIndex])!
|
||||
let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: updatedMasterDatacenterId)
|
||||
return updatedAccount
|
||||
|> mapToSignalPromotingError { updatedAccount -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
|
||||
return updatedAccount.network.request(sendCode, automaticFloodWait: false)
|
||||
|> map { sentCode in
|
||||
return (sentCode, updatedAccount)
|
||||
}
|
||||
}
|
||||
case _:
|
||||
return .fail(error)
|
||||
}
|
||||
}
|
||||
|> mapError { error -> AuthorizationCodeRequestError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
||||
return .invalidPhoneNumber
|
||||
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
|
||||
return .phoneLimitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
|
||||
return .phoneBanned
|
||||
} else {
|
||||
return .generic(info: (Int(error.errorCode), error.errorDescription))
|
||||
}
|
||||
}
|
||||
|> timeout(20.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.timeout))
|
||||
|
||||
return codeAndAccount
|
||||
|> mapToSignal { (sentCode, account) -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> in
|
||||
return account.postbox.transaction { transaction -> UnauthorizedAccount in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout, termsOfService):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
var explicitTerms = false
|
||||
if let termsOfService = termsOfService {
|
||||
switch termsOfService {
|
||||
case let .termsOfService(value):
|
||||
if (value.flags & (1 << 0)) != 0 {
|
||||
explicitTerms = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)).flatMap({ ($0, explicitTerms) }), syncContacts: syncContacts)))
|
||||
}
|
||||
return account
|
||||
}
|
||||
|> mapError { _ -> AuthorizationCodeRequestError in
|
||||
return .generic(info: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func resendAuthorizationCode(account: UnauthorizedAccount) -> Signal<Void, AuthorizationCodeRequestError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, AuthorizationCodeRequestError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(number, _, hash, _, nextType, _, syncContacts):
|
||||
if nextType != nil {
|
||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false)
|
||||
|> mapError { error -> AuthorizationCodeRequestError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
||||
return .invalidPhoneNumber
|
||||
} else if error.errorDescription == "PHONE_NUMBER_FLOOD" {
|
||||
return .phoneLimitExceeded
|
||||
} else if error.errorDescription == "PHONE_NUMBER_BANNED" {
|
||||
return .phoneBanned
|
||||
} else {
|
||||
return .generic(info: (Int(error.errorCode), error.errorDescription))
|
||||
}
|
||||
}
|
||||
|> mapToSignal { sentCode -> Signal<Void, AuthorizationCodeRequestError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout, termsOfService):
|
||||
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
|
||||
var explicitTerms = false
|
||||
if let termsOfService = termsOfService {
|
||||
switch termsOfService {
|
||||
case let .termsOfService(value):
|
||||
if (value.flags & (1 << 0)) != 0 {
|
||||
explicitTerms = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)).flatMap({ ($0, explicitTerms) }), syncContacts: syncContacts)))
|
||||
|
||||
}
|
||||
} |> mapError { _ -> AuthorizationCodeRequestError in return .generic(info: nil) }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic(info: nil))
|
||||
}
|
||||
default:
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic(info: nil))
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AuthorizationCodeRequestError in
|
||||
return .generic(info: nil)
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum AuthorizationCodeVerificationError {
|
||||
case invalidCode
|
||||
case limitExceeded
|
||||
case generic
|
||||
case codeExpired
|
||||
}
|
||||
|
||||
private enum AuthorizationCodeResult {
|
||||
case authorization(Api.auth.Authorization)
|
||||
case password(hint: String)
|
||||
case signUp
|
||||
}
|
||||
|
||||
public struct AuthorizationSignUpData {
|
||||
let number: String
|
||||
let codeHash: String
|
||||
let code: String
|
||||
let termsOfService: UnauthorizedAccountTermsOfService?
|
||||
let syncContacts: Bool
|
||||
}
|
||||
|
||||
public enum AuthorizeWithCodeResult {
|
||||
case signUp(AuthorizationSignUpData)
|
||||
case loggedIn
|
||||
}
|
||||
|
||||
public func authorizeWithCode(accountManager: AccountManager, account: UnauthorizedAccount, code: String, termsOfService: UnauthorizedAccountTermsOfService?) -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case let .confirmationCodeEntry(number, _, hash, _, _, _, syncContacts):
|
||||
return account.network.request(Api.functions.auth.signIn(phoneNumber: number, phoneCodeHash: hash, phoneCode: code), automaticFloodWait: false)
|
||||
|> map { authorization in
|
||||
return .authorization(authorization)
|
||||
}
|
||||
|> `catch` { error -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch (error.errorCode, error.errorDescription ?? "") {
|
||||
case (401, "SESSION_PASSWORD_NEEDED"):
|
||||
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|
||||
|> mapError { error -> AuthorizationCodeVerificationError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch result {
|
||||
case let .password(password):
|
||||
return .single(.password(hint: password.hint ?? ""))
|
||||
}
|
||||
}
|
||||
case let (_, errorDescription):
|
||||
if errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.limitExceeded)
|
||||
} else if errorDescription == "PHONE_CODE_INVALID" {
|
||||
return .fail(.invalidCode)
|
||||
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
|
||||
return .fail(.codeExpired)
|
||||
} else if errorDescription == "PHONE_NUMBER_UNOCCUPIED" {
|
||||
return .single(.signUp)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, NoError> in
|
||||
switch result {
|
||||
case .signUp:
|
||||
return .single(.signUp(AuthorizationSignUpData(number: number, codeHash: hash, code: code, termsOfService: termsOfService, syncContacts: syncContacts)))
|
||||
case let .password(hint):
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code, suggestReset: false, syncContacts: syncContacts)))
|
||||
return .single(.loggedIn)
|
||||
case let .authorization(authorization):
|
||||
switch authorization {
|
||||
case let .authorization(_, _, user):
|
||||
let user = TelegramUser(user: user)
|
||||
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
|
||||
transaction.setState(state)
|
||||
}
|
||||
return accountManager.transaction { transaction -> AuthorizeWithCodeResult in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account)
|
||||
return .loggedIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapError { _ -> AuthorizationCodeVerificationError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
default:
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AuthorizationCodeVerificationError in
|
||||
return .generic
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public func beginSignUp(account: UnauthorizedAccount, data: AuthorizationSignUpData) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .signUp(number: data.number, codeHash: data.codeHash, code: data.code, firstName: "", lastName: "", termsOfService: data.termsOfService, syncContacts: data.syncContacts)))
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public enum AuthorizationPasswordVerificationError {
|
||||
case limitExceeded
|
||||
case invalidPassword
|
||||
case generic
|
||||
}
|
||||
|
||||
public func authorizeWithPassword(accountManager: AccountManager, account: UnauthorizedAccount, password: String, syncContacts: Bool) -> Signal<Void, AuthorizationPasswordVerificationError> {
|
||||
return verifyPassword(account, password: password)
|
||||
|> `catch` { error -> Signal<Api.auth.Authorization, AuthorizationPasswordVerificationError> in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.limitExceeded)
|
||||
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
|
||||
return .fail(.invalidPassword)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, AuthorizationPasswordVerificationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .authorization(_, _, user):
|
||||
let user = TelegramUser(user: user)
|
||||
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
/*transaction.updatePeersInternal([user], update: { current, peer -> Peer? in
|
||||
return peer
|
||||
})*/
|
||||
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
|
||||
transaction.setState(state)
|
||||
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapError { _ -> AuthorizationPasswordVerificationError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PasswordRecoveryRequestError {
|
||||
case limitExceeded
|
||||
case generic
|
||||
}
|
||||
|
||||
public enum PasswordRecoveryOption {
|
||||
case none
|
||||
case email(pattern: String)
|
||||
}
|
||||
|
||||
public func requestPasswordRecovery(account: UnauthorizedAccount) -> Signal<PasswordRecoveryOption, PasswordRecoveryRequestError> {
|
||||
return account.network.request(Api.functions.auth.requestPasswordRecovery())
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.auth.PasswordRecovery?, PasswordRecoveryRequestError> in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.limitExceeded)
|
||||
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_NA") {
|
||||
return .single(nil)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> map { result -> PasswordRecoveryOption in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .passwordRecovery(emailPattern):
|
||||
return .email(pattern: emailPattern)
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PasswordRecoveryError {
|
||||
case invalidCode
|
||||
case limitExceeded
|
||||
case expired
|
||||
}
|
||||
|
||||
public func performPasswordRecovery(accountManager: AccountManager, account: UnauthorizedAccount, code: String, syncContacts: Bool) -> Signal<Void, PasswordRecoveryError> {
|
||||
return account.network.request(Api.functions.auth.recoverPassword(code: code))
|
||||
|> mapError { error -> PasswordRecoveryError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_EXPIRED") {
|
||||
return .expired
|
||||
} else {
|
||||
return .invalidCode
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, PasswordRecoveryError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
switch result {
|
||||
case let .authorization(_, _, user):
|
||||
let user = TelegramUser(user: user)
|
||||
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
/*transaction.updatePeersInternal([user], update: { current, peer -> Peer? in
|
||||
return peer
|
||||
})*/
|
||||
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
|
||||
transaction.setState(state)
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapError { _ in return PasswordRecoveryError.expired }
|
||||
}
|
||||
}
|
||||
|
||||
public enum AccountResetError {
|
||||
case generic
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public func performAccountReset(account: UnauthorizedAccount) -> Signal<Void, AccountResetError> {
|
||||
return account.network.request(Api.functions.account.deleteAccount(reason: ""))
|
||||
|> map { _ -> Int32? in return nil }
|
||||
|> `catch` { error -> Signal<Int32?, AccountResetError> in
|
||||
if error.errorDescription.hasPrefix("2FA_CONFIRM_WAIT_") {
|
||||
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "2FA_CONFIRM_WAIT_".count)...])
|
||||
if let value = Int32(timeout) {
|
||||
return .single(value)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else if error.errorDescription == "2FA_RECENT_CONFIRM" {
|
||||
return .fail(.limitExceeded)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { timeout -> Signal<Void, AccountResetError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
guard let state = transaction.getState() as? UnauthorizedAccountState else {
|
||||
return
|
||||
}
|
||||
var number: String?
|
||||
var syncContacts: Bool?
|
||||
if case let .passwordEntry(_, numberValue, _, _, syncContactsValue) = state.contents {
|
||||
number = numberValue
|
||||
syncContacts = syncContactsValue
|
||||
} else if case let .awaitingAccountReset(_, numberValue, syncContactsValue) = state.contents {
|
||||
number = numberValue
|
||||
syncContacts = syncContactsValue
|
||||
}
|
||||
if let number = number, let syncContacts = syncContacts {
|
||||
if let timeout = timeout {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .awaitingAccountReset(protectedUntil: timestamp + timeout, number: number, syncContacts: syncContacts)))
|
||||
} else {
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .empty))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapError { _ in return AccountResetError.generic }
|
||||
}
|
||||
}
|
||||
|
||||
public enum SignUpError {
|
||||
case generic
|
||||
case limitExceeded
|
||||
case codeExpired
|
||||
case invalidFirstName
|
||||
case invalidLastName
|
||||
}
|
||||
|
||||
public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?) -> Signal<Void, SignUpError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, SignUpError> in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState, case let .signUp(number, codeHash, code, _, _, _, syncContacts) = state.contents {
|
||||
return account.network.request(Api.functions.auth.signUp(phoneNumber: number, phoneCodeHash: codeHash, phoneCode: code, firstName: firstName, lastName: lastName))
|
||||
|> mapError { error -> SignUpError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else if error.errorDescription == "PHONE_CODE_EXPIRED" {
|
||||
return .codeExpired
|
||||
} else if error.errorDescription == "FIRSTNAME_INVALID" {
|
||||
return .invalidFirstName
|
||||
} else if error.errorDescription == "LASTNAME_INVALID" {
|
||||
return .invalidLastName
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, SignUpError> in
|
||||
switch result {
|
||||
case let .authorization(_, _, user):
|
||||
let user = TelegramUser(user: user)
|
||||
let appliedState = account.postbox.transaction { transaction -> Void in
|
||||
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
||||
if let hole = account.postbox.seedConfiguration.initializeChatListWithHole.topLevel {
|
||||
transaction.replaceChatListHole(groupId: .root, index: hole.index, hole: nil)
|
||||
}
|
||||
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
|
||||
transaction.setState(state)
|
||||
}
|
||||
|> introduceError(SignUpError.self)
|
||||
|
||||
let switchedAccounts = accountManager.transaction { transaction -> Void in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account)
|
||||
}
|
||||
|> introduceError(SignUpError.self)
|
||||
|
||||
if let avatarData = avatarData {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: avatarData)
|
||||
|
||||
return updatePeerPhotoInternal(postbox: account.postbox, network: account.network, stateManager: nil, accountPeerId: user.id, peer: .single(user), photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource), mapResourceToAvatarSizes: { _, _ in .single([:]) })
|
||||
|> `catch` { _ -> Signal<UpdatePeerPhotoStatus, SignUpError> in
|
||||
return .complete()
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, SignUpError> in
|
||||
switch result {
|
||||
case .complete:
|
||||
return .complete()
|
||||
case .progress:
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
|> then(appliedState)
|
||||
|> then(switchedAccounts)
|
||||
} else {
|
||||
return appliedState
|
||||
|> then(switchedAccounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> SignUpError in
|
||||
return .generic
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum AuthorizationStateReset {
|
||||
case empty
|
||||
}
|
||||
|
||||
public func resetAuthorizationState(account: UnauthorizedAccount, to value: AuthorizationStateReset) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let state = transaction.getState() as? UnauthorizedAccountState {
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .empty))
|
||||
}
|
||||
}
|
||||
}
|
||||
139
submodules/TelegramCore/TelegramCore/AutodownloadSettings.swift
Normal file
139
submodules/TelegramCore/TelegramCore/AutodownloadSettings.swift
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public enum AutodownloadPreset {
|
||||
case low
|
||||
case medium
|
||||
case high
|
||||
}
|
||||
|
||||
public struct AutodownloadPresetSettings: PostboxCoding, Equatable {
|
||||
public let disabled: Bool
|
||||
public let photoSizeMax: Int32
|
||||
public let videoSizeMax: Int32
|
||||
public let fileSizeMax: Int32
|
||||
public let preloadLargeVideo: Bool
|
||||
public let lessDataForPhoneCalls: Bool
|
||||
|
||||
public init(disabled: Bool, photoSizeMax: Int32, videoSizeMax: Int32, fileSizeMax: Int32, preloadLargeVideo: Bool, lessDataForPhoneCalls: Bool) {
|
||||
self.disabled = disabled
|
||||
self.photoSizeMax = photoSizeMax
|
||||
self.videoSizeMax = videoSizeMax
|
||||
self.fileSizeMax = fileSizeMax
|
||||
self.preloadLargeVideo = preloadLargeVideo
|
||||
self.lessDataForPhoneCalls = lessDataForPhoneCalls
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.disabled = decoder.decodeInt32ForKey("disabled", orElse: 0) != 0
|
||||
self.photoSizeMax = decoder.decodeInt32ForKey("photoSizeMax", orElse: 0)
|
||||
self.videoSizeMax = decoder.decodeInt32ForKey("videoSizeMax", orElse: 0)
|
||||
self.fileSizeMax = decoder.decodeInt32ForKey("fileSizeMax", orElse: 0)
|
||||
self.preloadLargeVideo = decoder.decodeInt32ForKey("preloadLargeVideo", orElse: 0) != 0
|
||||
self.lessDataForPhoneCalls = decoder.decodeInt32ForKey("lessDataForPhoneCalls", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.disabled ? 1 : 0, forKey: "disabled")
|
||||
encoder.encodeInt32(self.photoSizeMax, forKey: "photoSizeMax")
|
||||
encoder.encodeInt32(self.videoSizeMax, forKey: "videoSizeMax")
|
||||
encoder.encodeInt32(self.fileSizeMax, forKey: "fileSizeMax")
|
||||
encoder.encodeInt32(self.preloadLargeVideo ? 1 : 0, forKey: "preloadLargeVideo")
|
||||
encoder.encodeInt32(self.lessDataForPhoneCalls ? 1 : 0, forKey: "lessDataForPhoneCalls")
|
||||
}
|
||||
}
|
||||
|
||||
public struct AutodownloadSettings: PreferencesEntry, Equatable {
|
||||
public let lowPreset: AutodownloadPresetSettings
|
||||
public let mediumPreset: AutodownloadPresetSettings
|
||||
public let highPreset: AutodownloadPresetSettings
|
||||
|
||||
public static var defaultSettings: AutodownloadSettings {
|
||||
return AutodownloadSettings(lowPreset: AutodownloadPresetSettings(disabled: false, photoSizeMax: 1 * 1024 * 1024, videoSizeMax: 0, fileSizeMax: 0, preloadLargeVideo: false, lessDataForPhoneCalls: true),
|
||||
mediumPreset: AutodownloadPresetSettings(disabled: false, photoSizeMax: 1 * 1024 * 1024, videoSizeMax: Int32(2.5 * 1024 * 1024), fileSizeMax: 1 * 1024 * 1024, preloadLargeVideo: false, lessDataForPhoneCalls: false),
|
||||
highPreset: AutodownloadPresetSettings(disabled: false, photoSizeMax: 1 * 1024 * 1024, videoSizeMax: 10 * 1024 * 1024, fileSizeMax: 3 * 1024 * 1024, preloadLargeVideo: false, lessDataForPhoneCalls: false))
|
||||
}
|
||||
|
||||
init(lowPreset: AutodownloadPresetSettings, mediumPreset: AutodownloadPresetSettings, highPreset: AutodownloadPresetSettings) {
|
||||
self.lowPreset = lowPreset
|
||||
self.mediumPreset = mediumPreset
|
||||
self.highPreset = highPreset
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.lowPreset = decoder.decodeObjectForKey("lowPreset", decoder: AutodownloadPresetSettings.init(decoder:)) as! AutodownloadPresetSettings
|
||||
self.mediumPreset = decoder.decodeObjectForKey("mediumPreset", decoder: AutodownloadPresetSettings.init(decoder:)) as! AutodownloadPresetSettings
|
||||
self.highPreset = decoder.decodeObjectForKey("highPreset", decoder: AutodownloadPresetSettings.init(decoder:)) as! AutodownloadPresetSettings
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObject(self.lowPreset, forKey: "lowPreset")
|
||||
encoder.encodeObject(self.mediumPreset, forKey: "mediumPreset")
|
||||
encoder.encodeObject(self.highPreset, forKey: "highPreset")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? AutodownloadSettings {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: AutodownloadSettings, rhs: AutodownloadSettings) -> Bool {
|
||||
return lhs.lowPreset == rhs.lowPreset && lhs.mediumPreset == rhs.mediumPreset && lhs.highPreset == rhs.highPreset
|
||||
}
|
||||
}
|
||||
|
||||
public func updateAutodownloadSettingsInteractively(accountManager: AccountManager, _ f: @escaping (AutodownloadSettings) -> AutodownloadSettings) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(SharedDataKeys.autodownloadSettings, { entry in
|
||||
let currentSettings: AutodownloadSettings
|
||||
if let entry = entry as? AutodownloadSettings {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = AutodownloadSettings.defaultSettings
|
||||
}
|
||||
return f(currentSettings)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension AutodownloadPresetSettings {
|
||||
init(apiAutodownloadSettings: Api.AutoDownloadSettings) {
|
||||
switch apiAutodownloadSettings {
|
||||
case let .autoDownloadSettings(flags, photoSizeMax, videoSizeMax, fileSizeMax):
|
||||
self.init(disabled: (flags & (1 << 0)) != 0, photoSizeMax: photoSizeMax, videoSizeMax: videoSizeMax, fileSizeMax: fileSizeMax, preloadLargeVideo: (flags & (1 << 1)) != 0, lessDataForPhoneCalls: (flags & (1 << 3)) != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AutodownloadSettings {
|
||||
init(apiAutodownloadSettings: Api.account.AutoDownloadSettings) {
|
||||
switch apiAutodownloadSettings {
|
||||
case let .autoDownloadSettings(low, medium, high):
|
||||
self.init(lowPreset: AutodownloadPresetSettings(apiAutodownloadSettings: low), mediumPreset: AutodownloadPresetSettings(apiAutodownloadSettings: medium), highPreset: AutodownloadPresetSettings(apiAutodownloadSettings: high))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apiAutodownloadPresetSettings(_ autodownloadPresetSettings: AutodownloadPresetSettings) -> Api.AutoDownloadSettings {
|
||||
var flags: Int32 = 0
|
||||
if autodownloadPresetSettings.disabled {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
if autodownloadPresetSettings.preloadLargeVideo {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
if autodownloadPresetSettings.lessDataForPhoneCalls {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
return .autoDownloadSettings(flags: flags, photoSizeMax: autodownloadPresetSettings.photoSizeMax, videoSizeMax: autodownloadPresetSettings.videoSizeMax, fileSizeMax: autodownloadPresetSettings.fileSizeMax)
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public class AutoremoveTimeoutMessageAttribute: MessageAttribute {
|
||||
public let timeout: Int32
|
||||
public let countdownBeginTime: Int32?
|
||||
|
||||
public var associatedMessageIds: [MessageId] = []
|
||||
|
||||
public init(timeout: Int32, countdownBeginTime: Int32?) {
|
||||
self.timeout = timeout
|
||||
self.countdownBeginTime = countdownBeginTime
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.timeout = decoder.decodeInt32ForKey("t", orElse: 0)
|
||||
self.countdownBeginTime = decoder.decodeOptionalInt32ForKey("c")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.timeout, forKey: "t")
|
||||
if let countdownBeginTime = self.countdownBeginTime {
|
||||
encoder.encodeInt32(countdownBeginTime, forKey: "c")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "c")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
public var containsSecretMedia: Bool {
|
||||
var found = false
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
if attribute.timeout > 1 * 60 {
|
||||
return false
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
for media in self.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage:
|
||||
return true
|
||||
case let file as TelegramMediaFile:
|
||||
if file.isVideo || file.isAnimated || file.isVoice {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
76
submodules/TelegramCore/TelegramCore/BlockedPeers.swift
Normal file
76
submodules/TelegramCore/TelegramCore/BlockedPeers.swift
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public func requestBlockedPeers(account: Account) -> Signal<[Peer], NoError> {
|
||||
return account.network.request(Api.functions.contacts.getBlocked(offset: 0, limit: 100))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
||||
return account.postbox.transaction { transaction -> [Peer] in
|
||||
var peers: [Peer] = []
|
||||
let apiUsers: [Api.User]
|
||||
switch result {
|
||||
case let .blocked(_, users):
|
||||
apiUsers = users
|
||||
case let .blockedSlice(_, _, users):
|
||||
apiUsers = users
|
||||
}
|
||||
for user in apiUsers {
|
||||
let parsed = TelegramUser(user: user)
|
||||
peers.append(parsed)
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
|
||||
return peers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func requestUpdatePeerIsBlocked(account: Account, peerId: PeerId, isBlocked: Bool) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
let signal: Signal<Api.Bool, MTRpcError>
|
||||
if isBlocked {
|
||||
signal = account.network.request(Api.functions.contacts.block(id: inputUser))
|
||||
} else {
|
||||
signal = account.network.request(Api.functions.contacts.unblock(id: inputUser))
|
||||
}
|
||||
return signal
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if result != nil {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
let previous: CachedUserData
|
||||
if let current = current as? CachedUserData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedUserData()
|
||||
}
|
||||
return previous.withUpdatedIsBlocked(isBlocked)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
237
submodules/TelegramCore/TelegramCore/BlockedPeersContext.swift
Normal file
237
submodules/TelegramCore/TelegramCore/BlockedPeersContext.swift
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public struct BlockedPeersContextState: Equatable {
|
||||
public var isLoadingMore: Bool
|
||||
public var canLoadMore: Bool
|
||||
public var totalCount: Int?
|
||||
public var peers: [RenderedPeer]
|
||||
}
|
||||
|
||||
public enum BlockedPeersContextAddError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public enum BlockedPeersContextRemoveError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public final class BlockedPeersContext {
|
||||
private let account: Account
|
||||
private var _state: BlockedPeersContextState {
|
||||
didSet {
|
||||
if self._state != oldValue {
|
||||
self._statePromise.set(.single(self._state))
|
||||
}
|
||||
}
|
||||
}
|
||||
private let _statePromise = Promise<BlockedPeersContextState>()
|
||||
public var state: Signal<BlockedPeersContextState, NoError> {
|
||||
return self._statePromise.get()
|
||||
}
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
public init(account: Account) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.account = account
|
||||
self._state = BlockedPeersContextState(isLoadingMore: false, canLoadMore: true, totalCount: nil, peers: [])
|
||||
self._statePromise.set(.single(self._state))
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
if self._state.isLoadingMore || !self._state.canLoadMore {
|
||||
return
|
||||
}
|
||||
self._state = BlockedPeersContextState(isLoadingMore: true, canLoadMore: self._state.canLoadMore, totalCount: self._state.totalCount, peers: self._state.peers)
|
||||
let postbox = self.account.postbox
|
||||
self.disposable.set((self.account.network.request(Api.functions.contacts.getBlocked(offset: Int32(self._state.peers.count), limit: 64))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<(peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?), NoError> in
|
||||
return postbox.transaction { transaction -> (peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?) in
|
||||
switch result {
|
||||
case let .blocked(blocked, users):
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
peers.append(TelegramUser(user: user))
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
|
||||
|
||||
var renderedPeers: [RenderedPeer] = []
|
||||
for blockedPeer in blocked {
|
||||
switch blockedPeer {
|
||||
case let .contactBlocked(userId, _):
|
||||
if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)) {
|
||||
renderedPeers.append(RenderedPeer(peer: peer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (renderedPeers, false, nil)
|
||||
case let .blockedSlice(count, blocked, users):
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
peers.append(TelegramUser(user: user))
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
|
||||
|
||||
var renderedPeers: [RenderedPeer] = []
|
||||
for blockedPeer in blocked {
|
||||
switch blockedPeer {
|
||||
case let .contactBlocked(userId, _):
|
||||
if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)) {
|
||||
renderedPeers.append(RenderedPeer(peer: peer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (renderedPeers, true, Int(count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] (peers, canLoadMore, totalCount) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var mergedPeers = strongSelf._state.peers
|
||||
var existingPeerIds = Set(mergedPeers.map { $0.peerId })
|
||||
for peer in peers {
|
||||
if !existingPeerIds.contains(peer.peerId) {
|
||||
existingPeerIds.insert(peer.peerId)
|
||||
mergedPeers.append(peer)
|
||||
}
|
||||
}
|
||||
|
||||
let updatedTotalCount: Int?
|
||||
if !canLoadMore {
|
||||
updatedTotalCount = mergedPeers.count
|
||||
} else if let totalCount = totalCount {
|
||||
updatedTotalCount = totalCount
|
||||
} else {
|
||||
updatedTotalCount = strongSelf._state.totalCount
|
||||
}
|
||||
|
||||
strongSelf._state = BlockedPeersContextState(isLoadingMore: false, canLoadMore: canLoadMore, totalCount: updatedTotalCount, peers: mergedPeers)
|
||||
}))
|
||||
}
|
||||
|
||||
public func add(peerId: PeerId) -> Signal<Never, BlockedPeersContextAddError> {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
let postbox = self.account.postbox
|
||||
let network = self.account.network
|
||||
return self.account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputUser)
|
||||
}
|
||||
|> introduceError(BlockedPeersContextAddError.self)
|
||||
|> mapToSignal { [weak self] inputUser -> Signal<Never, BlockedPeersContextAddError> in
|
||||
guard let inputUser = inputUser else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return network.request(Api.functions.contacts.block(id: inputUser))
|
||||
|> mapError { _ -> BlockedPeersContextAddError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Peer?, BlockedPeersContextAddError> in
|
||||
return postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> introduceError(BlockedPeersContextAddError.self)
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { peer -> Signal<Never, BlockedPeersContextAddError> in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var mergedPeers = strongSelf._state.peers
|
||||
let existingPeerIds = Set(mergedPeers.map { $0.peerId })
|
||||
if !existingPeerIds.contains(peer.id) {
|
||||
mergedPeers.insert(RenderedPeer(peer: peer), at: 0)
|
||||
}
|
||||
|
||||
let updatedTotalCount: Int?
|
||||
if let totalCount = strongSelf._state.totalCount {
|
||||
updatedTotalCount = totalCount + 1
|
||||
} else {
|
||||
updatedTotalCount = nil
|
||||
}
|
||||
|
||||
strongSelf._state = BlockedPeersContextState(isLoadingMore: strongSelf._state.isLoadingMore, canLoadMore: strongSelf._state.canLoadMore, totalCount: updatedTotalCount, peers: mergedPeers)
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func remove(peerId: PeerId) -> Signal<Never, BlockedPeersContextRemoveError> {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
let network = self.account.network
|
||||
return self.account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputUser)
|
||||
}
|
||||
|> introduceError(BlockedPeersContextRemoveError.self)
|
||||
|> mapToSignal { [weak self] inputUser -> Signal<Never, BlockedPeersContextRemoveError> in
|
||||
guard let inputUser = inputUser else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return network.request(Api.functions.contacts.unblock(id: inputUser))
|
||||
|> mapError { _ -> BlockedPeersContextRemoveError in
|
||||
return .generic
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { _ -> Signal<Never, BlockedPeersContextRemoveError> in
|
||||
guard let strongSelf = self else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var mergedPeers = strongSelf._state.peers
|
||||
var found = false
|
||||
for i in 0 ..< mergedPeers.count {
|
||||
if mergedPeers[i].peerId == peerId {
|
||||
found = true
|
||||
mergedPeers.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let updatedTotalCount: Int?
|
||||
if let totalCount = strongSelf._state.totalCount {
|
||||
if found {
|
||||
updatedTotalCount = totalCount - 1
|
||||
} else {
|
||||
updatedTotalCount = totalCount
|
||||
}
|
||||
} else {
|
||||
updatedTotalCount = nil
|
||||
}
|
||||
|
||||
strongSelf._state = BlockedPeersContextState(isLoadingMore: strongSelf._state.isLoadingMore, canLoadMore: strongSelf._state.canLoadMore, totalCount: updatedTotalCount, peers: mergedPeers)
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
submodules/TelegramCore/TelegramCore/BotInfo.swift
Normal file
68
submodules/TelegramCore/TelegramCore/BotInfo.swift
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public struct BotCommand: PostboxCoding, Equatable {
|
||||
public let text: String
|
||||
public let description: String
|
||||
|
||||
init(text: String, description: String) {
|
||||
self.text = text
|
||||
self.description = description
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.text = decoder.decodeStringForKey("t", orElse: "")
|
||||
self.description = decoder.decodeStringForKey("d", orElse: "")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.text, forKey: "t")
|
||||
encoder.encodeString(self.description, forKey: "d")
|
||||
}
|
||||
|
||||
public static func ==(lhs: BotCommand, rhs: BotCommand) -> Bool {
|
||||
return lhs.text == rhs.text && lhs.description == rhs.description
|
||||
}
|
||||
}
|
||||
|
||||
public final class BotInfo: PostboxCoding, Equatable {
|
||||
public let description: String
|
||||
public let commands: [BotCommand]
|
||||
|
||||
init(description: String, commands: [BotCommand]) {
|
||||
self.description = description
|
||||
self.commands = commands
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.description = decoder.decodeStringForKey("d", orElse: "")
|
||||
self.commands = decoder.decodeObjectArrayWithDecoderForKey("c")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.description, forKey: "d")
|
||||
encoder.encodeObjectArray(self.commands, forKey: "c")
|
||||
}
|
||||
|
||||
public static func ==(lhs: BotInfo, rhs: BotInfo) -> Bool {
|
||||
return lhs.description == rhs.description && lhs.commands == rhs.commands
|
||||
}
|
||||
}
|
||||
|
||||
extension BotInfo {
|
||||
convenience init(apiBotInfo: Api.BotInfo) {
|
||||
switch apiBotInfo {
|
||||
case let .botInfo(_, description, commands):
|
||||
self.init(description: description, commands: commands.map { command in
|
||||
switch command {
|
||||
case let .botCommand(command, description):
|
||||
return BotCommand(text: command, description: description)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
438
submodules/TelegramCore/TelegramCore/BotPaymentForm.swift
Normal file
438
submodules/TelegramCore/TelegramCore/BotPaymentForm.swift
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import MtProtoKitMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public struct BotPaymentInvoiceFields: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.rawValue = 0
|
||||
}
|
||||
|
||||
public static let name = BotPaymentInvoiceFields(rawValue: 1 << 0)
|
||||
public static let phone = BotPaymentInvoiceFields(rawValue: 1 << 1)
|
||||
public static let email = BotPaymentInvoiceFields(rawValue: 1 << 2)
|
||||
public static let shippingAddress = BotPaymentInvoiceFields(rawValue: 1 << 3)
|
||||
public static let flexibleShipping = BotPaymentInvoiceFields(rawValue: 1 << 4)
|
||||
}
|
||||
|
||||
public struct BotPaymentPrice {
|
||||
public let label: String
|
||||
public let amount: Int64
|
||||
|
||||
public init(label: String, amount: Int64) {
|
||||
self.label = label
|
||||
self.amount = amount
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotPaymentInvoice {
|
||||
public let isTest: Bool
|
||||
public let requestedFields: BotPaymentInvoiceFields
|
||||
public let currency: String
|
||||
public let prices: [BotPaymentPrice]
|
||||
}
|
||||
|
||||
public struct BotPaymentNativeProvider {
|
||||
public let name: String
|
||||
public let params: String
|
||||
}
|
||||
|
||||
public struct BotPaymentShippingAddress: Equatable {
|
||||
public let streetLine1: String
|
||||
public let streetLine2: String
|
||||
public let city: String
|
||||
public let state: String
|
||||
public let countryIso2: String
|
||||
public let postCode: String
|
||||
|
||||
public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) {
|
||||
self.streetLine1 = streetLine1
|
||||
self.streetLine2 = streetLine2
|
||||
self.city = city
|
||||
self.state = state
|
||||
self.countryIso2 = countryIso2
|
||||
self.postCode = postCode
|
||||
}
|
||||
|
||||
public static func ==(lhs: BotPaymentShippingAddress, rhs: BotPaymentShippingAddress) -> Bool {
|
||||
if lhs.streetLine1 != rhs.streetLine1 {
|
||||
return false
|
||||
}
|
||||
if lhs.streetLine2 != rhs.streetLine2 {
|
||||
return false
|
||||
}
|
||||
if lhs.city != rhs.city {
|
||||
return false
|
||||
}
|
||||
if lhs.state != rhs.state {
|
||||
return false
|
||||
}
|
||||
if lhs.countryIso2 != rhs.countryIso2 {
|
||||
return false
|
||||
}
|
||||
if lhs.postCode != rhs.postCode {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotPaymentRequestedInfo: Equatable {
|
||||
public let name: String?
|
||||
public let phone: String?
|
||||
public let email: String?
|
||||
public let shippingAddress: BotPaymentShippingAddress?
|
||||
|
||||
public init(name: String?, phone: String?, email: String?, shippingAddress: BotPaymentShippingAddress?) {
|
||||
self.name = name
|
||||
self.phone = phone
|
||||
self.email = email
|
||||
self.shippingAddress = shippingAddress
|
||||
}
|
||||
|
||||
public static func ==(lhs: BotPaymentRequestedInfo, rhs: BotPaymentRequestedInfo) -> Bool {
|
||||
if lhs.name != rhs.name {
|
||||
return false
|
||||
}
|
||||
if lhs.phone != rhs.phone {
|
||||
return false
|
||||
}
|
||||
if lhs.email != rhs.email {
|
||||
return false
|
||||
}
|
||||
if lhs.shippingAddress != rhs.shippingAddress {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public enum BotPaymentSavedCredentials: Equatable {
|
||||
case card(id: String, title: String)
|
||||
|
||||
public static func ==(lhs: BotPaymentSavedCredentials, rhs: BotPaymentSavedCredentials) -> Bool {
|
||||
switch lhs {
|
||||
case let .card(id, title):
|
||||
if case .card(id, title) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotPaymentForm {
|
||||
public let canSaveCredentials: Bool
|
||||
public let passwordMissing: Bool
|
||||
public let invoice: BotPaymentInvoice
|
||||
public let providerId: PeerId
|
||||
public let url: String
|
||||
public let nativeProvider: BotPaymentNativeProvider?
|
||||
public let savedInfo: BotPaymentRequestedInfo?
|
||||
public let savedCredentials: BotPaymentSavedCredentials?
|
||||
}
|
||||
|
||||
public enum BotPaymentFormRequestError {
|
||||
case generic
|
||||
}
|
||||
|
||||
extension BotPaymentInvoice {
|
||||
init(apiInvoice: Api.Invoice) {
|
||||
switch apiInvoice {
|
||||
case let .invoice(flags, currency, prices):
|
||||
var fields = BotPaymentInvoiceFields()
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
fields.insert(.name)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
fields.insert(.phone)
|
||||
}
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
fields.insert(.email)
|
||||
}
|
||||
if (flags & (1 << 4)) != 0 {
|
||||
fields.insert(.shippingAddress)
|
||||
}
|
||||
if (flags & (1 << 5)) != 0 {
|
||||
fields.insert(.flexibleShipping)
|
||||
}
|
||||
self.init(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map {
|
||||
switch $0 {
|
||||
case let .labeledPrice(label, amount):
|
||||
return BotPaymentPrice(label: label, amount: amount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BotPaymentRequestedInfo {
|
||||
init(apiInfo: Api.PaymentRequestedInfo) {
|
||||
switch apiInfo {
|
||||
case let .paymentRequestedInfo(_, name, phone, email, shippingAddress):
|
||||
var parsedShippingAddress: BotPaymentShippingAddress?
|
||||
if let shippingAddress = shippingAddress {
|
||||
switch shippingAddress {
|
||||
case let .postAddress(streetLine1, streetLine2, city, state, countryIso2, postCode):
|
||||
parsedShippingAddress = BotPaymentShippingAddress(streetLine1: streetLine1, streetLine2: streetLine2, city: city, state: state, countryIso2: countryIso2, postCode: postCode)
|
||||
}
|
||||
}
|
||||
self.init(name: name, phone: phone, email: email, shippingAddress: parsedShippingAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
|
||||
return network.request(Api.functions.payments.getPaymentForm(msgId: messageId.id))
|
||||
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
|
||||
return postbox.transaction { transaction -> BotPaymentForm in
|
||||
switch result {
|
||||
case let .paymentForm(flags, _, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, apiUsers):
|
||||
var peers: [Peer] = []
|
||||
for user in apiUsers {
|
||||
let parsed = TelegramUser(user: user)
|
||||
peers.append(parsed)
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
|
||||
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||
var parsedNativeProvider: BotPaymentNativeProvider?
|
||||
if let nativeProvider = nativeProvider, let nativeParams = nativeParams {
|
||||
switch nativeParams {
|
||||
case let .dataJSON(data):
|
||||
parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data)
|
||||
}
|
||||
}
|
||||
let parsedSavedInfo = savedInfo.flatMap(BotPaymentRequestedInfo.init)
|
||||
var parsedSavedCredentials: BotPaymentSavedCredentials?
|
||||
if let savedCredentials = savedCredentials {
|
||||
switch savedCredentials {
|
||||
case let .paymentSavedCredentialsCard(id, title):
|
||||
parsedSavedCredentials = .card(id: id, title: title)
|
||||
}
|
||||
}
|
||||
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: providerId), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
|
||||
}
|
||||
} |> mapError { _ -> BotPaymentFormRequestError in return .generic }
|
||||
}
|
||||
}
|
||||
|
||||
public enum ValidateBotPaymentFormError {
|
||||
case generic
|
||||
case shippingNotAvailable
|
||||
case addressStateInvalid
|
||||
case addressPostcodeInvalid
|
||||
case addressCityInvalid
|
||||
case nameInvalid
|
||||
case emailInvalid
|
||||
case phoneInvalid
|
||||
}
|
||||
|
||||
public struct BotPaymentShippingOption {
|
||||
public let id: String
|
||||
public let title: String
|
||||
public let prices: [BotPaymentPrice]
|
||||
}
|
||||
|
||||
public struct BotPaymentValidatedFormInfo {
|
||||
public let id: String?
|
||||
public let shippingOptions: [BotPaymentShippingOption]?
|
||||
}
|
||||
|
||||
extension BotPaymentShippingOption {
|
||||
init(apiOption: Api.ShippingOption) {
|
||||
switch apiOption {
|
||||
case let .shippingOption(id, title, prices):
|
||||
self.init(id: id, title: title, prices: prices.map {
|
||||
switch $0 {
|
||||
case let .labeledPrice(label, amount):
|
||||
return BotPaymentPrice(label: label, amount: amount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func validateBotPaymentForm(network: Network, saveInfo: Bool, messageId: MessageId, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
|
||||
var flags: Int32 = 0
|
||||
if saveInfo {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
var infoFlags: Int32 = 0
|
||||
if let _ = formInfo.name {
|
||||
infoFlags |= (1 << 0)
|
||||
}
|
||||
if let _ = formInfo.phone {
|
||||
infoFlags |= (1 << 1)
|
||||
}
|
||||
if let _ = formInfo.email {
|
||||
infoFlags |= (1 << 2)
|
||||
}
|
||||
var apiShippingAddress: Api.PostAddress?
|
||||
if let address = formInfo.shippingAddress {
|
||||
infoFlags |= (1 << 3)
|
||||
apiShippingAddress = .postAddress(streetLine1: address.streetLine1, streetLine2: address.streetLine2, city: address.city, state: address.state, countryIso2: address.countryIso2, postCode: address.postCode)
|
||||
}
|
||||
return network.request(Api.functions.payments.validateRequestedInfo(flags: flags, msgId: messageId.id, info: .paymentRequestedInfo(flags: infoFlags, name: formInfo.name, phone: formInfo.phone, email: formInfo.email, shippingAddress: apiShippingAddress)))
|
||||
|> mapError { error -> ValidateBotPaymentFormError in
|
||||
if error.errorDescription == "SHIPPING_NOT_AVAILABLE" {
|
||||
return .shippingNotAvailable
|
||||
} else if error.errorDescription == "ADDRESS_STATE_INVALID" {
|
||||
return .addressStateInvalid
|
||||
} else if error.errorDescription == "ADDRESS_POSTCODE_INVALID" {
|
||||
return .addressPostcodeInvalid
|
||||
} else if error.errorDescription == "ADDRESS_CITY_INVALID" {
|
||||
return .addressCityInvalid
|
||||
} else if error.errorDescription == "REQ_INFO_NAME_INVALID" {
|
||||
return .nameInvalid
|
||||
} else if error.errorDescription == "REQ_INFO_EMAIL_INVALID" {
|
||||
return .emailInvalid
|
||||
} else if error.errorDescription == "REQ_INFO_PHONE_INVALID" {
|
||||
return .phoneInvalid
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> map { result -> BotPaymentValidatedFormInfo in
|
||||
switch result {
|
||||
case let .validatedRequestedInfo(_, id, shippingOptions):
|
||||
return BotPaymentValidatedFormInfo(id: id, shippingOptions: shippingOptions.flatMap {
|
||||
return $0.map(BotPaymentShippingOption.init)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum BotPaymentCredentials {
|
||||
case generic(data: String, saveOnServer: Bool)
|
||||
case saved(id: String, tempPassword: Data)
|
||||
case applePay(data: String)
|
||||
}
|
||||
|
||||
public enum SendBotPaymentFormError {
|
||||
case generic
|
||||
case precheckoutFailed
|
||||
case paymentFailed
|
||||
case alreadyPaid
|
||||
}
|
||||
|
||||
public enum SendBotPaymentResult {
|
||||
case done
|
||||
case externalVerificationRequired(url: String)
|
||||
}
|
||||
|
||||
public func sendBotPaymentForm(account: Account, messageId: MessageId, validatedInfoId: String?, shippingOptionId: String?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
|
||||
let apiCredentials: Api.InputPaymentCredentials
|
||||
switch credentials {
|
||||
case let .generic(data, saveOnServer):
|
||||
var credentialsFlags: Int32 = 0
|
||||
if saveOnServer {
|
||||
credentialsFlags |= (1 << 0)
|
||||
}
|
||||
apiCredentials = .inputPaymentCredentials(flags: credentialsFlags, data: .dataJSON(data: data))
|
||||
case let .saved(id, tempPassword):
|
||||
apiCredentials = .inputPaymentCredentialsSaved(id: id, tmpPassword: Buffer(data: tempPassword))
|
||||
case let .applePay(data):
|
||||
apiCredentials = .inputPaymentCredentialsApplePay(paymentData: .dataJSON(data: data))
|
||||
}
|
||||
var flags: Int32 = 0
|
||||
if validatedInfoId != nil {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
if shippingOptionId != nil {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
return account.network.request(Api.functions.payments.sendPaymentForm(flags: flags, msgId: messageId.id, requestedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, credentials: apiCredentials))
|
||||
|> map { result -> SendBotPaymentResult in
|
||||
switch result {
|
||||
case let .paymentResult(updates):
|
||||
account.stateManager.addUpdates(updates)
|
||||
return .done
|
||||
case let .paymentVerficationNeeded(url):
|
||||
return .externalVerificationRequired(url: url)
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
|
||||
if error.errorDescription == "BOT_PRECHECKOUT_FAILED" {
|
||||
return .fail(.precheckoutFailed)
|
||||
} else if error.errorDescription == "PAYMENT_FAILED" {
|
||||
return .fail(.paymentFailed)
|
||||
} else if error.errorDescription == "INVOICE_ALREADY_PAID" {
|
||||
return .fail(.alreadyPaid)
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotPaymentReceipt {
|
||||
public let invoice: BotPaymentInvoice
|
||||
public let info: BotPaymentRequestedInfo?
|
||||
public let shippingOption: BotPaymentShippingOption?
|
||||
public let credentialsTitle: String
|
||||
}
|
||||
|
||||
public func requestBotPaymentReceipt(network: Network, messageId: MessageId) -> Signal<BotPaymentReceipt, NoError> {
|
||||
return network.request(Api.functions.payments.getPaymentReceipt(msgId: messageId.id))
|
||||
|> retryRequest
|
||||
|> map { result -> BotPaymentReceipt in
|
||||
switch result {
|
||||
case let .paymentReceipt(_, _, _, invoice, _, info, shipping, _, _, credentialsTitle, _):
|
||||
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||
let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init)
|
||||
let shippingOption = shipping.flatMap(BotPaymentShippingOption.init)
|
||||
return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotPaymentInfo: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.rawValue = 0
|
||||
}
|
||||
|
||||
public static let paymentInfo = BotPaymentInfo(rawValue: 1 << 0)
|
||||
public static let shippingInfo = BotPaymentInfo(rawValue: 1 << 1)
|
||||
}
|
||||
|
||||
public func clearBotPaymentInfo(network: Network, info: BotPaymentInfo) -> Signal<Void, NoError> {
|
||||
var flags: Int32 = 0
|
||||
if info.contains(.paymentInfo) {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
if info.contains(.shippingInfo) {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
return network.request(Api.functions.payments.clearSavedInfo(flags: flags))
|
||||
|> retryRequest
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
358
submodules/TelegramCore/TelegramCore/Buffer.swift
Normal file
358
submodules/TelegramCore/TelegramCore/Buffer.swift
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
import Foundation
|
||||
|
||||
public struct Int128 {
|
||||
public var _0: Int64
|
||||
public var _1: Int64
|
||||
}
|
||||
|
||||
public struct Int256 {
|
||||
public var _0: Int64
|
||||
public var _1: Int64
|
||||
public var _2: Int64
|
||||
public var _3: Int64
|
||||
}
|
||||
|
||||
func serializeInt32(_ value: Int32, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(-1471112230)
|
||||
}
|
||||
buffer.appendInt32(value)
|
||||
}
|
||||
|
||||
func serializeInt64(_ value: Int64, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(570911930)
|
||||
}
|
||||
buffer.appendInt64(value)
|
||||
}
|
||||
|
||||
func serializeDouble(_ value: Double, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(571523412)
|
||||
}
|
||||
buffer.appendDouble(value)
|
||||
}
|
||||
|
||||
func serializeString(_ value: String, buffer: Buffer, boxed: Bool) {
|
||||
let stringBuffer = Buffer()
|
||||
let data = value.data(using: .utf8, allowLossyConversion: true) ?? Data()
|
||||
data.withUnsafeBytes { bytes in
|
||||
stringBuffer.appendBytes(bytes, length: UInt(data.count))
|
||||
}
|
||||
serializeBytes(stringBuffer, buffer: buffer, boxed: boxed)
|
||||
}
|
||||
|
||||
func serializeBytes(_ value: Buffer, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(-1255641564)
|
||||
}
|
||||
|
||||
var length: Int32 = Int32(value.size)
|
||||
var padding: Int32 = 0
|
||||
if (length >= 254)
|
||||
{
|
||||
var tmp: UInt8 = 254
|
||||
buffer.appendBytes(&tmp, length: 1)
|
||||
buffer.appendBytes(&length, length: 3)
|
||||
padding = (((length % 4) == 0 ? length : (length + 4 - (length % 4)))) - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.appendBytes(&length, length: 1)
|
||||
|
||||
let e1 = (((length + 1) % 4) == 0 ? (length + 1) : ((length + 1) + 4 - ((length + 1) % 4)))
|
||||
padding = (e1) - (length + 1)
|
||||
}
|
||||
|
||||
if value.size != 0 {
|
||||
buffer.appendBytes(value.data!, length: UInt(length))
|
||||
}
|
||||
|
||||
var i: Int32 = 0
|
||||
var tmp: UInt8 = 0
|
||||
while i < padding {
|
||||
buffer.appendBytes(&tmp, length: 1)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
func serializeInt128(_ value: Int128, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(1270167083)
|
||||
}
|
||||
|
||||
buffer.appendInt64(value._0)
|
||||
buffer.appendInt64(value._1)
|
||||
}
|
||||
|
||||
func serializeInt256(_ value: Int256, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(153731887)
|
||||
}
|
||||
|
||||
buffer.appendInt64(value._0)
|
||||
buffer.appendInt64(value._1)
|
||||
buffer.appendInt64(value._2)
|
||||
buffer.appendInt64(value._3)
|
||||
}
|
||||
|
||||
func parseInt128(_ reader: BufferReader) -> Int128? {
|
||||
let _0 = reader.readInt64()
|
||||
let _1 = reader.readInt64()
|
||||
if _0 != nil && _1 != nil {
|
||||
return Int128(_0: _0!, _1: _1!)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseInt256(_ reader: BufferReader) -> Int256? {
|
||||
let _0 = reader.readInt64()
|
||||
let _1 = reader.readInt64()
|
||||
let _2 = reader.readInt64()
|
||||
let _3 = reader.readInt64()
|
||||
if _0 != nil && _1 != nil && _2 != nil && _3 != nil {
|
||||
return Int256(_0: _0!, _1: _1!, _2: _2!, _3: _3!)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func roundUp(_ numToRound: Int, multiple: Int) -> Int
|
||||
{
|
||||
if multiple == 0 {
|
||||
return numToRound
|
||||
}
|
||||
|
||||
let remainder = numToRound % multiple
|
||||
if remainder == 0 {
|
||||
return numToRound;
|
||||
}
|
||||
|
||||
return numToRound + multiple - remainder
|
||||
}
|
||||
|
||||
func parseBytes(_ reader: BufferReader) -> Buffer? {
|
||||
if let tmp = reader.readBytesAsInt32(1) {
|
||||
var paddingBytes: Int = 0
|
||||
var length: Int = 0
|
||||
if tmp == 254 {
|
||||
if let len = reader.readBytesAsInt32(3) {
|
||||
length = Int(len)
|
||||
paddingBytes = roundUp(length, multiple: 4) - length
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
else {
|
||||
length = Int(tmp)
|
||||
paddingBytes = roundUp(length + 1, multiple: 4) - (length + 1)
|
||||
}
|
||||
|
||||
let buffer = reader.readBuffer(length)
|
||||
reader.skip(paddingBytes)
|
||||
return buffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseString(_ reader: BufferReader) -> String? {
|
||||
if let buffer = parseBytes(reader) {
|
||||
return (NSString(data: buffer.makeData() as Data, encoding: String.Encoding.utf8.rawValue) as? String) ?? ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public class Buffer: CustomStringConvertible {
|
||||
var data: UnsafeMutableRawPointer?
|
||||
var _size: UInt = 0
|
||||
private var capacity: UInt = 0
|
||||
private let freeWhenDone: Bool
|
||||
|
||||
public var size: Int {
|
||||
return Int(self._size)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if self.freeWhenDone {
|
||||
free(self.data)
|
||||
}
|
||||
}
|
||||
|
||||
public init(memory: UnsafeMutableRawPointer?, size: Int, capacity: Int, freeWhenDone: Bool) {
|
||||
self.data = memory
|
||||
self._size = UInt(size)
|
||||
self.capacity = UInt(capacity)
|
||||
self.freeWhenDone = freeWhenDone
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.data = nil
|
||||
self._size = 0
|
||||
self.capacity = 0
|
||||
self.freeWhenDone = true
|
||||
}
|
||||
|
||||
convenience public init(data: Data?) {
|
||||
self.init()
|
||||
|
||||
if let data = data {
|
||||
data.withUnsafeBytes { bytes in
|
||||
self.appendBytes(bytes, length: UInt(data.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func makeData() -> Data {
|
||||
return self.withUnsafeMutablePointer { pointer, size -> Data in
|
||||
if let pointer = pointer {
|
||||
return Data(bytes: pointer.assumingMemoryBound(to: UInt8.self), count: Int(size))
|
||||
} else {
|
||||
return Data()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
get {
|
||||
var string = ""
|
||||
if let data = self.data {
|
||||
var i: UInt = 0
|
||||
let bytes = data.assumingMemoryBound(to: UInt8.self)
|
||||
while i < _size && i < 8 {
|
||||
string += String(format: "%02x", Int(bytes.advanced(by: Int(i)).pointee))
|
||||
i += 1
|
||||
}
|
||||
if i < _size {
|
||||
string += "...\(_size)b"
|
||||
}
|
||||
} else {
|
||||
string += "<null>"
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
public func appendBytes(_ bytes: UnsafeRawPointer, length: UInt) {
|
||||
if self.capacity < self._size + length {
|
||||
self.capacity = self._size + length + 128
|
||||
if self.data == nil {
|
||||
self.data = malloc(Int(self.capacity))!
|
||||
}
|
||||
else {
|
||||
self.data = realloc(self.data, Int(self.capacity))!
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(self.data?.advanced(by: Int(self._size)), bytes, Int(length))
|
||||
self._size += length
|
||||
}
|
||||
|
||||
public func appendBuffer(_ buffer: Buffer) {
|
||||
if self.capacity < self._size + buffer._size {
|
||||
self.capacity = self._size + buffer._size + 128
|
||||
if self.data == nil {
|
||||
self.data = malloc(Int(self.capacity))!
|
||||
}
|
||||
else {
|
||||
self.data = realloc(self.data, Int(self.capacity))!
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(self.data?.advanced(by: Int(self._size)), buffer.data, Int(buffer._size))
|
||||
}
|
||||
|
||||
public func appendInt32(_ value: Int32) {
|
||||
var v = value
|
||||
self.appendBytes(&v, length: 4)
|
||||
}
|
||||
|
||||
public func appendInt64(_ value: Int64) {
|
||||
var v = value
|
||||
self.appendBytes(&v, length: 8)
|
||||
}
|
||||
|
||||
public func appendDouble(_ value: Double) {
|
||||
var v = value
|
||||
self.appendBytes(&v, length: 8)
|
||||
}
|
||||
|
||||
public func withUnsafeMutablePointer<R>(_ f: (UnsafeMutableRawPointer?, UInt) -> R) -> R {
|
||||
return f(self.data, self._size)
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferReader {
|
||||
private let buffer: Buffer
|
||||
public private(set) var offset: UInt = 0
|
||||
|
||||
public init(_ buffer: Buffer) {
|
||||
self.buffer = buffer
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
self.offset = 0
|
||||
}
|
||||
|
||||
public func skip(_ count: Int) {
|
||||
self.offset = min(self.buffer._size, self.offset + UInt(count))
|
||||
}
|
||||
|
||||
public func readInt32() -> Int32? {
|
||||
if self.offset + 4 <= self.buffer._size {
|
||||
let value: Int32 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int32.self).pointee
|
||||
self.offset += 4
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func readInt64() -> Int64? {
|
||||
if self.offset + 8 <= self.buffer._size {
|
||||
let value: Int64 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int64.self).pointee
|
||||
self.offset += 8
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func readDouble() -> Double? {
|
||||
if self.offset + 8 <= self.buffer._size {
|
||||
let value: Double = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Double.self).pointee
|
||||
self.offset += 8
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func readBytesAsInt32(_ count: Int) -> Int32? {
|
||||
if count == 0 {
|
||||
return 0
|
||||
}
|
||||
else if count > 0 && count <= 4 || self.offset + UInt(count) <= self.buffer._size {
|
||||
var value: Int32 = 0
|
||||
memcpy(&value, self.buffer.data?.advanced(by: Int(self.offset)), count)
|
||||
self.offset += UInt(count)
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func readBuffer(_ count: Int) -> Buffer? {
|
||||
if count >= 0 && self.offset + UInt(count) <= self.buffer._size {
|
||||
let buffer = Buffer()
|
||||
buffer.appendBytes((self.buffer.data?.advanced(by: Int(self.offset)))!, length: UInt(count))
|
||||
self.offset += UInt(count)
|
||||
return buffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func withReadBufferNoCopy<T>(_ count: Int, _ f: (Buffer) -> T) -> T? {
|
||||
if count >= 0 && self.offset + UInt(count) <= self.buffer._size {
|
||||
return f(Buffer(memory: self.buffer.data!.advanced(by: Int(self.offset)), size: count, capacity: count, freeWhenDone: false))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public struct CacheStorageSettings: PreferencesEntry, Equatable {
|
||||
public let defaultCacheStorageTimeout: Int32
|
||||
|
||||
public static var defaultSettings: CacheStorageSettings {
|
||||
return CacheStorageSettings(defaultCacheStorageTimeout: Int32.max)
|
||||
}
|
||||
|
||||
init(defaultCacheStorageTimeout: Int32) {
|
||||
self.defaultCacheStorageTimeout = defaultCacheStorageTimeout
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.defaultCacheStorageTimeout = decoder.decodeInt32ForKey("dt", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.defaultCacheStorageTimeout, forKey: "dt")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? CacheStorageSettings {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: CacheStorageSettings, rhs: CacheStorageSettings) -> Bool {
|
||||
return lhs.defaultCacheStorageTimeout == rhs.defaultCacheStorageTimeout
|
||||
}
|
||||
|
||||
public func withUpdatedDefaultCacheStorageTimeout(_ defaultCacheStorageTimeout: Int32) -> CacheStorageSettings {
|
||||
return CacheStorageSettings(defaultCacheStorageTimeout: defaultCacheStorageTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateCacheStorageSettingsInteractively(accountManager: AccountManager, _ f: @escaping (CacheStorageSettings) -> CacheStorageSettings) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(SharedDataKeys.cacheStorageSettings, { entry in
|
||||
let currentSettings: CacheStorageSettings
|
||||
if let entry = entry as? CacheStorageSettings {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = CacheStorageSettings.defaultSettings
|
||||
}
|
||||
return f(currentSettings)
|
||||
})
|
||||
}
|
||||
}
|
||||
412
submodules/TelegramCore/TelegramCore/CachedChannelData.swift
Normal file
412
submodules/TelegramCore/TelegramCore/CachedChannelData.swift
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public struct CachedChannelFlags: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init() {
|
||||
self.rawValue = 0
|
||||
}
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let canDisplayParticipants = CachedChannelFlags(rawValue: 1 << 0)
|
||||
public static let canChangeUsername = CachedChannelFlags(rawValue: 1 << 1)
|
||||
public static let canSetStickerSet = CachedChannelFlags(rawValue: 1 << 2)
|
||||
public static let preHistoryEnabled = CachedChannelFlags(rawValue: 1 << 3)
|
||||
public static let canViewStats = CachedChannelFlags(rawValue: 1 << 4)
|
||||
}
|
||||
|
||||
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
||||
public let memberCount: Int32?
|
||||
public let adminCount: Int32?
|
||||
public let bannedCount: Int32?
|
||||
public let kickedCount: Int32?
|
||||
|
||||
init(memberCount: Int32?, adminCount: Int32?, bannedCount: Int32?, kickedCount: Int32?) {
|
||||
self.memberCount = memberCount
|
||||
self.adminCount = adminCount
|
||||
self.bannedCount = bannedCount
|
||||
self.kickedCount = kickedCount
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
if let memberCount = decoder.decodeOptionalInt32ForKey("p.m") {
|
||||
self.memberCount = memberCount
|
||||
} else {
|
||||
self.memberCount = nil
|
||||
}
|
||||
if let adminCount = decoder.decodeOptionalInt32ForKey("p.a") {
|
||||
self.adminCount = adminCount
|
||||
} else {
|
||||
self.adminCount = nil
|
||||
}
|
||||
if let bannedCount = decoder.decodeOptionalInt32ForKey("p.b") {
|
||||
self.bannedCount = bannedCount
|
||||
} else {
|
||||
self.bannedCount = nil
|
||||
}
|
||||
if let kickedCount = decoder.decodeOptionalInt32ForKey("p.k") {
|
||||
self.kickedCount = kickedCount
|
||||
} else {
|
||||
self.kickedCount = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let memberCount = self.memberCount {
|
||||
encoder.encodeInt32(memberCount, forKey: "p.m")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p.m")
|
||||
}
|
||||
if let adminCount = self.adminCount {
|
||||
encoder.encodeInt32(adminCount, forKey: "p.a")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p.a")
|
||||
}
|
||||
if let bannedCount = self.bannedCount {
|
||||
encoder.encodeInt32(bannedCount, forKey: "p.b")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p.b")
|
||||
}
|
||||
if let kickedCount = self.kickedCount {
|
||||
encoder.encodeInt32(kickedCount, forKey: "p.k")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p.k")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: CachedChannelParticipantsSummary, rhs: CachedChannelParticipantsSummary) -> Bool {
|
||||
return lhs.memberCount == rhs.memberCount && lhs.adminCount == rhs.adminCount && lhs.bannedCount == rhs.bannedCount && lhs.kickedCount == rhs.kickedCount
|
||||
}
|
||||
|
||||
public func withUpdatedMemberCount(_ memberCount: Int32?) -> CachedChannelParticipantsSummary {
|
||||
return CachedChannelParticipantsSummary(memberCount: memberCount, adminCount: self.adminCount, bannedCount: self.bannedCount, kickedCount: self.kickedCount)
|
||||
}
|
||||
|
||||
public func withUpdatedAdminCount(_ adminCount: Int32?) -> CachedChannelParticipantsSummary {
|
||||
return CachedChannelParticipantsSummary(memberCount: self.memberCount, adminCount: adminCount, bannedCount: self.bannedCount, kickedCount: self.kickedCount)
|
||||
}
|
||||
|
||||
public func withUpdatedBannedCount(_ bannedCount: Int32?) -> CachedChannelParticipantsSummary {
|
||||
return CachedChannelParticipantsSummary(memberCount: self.memberCount, adminCount: self.adminCount, bannedCount: bannedCount, kickedCount: self.kickedCount)
|
||||
}
|
||||
|
||||
public func withUpdatedKickedCount(_ kickedCount: Int32?) -> CachedChannelParticipantsSummary {
|
||||
return CachedChannelParticipantsSummary(memberCount: self.memberCount, adminCount: self.adminCount, bannedCount: self.bannedCount, kickedCount: kickedCount)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelMigrationReference: PostboxCoding, Equatable {
|
||||
public let maxMessageId: MessageId
|
||||
|
||||
public init(maxMessageId: MessageId) {
|
||||
self.maxMessageId = maxMessageId
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.maxMessageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("p", orElse: 0)), namespace: decoder.decodeInt32ForKey("n", orElse: 0), id: decoder.decodeInt32ForKey("i", orElse: 0))
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.maxMessageId.peerId.toInt64(), forKey: "p")
|
||||
encoder.encodeInt32(self.maxMessageId.namespace, forKey: "n")
|
||||
encoder.encodeInt32(self.maxMessageId.id, forKey: "i")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChannelMigrationReference, rhs: ChannelMigrationReference) -> Bool {
|
||||
return lhs.maxMessageId == rhs.maxMessageId
|
||||
}
|
||||
}
|
||||
|
||||
public final class CachedChannelData: CachedPeerData {
|
||||
public let isNotAccessible: Bool
|
||||
public let flags: CachedChannelFlags
|
||||
public let about: String?
|
||||
public let participantsSummary: CachedChannelParticipantsSummary
|
||||
public let exportedInvitation: ExportedInvitation?
|
||||
public let botInfos: [CachedPeerBotInfo]
|
||||
public let peerStatusSettings: PeerStatusSettings?
|
||||
public let pinnedMessageId: MessageId?
|
||||
public let stickerPack: StickerPackCollectionInfo?
|
||||
public let minAvailableMessageId: MessageId?
|
||||
public let migrationReference: ChannelMigrationReference?
|
||||
|
||||
public let linkedDiscussionPeerId: PeerId?
|
||||
|
||||
public let peerIds: Set<PeerId>
|
||||
public let messageIds: Set<MessageId>
|
||||
public var associatedHistoryMessageId: MessageId? {
|
||||
return self.migrationReference?.maxMessageId
|
||||
}
|
||||
|
||||
init() {
|
||||
self.isNotAccessible = false
|
||||
self.flags = []
|
||||
self.about = nil
|
||||
self.participantsSummary = CachedChannelParticipantsSummary(memberCount: nil, adminCount: nil, bannedCount: nil, kickedCount: nil)
|
||||
self.exportedInvitation = nil
|
||||
self.botInfos = []
|
||||
self.peerStatusSettings = nil
|
||||
self.pinnedMessageId = nil
|
||||
self.peerIds = Set()
|
||||
self.messageIds = Set()
|
||||
self.stickerPack = nil
|
||||
self.minAvailableMessageId = nil
|
||||
self.migrationReference = nil
|
||||
self.linkedDiscussionPeerId = nil
|
||||
}
|
||||
|
||||
init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: PeerId?) {
|
||||
self.isNotAccessible = isNotAccessible
|
||||
self.flags = flags
|
||||
self.about = about
|
||||
self.participantsSummary = participantsSummary
|
||||
self.exportedInvitation = exportedInvitation
|
||||
self.botInfos = botInfos
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
self.pinnedMessageId = pinnedMessageId
|
||||
self.stickerPack = stickerPack
|
||||
self.minAvailableMessageId = minAvailableMessageId
|
||||
self.migrationReference = migrationReference
|
||||
self.linkedDiscussionPeerId = linkedDiscussionPeerId
|
||||
|
||||
var peerIds = Set<PeerId>()
|
||||
for botInfo in botInfos {
|
||||
peerIds.insert(botInfo.peerId)
|
||||
}
|
||||
|
||||
if let linkedDiscussionPeerId = linkedDiscussionPeerId {
|
||||
peerIds.insert(linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
self.peerIds = peerIds
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
}
|
||||
self.messageIds = messageIds
|
||||
}
|
||||
|
||||
func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedAbout(_ about: String?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: PeerId?) -> CachedChannelData {
|
||||
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.isNotAccessible = decoder.decodeInt32ForKey("isNotAccessible", orElse: 0) != 0
|
||||
self.flags = CachedChannelFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
|
||||
self.about = decoder.decodeOptionalStringForKey("a")
|
||||
self.participantsSummary = CachedChannelParticipantsSummary(decoder: decoder)
|
||||
self.exportedInvitation = decoder.decodeObjectForKey("i", decoder: { ExportedInvitation(decoder: $0) }) as? ExportedInvitation
|
||||
self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo]
|
||||
var peerIds = Set<PeerId>()
|
||||
if let value = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(rawValue: value)
|
||||
} else {
|
||||
self.peerStatusSettings = nil
|
||||
}
|
||||
if let pinnedMessagePeerId = decoder.decodeOptionalInt64ForKey("pm.p"), let pinnedMessageNamespace = decoder.decodeOptionalInt32ForKey("pm.n"), let pinnedMessageId = decoder.decodeOptionalInt32ForKey("pm.i") {
|
||||
self.pinnedMessageId = MessageId(peerId: PeerId(pinnedMessagePeerId), namespace: pinnedMessageNamespace, id: pinnedMessageId)
|
||||
} else {
|
||||
self.pinnedMessageId = nil
|
||||
}
|
||||
|
||||
if let stickerPack = decoder.decodeObjectForKey("sp", decoder: { StickerPackCollectionInfo(decoder: $0) }) as? StickerPackCollectionInfo {
|
||||
self.stickerPack = stickerPack
|
||||
} else {
|
||||
self.stickerPack = nil
|
||||
}
|
||||
|
||||
if let minAvailableMessagePeerId = decoder.decodeOptionalInt64ForKey("ma.p"), let minAvailableMessageNamespace = decoder.decodeOptionalInt32ForKey("ma.n"), let minAvailableMessageId = decoder.decodeOptionalInt32ForKey("ma.i") {
|
||||
self.minAvailableMessageId = MessageId(peerId: PeerId(minAvailableMessagePeerId), namespace: minAvailableMessageNamespace, id: minAvailableMessageId)
|
||||
} else {
|
||||
self.minAvailableMessageId = nil
|
||||
}
|
||||
|
||||
self.migrationReference = decoder.decodeObjectForKey("mr", decoder: { ChannelMigrationReference(decoder: $0) }) as? ChannelMigrationReference
|
||||
|
||||
for botInfo in self.botInfos {
|
||||
peerIds.insert(botInfo.peerId)
|
||||
}
|
||||
|
||||
if let linkedDiscussionPeerId = decoder.decodeOptionalInt64ForKey("dgi") {
|
||||
self.linkedDiscussionPeerId = PeerId(linkedDiscussionPeerId)
|
||||
} else {
|
||||
self.linkedDiscussionPeerId = nil
|
||||
}
|
||||
|
||||
if let linkedDiscussionPeerId = self.linkedDiscussionPeerId {
|
||||
peerIds.insert(linkedDiscussionPeerId)
|
||||
}
|
||||
|
||||
self.peerIds = peerIds
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
}
|
||||
self.messageIds = messageIds
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.isNotAccessible ? 1 : 0, forKey: "isNotAccessible")
|
||||
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
|
||||
if let about = self.about {
|
||||
encoder.encodeString(about, forKey: "a")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "a")
|
||||
}
|
||||
self.participantsSummary.encode(encoder)
|
||||
if let exportedInvitation = self.exportedInvitation {
|
||||
encoder.encodeObject(exportedInvitation, forKey: "i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "i")
|
||||
}
|
||||
encoder.encodeObjectArray(self.botInfos, forKey: "b")
|
||||
if let peerStatusSettings = self.peerStatusSettings {
|
||||
encoder.encodeInt32(peerStatusSettings.rawValue, forKey: "pcs")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pcs")
|
||||
}
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
encoder.encodeInt64(pinnedMessageId.peerId.toInt64(), forKey: "pm.p")
|
||||
encoder.encodeInt32(pinnedMessageId.namespace, forKey: "pm.n")
|
||||
encoder.encodeInt32(pinnedMessageId.id, forKey: "pm.i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pm.p")
|
||||
encoder.encodeNil(forKey: "pm.n")
|
||||
encoder.encodeNil(forKey: "pm.i")
|
||||
}
|
||||
if let stickerPack = self.stickerPack {
|
||||
encoder.encodeObject(stickerPack, forKey: "sp")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "sp")
|
||||
}
|
||||
if let minAvailableMessageId = self.minAvailableMessageId {
|
||||
encoder.encodeInt64(minAvailableMessageId.peerId.toInt64(), forKey: "ma.p")
|
||||
encoder.encodeInt32(minAvailableMessageId.namespace, forKey: "ma.n")
|
||||
encoder.encodeInt32(minAvailableMessageId.id, forKey: "ma.i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "ma.p")
|
||||
encoder.encodeNil(forKey: "ma.n")
|
||||
encoder.encodeNil(forKey: "ma.i")
|
||||
}
|
||||
if let migrationReference = self.migrationReference {
|
||||
encoder.encodeObject(migrationReference, forKey: "mr")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "mr")
|
||||
}
|
||||
if let linkedDiscussionPeerId = self.linkedDiscussionPeerId {
|
||||
encoder.encodeInt64(linkedDiscussionPeerId.toInt64(), forKey: "dgi")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "dgi")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: CachedPeerData) -> Bool {
|
||||
guard let other = to as? CachedChannelData else {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.isNotAccessible != self.isNotAccessible {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.flags != self.flags {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.linkedDiscussionPeerId != self.linkedDiscussionPeerId {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.about != self.about {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.participantsSummary != self.participantsSummary {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.exportedInvitation != self.exportedInvitation {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.botInfos != self.botInfos {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.peerStatusSettings != self.peerStatusSettings {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.pinnedMessageId != self.pinnedMessageId {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.stickerPack != self.stickerPack {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.minAvailableMessageId != self.minAvailableMessageId {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.migrationReference != self.migrationReference {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
private enum ChannelParticipantValue: Int32 {
|
||||
case member = 0
|
||||
case creator = 1
|
||||
case editor = 2
|
||||
case moderator = 3
|
||||
}
|
||||
|
||||
public struct ChannelParticipantAdminInfo: PostboxCoding, Equatable {
|
||||
public let rights: TelegramChatAdminRights
|
||||
public let promotedBy: PeerId
|
||||
public let canBeEditedByAccountPeer: Bool
|
||||
|
||||
public init(rights: TelegramChatAdminRights, promotedBy: PeerId, canBeEditedByAccountPeer: Bool) {
|
||||
self.rights = rights
|
||||
self.promotedBy = promotedBy
|
||||
self.canBeEditedByAccountPeer = canBeEditedByAccountPeer
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.rights = decoder.decodeObjectForKey("r", decoder: { TelegramChatAdminRights(decoder: $0) }) as! TelegramChatAdminRights
|
||||
self.promotedBy = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||
self.canBeEditedByAccountPeer = decoder.decodeInt32ForKey("e", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObject(self.rights, forKey: "r")
|
||||
encoder.encodeInt64(self.promotedBy.toInt64(), forKey: "p")
|
||||
encoder.encodeInt32(self.canBeEditedByAccountPeer ? 1 : 0, forKey: "e")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChannelParticipantAdminInfo, rhs: ChannelParticipantAdminInfo) -> Bool {
|
||||
return lhs.rights == rhs.rights && lhs.promotedBy == rhs.promotedBy && lhs.canBeEditedByAccountPeer == rhs.canBeEditedByAccountPeer
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelParticipantBannedInfo: PostboxCoding, Equatable {
|
||||
public let rights: TelegramChatBannedRights
|
||||
public let restrictedBy: PeerId
|
||||
public let timestamp: Int32
|
||||
public let isMember: Bool
|
||||
|
||||
public init(rights: TelegramChatBannedRights, restrictedBy: PeerId, timestamp: Int32, isMember: Bool) {
|
||||
self.rights = rights
|
||||
self.restrictedBy = restrictedBy
|
||||
self.timestamp = timestamp
|
||||
self.isMember = isMember
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.rights = decoder.decodeObjectForKey("r", decoder: { TelegramChatBannedRights(decoder: $0) }) as! TelegramChatBannedRights
|
||||
self.restrictedBy = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||
self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0)
|
||||
self.isMember = decoder.decodeInt32ForKey("m", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObject(self.rights, forKey: "r")
|
||||
encoder.encodeInt64(self.restrictedBy.toInt64(), forKey: "p")
|
||||
encoder.encodeInt32(self.timestamp, forKey: "t")
|
||||
encoder.encodeInt32(self.isMember ? 1 : 0, forKey: "m")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChannelParticipantBannedInfo, rhs: ChannelParticipantBannedInfo) -> Bool {
|
||||
return lhs.rights == rhs.rights && lhs.restrictedBy == rhs.restrictedBy && lhs.timestamp == rhs.timestamp && lhs.isMember == rhs.isMember
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChannelParticipant: PostboxCoding, Equatable {
|
||||
case creator(id: PeerId)
|
||||
case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?)
|
||||
|
||||
public var peerId: PeerId {
|
||||
switch self {
|
||||
case let .creator(id):
|
||||
return id
|
||||
case let .member(id, _, _, _):
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChannelParticipant, rhs: ChannelParticipant) -> Bool {
|
||||
switch lhs {
|
||||
case let .member(lhsId, lhsInvitedAt, lhsAdminInfo, lhsBanInfo):
|
||||
if case let .member(rhsId, rhsInvitedAt, rhsAdminInfo, rhsBanInfo) = rhs {
|
||||
if lhsId != rhsId {
|
||||
return false
|
||||
}
|
||||
if lhsInvitedAt != rhsInvitedAt {
|
||||
return false
|
||||
}
|
||||
if lhsAdminInfo != rhsAdminInfo {
|
||||
return false
|
||||
}
|
||||
if lhsBanInfo != rhsBanInfo {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .creator(id):
|
||||
if case .creator(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("r", orElse: 0) {
|
||||
case ChannelParticipantValue.member.rawValue:
|
||||
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, banInfo: decoder.decodeObjectForKey("bi", decoder: { ChannelParticipantBannedInfo(decoder: $0) }) as? ChannelParticipantBannedInfo)
|
||||
case ChannelParticipantValue.creator.rawValue:
|
||||
self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)))
|
||||
default:
|
||||
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: nil, banInfo: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case let .member(id, invitedAt, adminInfo, banInfo):
|
||||
encoder.encodeInt32(ChannelParticipantValue.member.rawValue, forKey: "r")
|
||||
encoder.encodeInt64(id.toInt64(), forKey: "i")
|
||||
encoder.encodeInt32(invitedAt, forKey: "t")
|
||||
if let adminInfo = adminInfo {
|
||||
encoder.encodeObject(adminInfo, forKey: "ai")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "ai")
|
||||
}
|
||||
if let banInfo = banInfo {
|
||||
encoder.encodeObject(banInfo, forKey: "bi")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "bi")
|
||||
}
|
||||
case let .creator(id):
|
||||
encoder.encodeInt32(ChannelParticipantValue.creator.rawValue, forKey: "r")
|
||||
encoder.encodeInt64(id.toInt64(), forKey: "i")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class CachedChannelParticipants: PostboxCoding, Equatable {
|
||||
public let participants: [ChannelParticipant]
|
||||
|
||||
init(participants: [ChannelParticipant]) {
|
||||
self.participants = participants
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.participants = decoder.decodeObjectArrayWithDecoderForKey("p")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObjectArray(self.participants, forKey: "p")
|
||||
}
|
||||
|
||||
public static func ==(lhs: CachedChannelParticipants, rhs: CachedChannelParticipants) -> Bool {
|
||||
return lhs.participants == rhs.participants
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ChannelParticipant {
|
||||
init(apiParticipant: Api.ChannelParticipant) {
|
||||
switch apiParticipant {
|
||||
case let .channelParticipant(userId, date):
|
||||
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedAt: date, adminInfo: nil, banInfo: nil)
|
||||
case let .channelParticipantCreator(userId):
|
||||
self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
|
||||
case let .channelParticipantBanned(flags, userId, restrictedBy, date, bannedRights):
|
||||
let hasLeft = (flags & (1 << 0)) != 0
|
||||
let banInfo = ChannelParticipantBannedInfo(rights: TelegramChatBannedRights(apiBannedRights: bannedRights), restrictedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: restrictedBy), timestamp: date, isMember: !hasLeft)
|
||||
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedAt: date, adminInfo: nil, banInfo: banInfo)
|
||||
case let .channelParticipantAdmin(flags, userId, _, promotedBy, date, adminRights):
|
||||
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedAt: date, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: promotedBy), canBeEditedByAccountPeer: (flags & (1 << 0)) != 0), banInfo: nil)
|
||||
case let .channelParticipantSelf(userId, _, date):
|
||||
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedAt: date, adminInfo: nil, banInfo: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CachedChannelParticipants {
|
||||
convenience init(apiParticipants: [Api.ChannelParticipant]) {
|
||||
self.init(participants: apiParticipants.map(ChannelParticipant.init))
|
||||
}
|
||||
}
|
||||
204
submodules/TelegramCore/TelegramCore/CachedGroupData.swift
Normal file
204
submodules/TelegramCore/TelegramCore/CachedGroupData.swift
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public final class CachedPeerBotInfo: PostboxCoding, Equatable {
|
||||
public let peerId: PeerId
|
||||
public let botInfo: BotInfo
|
||||
|
||||
init(peerId: PeerId, botInfo: BotInfo) {
|
||||
self.peerId = peerId
|
||||
self.botInfo = botInfo
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||
self.botInfo = decoder.decodeObjectForKey("i", decoder: { return BotInfo(decoder: $0) }) as! BotInfo
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.peerId.toInt64(), forKey: "p")
|
||||
encoder.encodeObject(self.botInfo, forKey: "i")
|
||||
}
|
||||
|
||||
public static func ==(lhs: CachedPeerBotInfo, rhs: CachedPeerBotInfo) -> Bool {
|
||||
return lhs.peerId == rhs.peerId && lhs.botInfo == rhs.botInfo
|
||||
}
|
||||
}
|
||||
|
||||
public struct CachedGroupFlags: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init() {
|
||||
self.rawValue = 0
|
||||
}
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let canChangeUsername = CachedGroupFlags(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
public final class CachedGroupData: CachedPeerData {
|
||||
public let participants: CachedGroupParticipants?
|
||||
public let exportedInvitation: ExportedInvitation?
|
||||
public let botInfos: [CachedPeerBotInfo]
|
||||
public let peerStatusSettings: PeerStatusSettings?
|
||||
public let pinnedMessageId: MessageId?
|
||||
public let about: String?
|
||||
public let flags: CachedGroupFlags
|
||||
|
||||
public let peerIds: Set<PeerId>
|
||||
public let messageIds: Set<MessageId>
|
||||
public let associatedHistoryMessageId: MessageId? = nil
|
||||
|
||||
init() {
|
||||
self.participants = nil
|
||||
self.exportedInvitation = nil
|
||||
self.botInfos = []
|
||||
self.peerStatusSettings = nil
|
||||
self.pinnedMessageId = nil
|
||||
self.messageIds = Set()
|
||||
self.peerIds = Set()
|
||||
self.about = nil
|
||||
self.flags = CachedGroupFlags()
|
||||
}
|
||||
|
||||
public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags) {
|
||||
self.participants = participants
|
||||
self.exportedInvitation = exportedInvitation
|
||||
self.botInfos = botInfos
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
self.pinnedMessageId = pinnedMessageId
|
||||
self.about = about
|
||||
self.flags = flags
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
}
|
||||
self.messageIds = messageIds
|
||||
|
||||
var peerIds = Set<PeerId>()
|
||||
if let participants = participants {
|
||||
for participant in participants.participants {
|
||||
peerIds.insert(participant.peerId)
|
||||
}
|
||||
}
|
||||
for botInfo in botInfos {
|
||||
peerIds.insert(botInfo.peerId)
|
||||
}
|
||||
self.peerIds = peerIds
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
let participants = decoder.decodeObjectForKey("p", decoder: { CachedGroupParticipants(decoder: $0) }) as? CachedGroupParticipants
|
||||
self.participants = participants
|
||||
self.exportedInvitation = decoder.decodeObjectForKey("i", decoder: { ExportedInvitation(decoder: $0) }) as? ExportedInvitation
|
||||
self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo]
|
||||
if let value = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(rawValue: value)
|
||||
} else {
|
||||
self.peerStatusSettings = nil
|
||||
}
|
||||
if let pinnedMessagePeerId = decoder.decodeOptionalInt64ForKey("pm.p"), let pinnedMessageNamespace = decoder.decodeOptionalInt32ForKey("pm.n"), let pinnedMessageId = decoder.decodeOptionalInt32ForKey("pm.i") {
|
||||
self.pinnedMessageId = MessageId(peerId: PeerId(pinnedMessagePeerId), namespace: pinnedMessageNamespace, id: pinnedMessageId)
|
||||
} else {
|
||||
self.pinnedMessageId = nil
|
||||
}
|
||||
self.about = decoder.decodeOptionalStringForKey("ab")
|
||||
self.flags = CachedGroupFlags(rawValue: decoder.decodeInt32ForKey("fl", orElse: 0))
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
}
|
||||
self.messageIds = messageIds
|
||||
|
||||
var peerIds = Set<PeerId>()
|
||||
if let participants = participants {
|
||||
for participant in participants.participants {
|
||||
peerIds.insert(participant.peerId)
|
||||
}
|
||||
}
|
||||
for botInfo in self.botInfos {
|
||||
peerIds.insert(botInfo.peerId)
|
||||
}
|
||||
|
||||
self.peerIds = peerIds
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let participants = self.participants {
|
||||
encoder.encodeObject(participants, forKey: "p")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "p")
|
||||
}
|
||||
if let exportedInvitation = self.exportedInvitation {
|
||||
encoder.encodeObject(exportedInvitation, forKey: "i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "i")
|
||||
}
|
||||
encoder.encodeObjectArray(self.botInfos, forKey: "b")
|
||||
if let peerStatusSettings = self.peerStatusSettings {
|
||||
encoder.encodeInt32(peerStatusSettings.rawValue, forKey: "pcs")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pcs")
|
||||
}
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
encoder.encodeInt64(pinnedMessageId.peerId.toInt64(), forKey: "pm.p")
|
||||
encoder.encodeInt32(pinnedMessageId.namespace, forKey: "pm.n")
|
||||
encoder.encodeInt32(pinnedMessageId.id, forKey: "pm.i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pm.p")
|
||||
encoder.encodeNil(forKey: "pm.n")
|
||||
encoder.encodeNil(forKey: "pm.i")
|
||||
}
|
||||
if let about = self.about {
|
||||
encoder.encodeString(about, forKey: "ab")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "ab")
|
||||
}
|
||||
encoder.encodeInt32(self.flags.rawValue, forKey: "fl")
|
||||
}
|
||||
|
||||
public func isEqual(to: CachedPeerData) -> Bool {
|
||||
guard let other = to as? CachedGroupData else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self.participants == other.participants && self.exportedInvitation == other.exportedInvitation && self.botInfos == other.botInfos && self.peerStatusSettings == other.peerStatusSettings && self.pinnedMessageId == other.pinnedMessageId && self.about == other.about && self.flags == other.flags
|
||||
}
|
||||
|
||||
func withUpdatedParticipants(_ participants: CachedGroupParticipants?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags)
|
||||
}
|
||||
|
||||
func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags)
|
||||
}
|
||||
|
||||
func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags)
|
||||
}
|
||||
|
||||
func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags)
|
||||
}
|
||||
|
||||
func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags)
|
||||
}
|
||||
|
||||
func withUpdatedAbout(_ about: String?) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags)
|
||||
}
|
||||
|
||||
func withUpdatedFlags(_ flags: CachedGroupFlags) -> CachedGroupData {
|
||||
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public enum GroupParticipant: PostboxCoding, Equatable {
|
||||
case member(id: PeerId, invitedBy: PeerId, invitedAt: Int32)
|
||||
case creator(id: PeerId)
|
||||
case admin(id: PeerId, invitedBy: PeerId, invitedAt: Int32)
|
||||
|
||||
public var peerId: PeerId {
|
||||
switch self {
|
||||
case let .member(id, _, _):
|
||||
return id
|
||||
case let .creator(id):
|
||||
return id
|
||||
case let .admin(id, _, _):
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
||||
case 0:
|
||||
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedBy: PeerId(decoder.decodeInt64ForKey("b", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0))
|
||||
case 1:
|
||||
self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)))
|
||||
case 2:
|
||||
self = .admin(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedBy: PeerId(decoder.decodeInt64ForKey("b", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0))
|
||||
default:
|
||||
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedBy: PeerId(decoder.decodeInt64ForKey("b", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0))
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case let .member(id, invitedBy, invitedAt):
|
||||
encoder.encodeInt32(0, forKey: "v")
|
||||
encoder.encodeInt64(id.toInt64(), forKey: "i")
|
||||
encoder.encodeInt64(invitedBy.toInt64(), forKey: "b")
|
||||
encoder.encodeInt32(invitedAt, forKey: "t")
|
||||
case let .creator(id):
|
||||
encoder.encodeInt32(1, forKey: "v")
|
||||
encoder.encodeInt64(id.toInt64(), forKey: "i")
|
||||
case let .admin(id, invitedBy, invitedAt):
|
||||
encoder.encodeInt32(2, forKey: "v")
|
||||
encoder.encodeInt64(id.toInt64(), forKey: "i")
|
||||
encoder.encodeInt64(invitedBy.toInt64(), forKey: "b")
|
||||
encoder.encodeInt32(invitedAt, forKey: "t")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: GroupParticipant, rhs: GroupParticipant) -> Bool {
|
||||
switch lhs {
|
||||
case let .admin(lhsId, lhIinvitedBy, lhsInvitedAt):
|
||||
if case .admin(lhsId, lhIinvitedBy, lhsInvitedAt) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .creator(lhsId):
|
||||
if case .creator(lhsId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .member(lhsId, lhIinvitedBy, lhsInvitedAt):
|
||||
if case .member(lhsId, lhIinvitedBy, lhsInvitedAt) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var invitedBy: PeerId {
|
||||
switch self {
|
||||
case let .admin(_, invitedBy, _):
|
||||
return invitedBy
|
||||
case let .member(_, invitedBy, _):
|
||||
return invitedBy
|
||||
case let .creator(id):
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class CachedGroupParticipants: PostboxCoding, Equatable {
|
||||
public let participants: [GroupParticipant]
|
||||
let version: Int32
|
||||
|
||||
init(participants: [GroupParticipant], version: Int32) {
|
||||
self.participants = participants
|
||||
self.version = version
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.participants = decoder.decodeObjectArrayWithDecoderForKey("p")
|
||||
self.version = decoder.decodeInt32ForKey("v", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObjectArray(self.participants, forKey: "p")
|
||||
encoder.encodeInt32(self.version, forKey: "v")
|
||||
}
|
||||
|
||||
public static func ==(lhs: CachedGroupParticipants, rhs: CachedGroupParticipants) -> Bool {
|
||||
return lhs.version == rhs.version && lhs.participants == rhs.participants
|
||||
}
|
||||
}
|
||||
|
||||
extension GroupParticipant {
|
||||
init(apiParticipant: Api.ChatParticipant) {
|
||||
switch apiParticipant {
|
||||
case let .chatParticipantCreator(userId):
|
||||
self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
|
||||
case let .chatParticipantAdmin(userId, inviterId, date):
|
||||
self = .admin(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: inviterId), invitedAt: date)
|
||||
case let .chatParticipant(userId, inviterId, date):
|
||||
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: inviterId), invitedAt: date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CachedGroupParticipants {
|
||||
convenience init?(apiParticipants: Api.ChatParticipants) {
|
||||
switch apiParticipants {
|
||||
case let .chatParticipants(_, participants, version):
|
||||
self.init(participants: participants.map { GroupParticipant(apiParticipant: $0) }, version: version)
|
||||
case .chatParticipantsForbidden:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private let cachedSentMediaCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10000, highWaterItemCount: 20000)
|
||||
|
||||
enum CachedSentMediaReferenceKey {
|
||||
case image(hash: Data)
|
||||
case file(hash: Data)
|
||||
|
||||
var key: ValueBoxKey {
|
||||
switch self {
|
||||
case let .image(hash):
|
||||
let result = ValueBoxKey(length: 1 + hash.count)
|
||||
result.setUInt8(0, value: 0)
|
||||
hash.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
memcpy(result.memory.advanced(by: 1), bytes, hash.count)
|
||||
}
|
||||
return result
|
||||
case let .file(hash):
|
||||
let result = ValueBoxKey(length: 1 + hash.count)
|
||||
result.setUInt8(0, value: 1)
|
||||
hash.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
memcpy(result.memory.advanced(by: 1), bytes, hash.count)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cachedSentMediaReference(postbox: Postbox, key: CachedSentMediaReferenceKey) -> Signal<Media?, NoError> {
|
||||
return postbox.transaction { transaction -> Media? in
|
||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSentMediaReferences, key: key.key)) as? Media
|
||||
}
|
||||
}
|
||||
|
||||
func storeCachedSentMediaReference(transaction: Transaction, key: CachedSentMediaReferenceKey, media: Media) {
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSentMediaReferences, key: key.key), entry: media, collectionSpec: cachedSentMediaCollectionSpec)
|
||||
}
|
||||
123
submodules/TelegramCore/TelegramCore/CachedStickerPack.swift
Normal file
123
submodules/TelegramCore/TelegramCore/CachedStickerPack.swift
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
final class CachedStickerPack: PostboxCoding {
|
||||
let info: StickerPackCollectionInfo?
|
||||
let items: [StickerPackItem]
|
||||
let hash: Int32
|
||||
|
||||
init(info: StickerPackCollectionInfo?, items: [StickerPackItem], hash: Int32) {
|
||||
self.info = info
|
||||
self.items = items
|
||||
self.hash = hash
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.info = decoder.decodeObjectForKey("in", decoder: { StickerPackCollectionInfo(decoder: $0) }) as? StickerPackCollectionInfo
|
||||
self.items = decoder.decodeObjectArrayForKey("it").map { $0 as! StickerPackItem }
|
||||
self.hash = decoder.decodeInt32ForKey("h", orElse: 0)
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
if let info = self.info {
|
||||
encoder.encodeObject(info, forKey: "in")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "in")
|
||||
}
|
||||
encoder.encodeObjectArray(self.items, forKey: "it")
|
||||
encoder.encodeInt32(self.hash, forKey: "h")
|
||||
}
|
||||
|
||||
static func cacheKey(_ id: ItemCollectionId) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 4 + 8)
|
||||
key.setInt32(0, value: id.namespace)
|
||||
key.setInt64(4, value: id.id)
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200)
|
||||
|
||||
public enum CachedStickerPackResult {
|
||||
case none
|
||||
case fetching
|
||||
case result(StickerPackCollectionInfo, [ItemCollectionItem], Bool)
|
||||
}
|
||||
|
||||
func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo, items: [ItemCollectionItem]) {
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(info.id)), entry: CachedStickerPack(info: info, items: items.map { $0 as! StickerPackItem }, hash: info.hash), collectionSpec: collectionSpec)
|
||||
}
|
||||
|
||||
public func cachedStickerPack(postbox: Postbox, network: Network, reference: StickerPackReference, forceRemote: Bool) -> Signal<CachedStickerPackResult, NoError> {
|
||||
return postbox.transaction { transaction -> CachedStickerPackResult? in
|
||||
if let (info, items, local) = cachedStickerPack(transaction: transaction, reference: reference) {
|
||||
if local {
|
||||
return .result(info, items, true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} |> mapToSignal { value -> Signal<CachedStickerPackResult, NoError> in
|
||||
if let value = value {
|
||||
return .single(value)
|
||||
} else {
|
||||
return postbox.transaction { transaction -> (CachedStickerPackResult, Bool, Int32?) in
|
||||
var loadRemote = false
|
||||
let namespace = Namespaces.ItemCollection.CloudStickerPacks
|
||||
var previousHash: Int32?
|
||||
if case let .id(id, _) = reference, let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
|
||||
previousHash = cached.hash
|
||||
let current: CachedStickerPackResult = .result(info, cached.items, false)
|
||||
if cached.hash != info.hash {
|
||||
return (current, true, previousHash)
|
||||
} else {
|
||||
return (current, false, previousHash)
|
||||
}
|
||||
} else {
|
||||
return (.fetching, true, nil)
|
||||
}
|
||||
} |> mapToSignal { result, loadRemote, previousHash in
|
||||
if loadRemote || forceRemote {
|
||||
let appliedRemote = updatedRemoteStickerPack(postbox: postbox, network: network, reference: reference)
|
||||
|> mapToSignal { result -> Signal<CachedStickerPackResult, NoError> in
|
||||
if let result = result, result.0.hash == previousHash {
|
||||
return .complete()
|
||||
}
|
||||
return postbox.transaction { transaction -> CachedStickerPackResult in
|
||||
if let result = result {
|
||||
cacheStickerPack(transaction: transaction, info: result.0, items: result.1)
|
||||
|
||||
let currentInfo = transaction.getItemCollectionInfo(collectionId: result.0.id) as? StickerPackCollectionInfo
|
||||
|
||||
return .result(result.0, result.1, currentInfo != nil)
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(result) |> then(appliedRemote)
|
||||
} else {
|
||||
return .single(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cachedStickerPack(transaction: Transaction, reference: StickerPackReference) -> (StickerPackCollectionInfo, [ItemCollectionItem], Bool)? {
|
||||
let namespace = Namespaces.ItemCollection.CloudStickerPacks
|
||||
if case let .id(id, _) = reference, let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
|
||||
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
|
||||
return (currentInfo, items, true)
|
||||
} else {
|
||||
if case let .id(id, _) = reference, let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
|
||||
return (info, cached.items, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
161
submodules/TelegramCore/TelegramCore/CachedUserData.swift
Normal file
161
submodules/TelegramCore/TelegramCore/CachedUserData.swift
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public final class CachedUserData: CachedPeerData {
|
||||
public let about: String?
|
||||
public let botInfo: BotInfo?
|
||||
public let peerStatusSettings: PeerStatusSettings?
|
||||
public let pinnedMessageId: MessageId?
|
||||
public let isBlocked: Bool
|
||||
public let commonGroupCount: Int32
|
||||
public let callsAvailable: Bool
|
||||
public let callsPrivate: Bool
|
||||
public let canPinMessages: Bool
|
||||
|
||||
public let peerIds = Set<PeerId>()
|
||||
public let messageIds: Set<MessageId>
|
||||
public let associatedHistoryMessageId: MessageId? = nil
|
||||
|
||||
init() {
|
||||
self.about = nil
|
||||
self.botInfo = nil
|
||||
self.peerStatusSettings = nil
|
||||
self.pinnedMessageId = nil
|
||||
self.isBlocked = false
|
||||
self.commonGroupCount = 0
|
||||
self.callsAvailable = false
|
||||
self.callsPrivate = false
|
||||
self.canPinMessages = false
|
||||
self.messageIds = Set()
|
||||
}
|
||||
|
||||
init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, callsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool) {
|
||||
self.about = about
|
||||
self.botInfo = botInfo
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
self.pinnedMessageId = pinnedMessageId
|
||||
self.isBlocked = isBlocked
|
||||
self.commonGroupCount = commonGroupCount
|
||||
self.callsAvailable = callsAvailable
|
||||
self.callsPrivate = callsPrivate
|
||||
self.canPinMessages = canPinMessages
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
}
|
||||
self.messageIds = messageIds
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.about = decoder.decodeOptionalStringForKey("a")
|
||||
self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo
|
||||
if let value = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(rawValue: value)
|
||||
} else {
|
||||
self.peerStatusSettings = nil
|
||||
}
|
||||
if let pinnedMessagePeerId = decoder.decodeOptionalInt64ForKey("pm.p"), let pinnedMessageNamespace = decoder.decodeOptionalInt32ForKey("pm.n"), let pinnedMessageId = decoder.decodeOptionalInt32ForKey("pm.i") {
|
||||
self.pinnedMessageId = MessageId(peerId: PeerId(pinnedMessagePeerId), namespace: pinnedMessageNamespace, id: pinnedMessageId)
|
||||
} else {
|
||||
self.pinnedMessageId = nil
|
||||
}
|
||||
self.isBlocked = decoder.decodeInt32ForKey("b", orElse: 0) != 0
|
||||
self.commonGroupCount = decoder.decodeInt32ForKey("cg", orElse: 0)
|
||||
self.callsAvailable = decoder.decodeInt32ForKey("ca", orElse: 0) != 0
|
||||
self.callsPrivate = decoder.decodeInt32ForKey("cp", orElse: 0) != 0
|
||||
self.canPinMessages = decoder.decodeInt32ForKey("cpm", orElse: 0) != 0
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
messageIds.insert(pinnedMessageId)
|
||||
}
|
||||
self.messageIds = messageIds
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let about = self.about {
|
||||
encoder.encodeString(about, forKey: "a")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "a")
|
||||
}
|
||||
if let botInfo = self.botInfo {
|
||||
encoder.encodeObject(botInfo, forKey: "bi")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "bi")
|
||||
}
|
||||
if let peerStatusSettings = self.peerStatusSettings {
|
||||
encoder.encodeInt32(peerStatusSettings.rawValue, forKey: "pcs")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pcs")
|
||||
}
|
||||
if let pinnedMessageId = self.pinnedMessageId {
|
||||
encoder.encodeInt64(pinnedMessageId.peerId.toInt64(), forKey: "pm.p")
|
||||
encoder.encodeInt32(pinnedMessageId.namespace, forKey: "pm.n")
|
||||
encoder.encodeInt32(pinnedMessageId.id, forKey: "pm.i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pm.p")
|
||||
encoder.encodeNil(forKey: "pm.n")
|
||||
encoder.encodeNil(forKey: "pm.i")
|
||||
}
|
||||
encoder.encodeInt32(self.isBlocked ? 1 : 0, forKey: "b")
|
||||
encoder.encodeInt32(self.commonGroupCount, forKey: "cg")
|
||||
encoder.encodeInt32(self.callsAvailable ? 1 : 0, forKey: "ca")
|
||||
encoder.encodeInt32(self.callsPrivate ? 1 : 0, forKey: "cp")
|
||||
encoder.encodeInt32(self.canPinMessages ? 1 : 0, forKey: "cpm")
|
||||
}
|
||||
|
||||
public func isEqual(to: CachedPeerData) -> Bool {
|
||||
guard let other = to as? CachedUserData else {
|
||||
return false
|
||||
}
|
||||
|
||||
if other.pinnedMessageId != self.pinnedMessageId {
|
||||
return false
|
||||
}
|
||||
if other.canPinMessages != self.canPinMessages {
|
||||
return false
|
||||
}
|
||||
|
||||
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.callsAvailable == other.callsAvailable && self.callsPrivate == other.callsPrivate
|
||||
}
|
||||
|
||||
func withUpdatedAbout(_ about: String?) -> CachedUserData {
|
||||
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedCallsAvailable(_ callsAvailable: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages)
|
||||
}
|
||||
|
||||
func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages)
|
||||
}
|
||||
}
|
||||
1013
submodules/TelegramCore/TelegramCore/CallSessionManager.swift
Normal file
1013
submodules/TelegramCore/TelegramCore/CallSessionManager.swift
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,18 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public func canSendMessagesToPeer(_ peer: Peer) -> Bool {
|
||||
if peer is TelegramUser || peer is TelegramGroup {
|
||||
return !peer.isDeleted
|
||||
} else if let peer = peer as? TelegramSecretChat {
|
||||
return peer.embeddedState == .active
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
return peer.hasPermission(.sendMessages)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public struct CancelAccountResetData: Equatable {
|
||||
public let type: SentAuthorizationCodeType
|
||||
public let hash: String
|
||||
public let timeout: Int32?
|
||||
public let nextType: AuthorizationCodeNextType?
|
||||
}
|
||||
|
||||
public enum RequestCancelAccountResetDataError {
|
||||
case limitExceeded
|
||||
case generic
|
||||
}
|
||||
|
||||
public func requestCancelAccountResetData(network: Network, hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
return network.request(Api.functions.account.sendConfirmPhoneCode(flags: 0, hash: hash, currentNumber: nil), automaticFloodWait: false)
|
||||
|> mapError { error -> RequestCancelAccountResetDataError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> map { sentCode -> CancelAccountResetData in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout, _):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
return CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
|
||||
return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false)
|
||||
|> mapError { error -> RequestCancelAccountResetDataError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> map { sentCode -> CancelAccountResetData in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout, _):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
return CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum CancelAccountResetError {
|
||||
case generic
|
||||
case invalidCode
|
||||
case codeExpired
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public func requestCancelAccountReset(network: Network, phoneCodeHash: String, phoneCode: String) -> Signal<Never, CancelAccountResetError> {
|
||||
return network.request(Api.functions.account.confirmPhone(phoneCodeHash: phoneCodeHash, phoneCode: phoneCode))
|
||||
|> mapError { error -> CancelAccountResetError 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
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
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 generic
|
||||
}
|
||||
|
||||
public func requestChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
return account.network.request(Api.functions.account.sendChangePhoneCode(flags: 0, phoneNumber: phoneNumber, currentNumber: nil), 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
|
||||
}
|
||||
}
|
||||
|> map { sentCode -> ChangeAccountPhoneNumberData in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout, _):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
return ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
|
||||
return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), 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
|
||||
}
|
||||
}
|
||||
|> map { sentCode -> ChangeAccountPhoneNumberData in
|
||||
switch sentCode {
|
||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout, _):
|
||||
var parsedNextType: AuthorizationCodeNextType?
|
||||
if let nextType = nextType {
|
||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
||||
}
|
||||
return ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChangeAccountPhoneNumberError {
|
||||
case generic
|
||||
case invalidCode
|
||||
case codeExpired
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public func requestChangeAccountPhoneNumber(account: Account, phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> Signal<Void, ChangeAccountPhoneNumberError> {
|
||||
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
|
||||
let user = TelegramUser(user: result)
|
||||
updatePeers(transaction: transaction, peers: [user], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
} |> mapError { _ -> ChangeAccountPhoneNumberError in return .generic }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public func togglePeerMuted(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
notificationPeerId = associatedPeerId
|
||||
}
|
||||
|
||||
let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings
|
||||
let previousSettings: TelegramPeerNotificationSettings
|
||||
if let currentSettings = currentSettings {
|
||||
previousSettings = currentSettings
|
||||
} else {
|
||||
previousSettings = TelegramPeerNotificationSettings.defaultSettings
|
||||
}
|
||||
|
||||
let updatedSettings: TelegramPeerNotificationSettings
|
||||
switch previousSettings.muteState {
|
||||
case .unmuted, .default:
|
||||
updatedSettings = previousSettings.withUpdatedMuteState(.muted(until: Int32.max))
|
||||
case .muted:
|
||||
updatedSettings = previousSettings.withUpdatedMuteState(.default)
|
||||
}
|
||||
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerMuteSetting(account: Account, peerId: PeerId, muteInterval: Int32?) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: muteInterval)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerMuteSetting(transaction: Transaction, peerId: PeerId, muteInterval: Int32?) {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
notificationPeerId = associatedPeerId
|
||||
}
|
||||
|
||||
let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings
|
||||
let previousSettings: TelegramPeerNotificationSettings
|
||||
if let currentSettings = currentSettings {
|
||||
previousSettings = currentSettings
|
||||
} else {
|
||||
previousSettings = TelegramPeerNotificationSettings.defaultSettings
|
||||
}
|
||||
|
||||
let muteState: PeerMuteState
|
||||
if let muteInterval = muteInterval {
|
||||
if muteInterval == 0 {
|
||||
muteState = .unmuted
|
||||
} else {
|
||||
let absoluteUntil: Int32
|
||||
if muteInterval == Int32.max {
|
||||
absoluteUntil = Int32.max
|
||||
} else {
|
||||
absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval
|
||||
}
|
||||
muteState = .muted(until: absoluteUntil)
|
||||
}
|
||||
} else {
|
||||
muteState = .default
|
||||
}
|
||||
|
||||
let updatedSettings = previousSettings.withUpdatedMuteState(muteState)
|
||||
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: displayPreviews)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerDisplayPreviewsSetting(transaction: Transaction, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
notificationPeerId = associatedPeerId
|
||||
}
|
||||
|
||||
let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings
|
||||
let previousSettings: TelegramPeerNotificationSettings
|
||||
if let currentSettings = currentSettings {
|
||||
previousSettings = currentSettings
|
||||
} else {
|
||||
previousSettings = TelegramPeerNotificationSettings.defaultSettings
|
||||
}
|
||||
|
||||
let updatedSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews)
|
||||
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerNotificationSoundInteractive(account: Account, peerId: PeerId, sound: PeerMessageSound) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: sound)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePeerNotificationSoundInteractive(transaction: Transaction, peerId: PeerId, sound: PeerMessageSound) {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
var notificationPeerId = peerId
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
notificationPeerId = associatedPeerId
|
||||
}
|
||||
|
||||
let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings
|
||||
let previousSettings: TelegramPeerNotificationSettings
|
||||
if let currentSettings = currentSettings {
|
||||
previousSettings = currentSettings
|
||||
} else {
|
||||
previousSettings = TelegramPeerNotificationSettings.defaultSettings
|
||||
}
|
||||
|
||||
let updatedSettings = previousSettings.withUpdatedMessageSound(sound)
|
||||
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public struct ChannelAdminEventLogEntry: Comparable {
|
||||
public let stableId: UInt32
|
||||
public let event: AdminLogEvent
|
||||
public let peers: [PeerId: Peer]
|
||||
|
||||
public static func ==(lhs: ChannelAdminEventLogEntry, rhs: ChannelAdminEventLogEntry) -> Bool {
|
||||
return lhs.event == rhs.event
|
||||
}
|
||||
|
||||
public static func <(lhs: ChannelAdminEventLogEntry, rhs: ChannelAdminEventLogEntry) -> Bool {
|
||||
return lhs.event < rhs.event
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChannelAdminEventLogUpdateType {
|
||||
case initial
|
||||
case generic
|
||||
case load
|
||||
}
|
||||
|
||||
public struct ChannelAdminEventLogFilter: Equatable {
|
||||
public let query: String?
|
||||
public let events: AdminLogEventsFlags
|
||||
public let adminPeerIds: [PeerId]?
|
||||
|
||||
public init(query: String? = nil, events: AdminLogEventsFlags = .all, adminPeerIds: [PeerId]? = nil) {
|
||||
self.query = query
|
||||
self.events = events
|
||||
self.adminPeerIds = adminPeerIds
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChannelAdminEventLogFilter, rhs: ChannelAdminEventLogFilter) -> Bool {
|
||||
if lhs.query != rhs.query {
|
||||
return false
|
||||
}
|
||||
if lhs.events != rhs.events {
|
||||
return false
|
||||
}
|
||||
if let lhsAdminPeerIds = lhs.adminPeerIds, let rhsAdminPeerIds = rhs.adminPeerIds {
|
||||
if lhsAdminPeerIds != rhsAdminPeerIds {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.adminPeerIds != nil) != (rhs.adminPeerIds != nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
if self.query != nil {
|
||||
return false
|
||||
}
|
||||
if self.events != .all {
|
||||
return false
|
||||
}
|
||||
if self.adminPeerIds != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func withQuery(_ query: String?) -> ChannelAdminEventLogFilter {
|
||||
return ChannelAdminEventLogFilter(query: query, events: self.events, adminPeerIds: self.adminPeerIds)
|
||||
}
|
||||
|
||||
public func withEvents(_ events: AdminLogEventsFlags) -> ChannelAdminEventLogFilter {
|
||||
return ChannelAdminEventLogFilter(query: self.query, events: events, adminPeerIds: self.adminPeerIds)
|
||||
}
|
||||
|
||||
public func withAdminPeerIds(_ adminPeerIds: [PeerId]?) -> ChannelAdminEventLogFilter {
|
||||
return ChannelAdminEventLogFilter(query: self.query, events: self.events, adminPeerIds: adminPeerIds)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChannelAdminEventLogContext {
|
||||
private let queue: Queue = Queue.mainQueue()
|
||||
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let peerId: PeerId
|
||||
|
||||
private var filter: ChannelAdminEventLogFilter = ChannelAdminEventLogFilter()
|
||||
|
||||
private var nextStableId: UInt32 = 1
|
||||
private var stableIds: [AdminLogEventId: UInt32] = [:]
|
||||
|
||||
private var entries: ([ChannelAdminEventLogEntry], ChannelAdminEventLogFilter) = ([], ChannelAdminEventLogFilter())
|
||||
private var hasEntries: Bool = false
|
||||
private var hasEarlier: Bool = true
|
||||
private var loadingMoreEarlier: Bool = false
|
||||
|
||||
private var subscribers = Bag<([ChannelAdminEventLogEntry], Bool, ChannelAdminEventLogUpdateType, Bool) -> Void>()
|
||||
|
||||
private let loadMoreDisposable = MetaDisposable()
|
||||
|
||||
public init(postbox: Postbox, network: Network, peerId: PeerId) {
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.peerId = peerId
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.loadMoreDisposable.dispose()
|
||||
}
|
||||
|
||||
public func get() -> Signal<([ChannelAdminEventLogEntry], Bool, ChannelAdminEventLogUpdateType, Bool), NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
if let strongSelf = self {
|
||||
subscriber.putNext((strongSelf.entries.0, strongSelf.hasEarlier, .initial, strongSelf.hasEntries))
|
||||
|
||||
let index = strongSelf.subscribers.add({ entries, hasEarlier, type, hasEntries in
|
||||
subscriber.putNext((entries, hasEarlier, type, hasEntries))
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
queue.async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.subscribers.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return EmptyDisposable
|
||||
}
|
||||
} |> runOn(queue)
|
||||
}
|
||||
|
||||
public func setFilter(_ filter: ChannelAdminEventLogFilter) {
|
||||
if self.filter != filter {
|
||||
self.filter = filter
|
||||
self.loadingMoreEarlier = false
|
||||
self.hasEarlier = false
|
||||
self.hasEntries = false
|
||||
|
||||
for subscriber in self.subscribers.copyItems() {
|
||||
subscriber(self.entries.0, self.hasEarlier, .load, self.hasEntries)
|
||||
}
|
||||
|
||||
self.loadMoreEntries()
|
||||
}
|
||||
}
|
||||
|
||||
public func loadMoreEntries() {
|
||||
assert(self.queue.isCurrent())
|
||||
|
||||
if self.loadingMoreEarlier {
|
||||
return
|
||||
}
|
||||
|
||||
let maxId: AdminLogEventId
|
||||
if self.entries.1 == self.filter, let first = self.entries.0.first {
|
||||
maxId = first.event.id
|
||||
} else {
|
||||
maxId = AdminLogEventId.max
|
||||
}
|
||||
|
||||
self.loadingMoreEarlier = true
|
||||
self.loadMoreDisposable.set((channelAdminLogEvents(postbox: self.postbox, network: self.network, peerId: self.peerId, maxId: maxId, minId: AdminLogEventId.min, limit: 100, query: self.filter.query, filter: self.filter.events, admins: self.filter.adminPeerIds)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
var events = result.events.sorted()
|
||||
if strongSelf.entries.1 == strongSelf.filter {
|
||||
if let first = strongSelf.entries.0.first {
|
||||
var clipIndex = events.count
|
||||
for i in (0 ..< events.count).reversed() {
|
||||
if events[i] >= first.event {
|
||||
clipIndex = i - 1
|
||||
}
|
||||
}
|
||||
if clipIndex < events.count {
|
||||
events.removeSubrange(clipIndex ..< events.count)
|
||||
}
|
||||
}
|
||||
|
||||
var entries: [ChannelAdminEventLogEntry] = events.map { event in
|
||||
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
|
||||
}
|
||||
entries.append(contentsOf: strongSelf.entries.0)
|
||||
strongSelf.entries = (entries, strongSelf.filter)
|
||||
} else {
|
||||
let entries: [ChannelAdminEventLogEntry] = events.map { event in
|
||||
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), event: event, peers: result.peers)
|
||||
}
|
||||
strongSelf.entries = (entries, strongSelf.filter)
|
||||
}
|
||||
|
||||
strongSelf.hasEarlier = !events.isEmpty
|
||||
strongSelf.loadingMoreEarlier = false
|
||||
strongSelf.hasEntries = true
|
||||
|
||||
for subscriber in strongSelf.subscribers.copyItems() {
|
||||
subscriber(strongSelf.entries.0, strongSelf.hasEarlier, .load, strongSelf.hasEntries)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func stableIdForEventId(_ id: AdminLogEventId) -> UInt32 {
|
||||
if let value = self.stableIds[id] {
|
||||
return value
|
||||
} else {
|
||||
let value = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
self.stableIds[id] = value
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
236
submodules/TelegramCore/TelegramCore/ChannelAdminEventLogs.swift
Normal file
236
submodules/TelegramCore/TelegramCore/ChannelAdminEventLogs.swift
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public typealias AdminLogEventId = Int64
|
||||
|
||||
public struct AdminLogEvent: Comparable {
|
||||
public let id: AdminLogEventId
|
||||
public let peerId: PeerId
|
||||
public let date: Int32
|
||||
public let action: AdminLogEventAction
|
||||
|
||||
public static func ==(lhs: AdminLogEvent, rhs: AdminLogEvent) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
public static func <(lhs: AdminLogEvent, rhs: AdminLogEvent) -> Bool {
|
||||
if lhs.date != rhs.date {
|
||||
return lhs.date < rhs.date
|
||||
} else {
|
||||
return lhs.id < rhs.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct AdminLogEventsResult {
|
||||
public let peerId: PeerId
|
||||
public let peers: [PeerId: Peer]
|
||||
public let events: [AdminLogEvent]
|
||||
}
|
||||
|
||||
public enum AdminLogEventAction {
|
||||
case changeTitle(prev: String, new: String)
|
||||
case changeAbout(prev: String, new: String)
|
||||
case changeUsername(prev: String, new: String)
|
||||
case changePhoto(prev: [TelegramMediaImageRepresentation], new: [TelegramMediaImageRepresentation])
|
||||
case toggleInvites(Bool)
|
||||
case toggleSignatures(Bool)
|
||||
case updatePinned(Message?)
|
||||
case editMessage(prev: Message, new: Message)
|
||||
case deleteMessage(Message)
|
||||
case participantJoin
|
||||
case participantLeave
|
||||
case participantInvite(RenderedChannelParticipant)
|
||||
case participantToggleBan(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
|
||||
case participantToggleAdmin(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
|
||||
case changeStickerPack(prev: StickerPackReference?, new: StickerPackReference?)
|
||||
case togglePreHistoryHidden(Bool)
|
||||
case updateDefaultBannedRights(prev: TelegramChatBannedRights, new: TelegramChatBannedRights
|
||||
)
|
||||
case pollStopped(Message)
|
||||
case linkedPeerUpdated(previous: Peer?, updated: Peer?)
|
||||
}
|
||||
|
||||
public enum ChannelAdminLogEventError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public struct AdminLogEventsFlags: OptionSet {
|
||||
public var rawValue: UInt32
|
||||
|
||||
public init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.rawValue = 0
|
||||
}
|
||||
public static let join = AdminLogEventsFlags(rawValue: 1 << 0)
|
||||
public static let leave = AdminLogEventsFlags(rawValue: 1 << 1)
|
||||
public static let invite = AdminLogEventsFlags(rawValue: 1 << 2)
|
||||
public static let ban = AdminLogEventsFlags(rawValue: 1 << 3)
|
||||
public static let unban = AdminLogEventsFlags(rawValue: 1 << 4)
|
||||
public static let kick = AdminLogEventsFlags(rawValue: 1 << 5)
|
||||
public static let unkick = AdminLogEventsFlags(rawValue: 1 << 6)
|
||||
public static let promote = AdminLogEventsFlags(rawValue: 1 << 7)
|
||||
public static let demote = AdminLogEventsFlags(rawValue: 1 << 8)
|
||||
public static let info = AdminLogEventsFlags(rawValue: 1 << 9)
|
||||
public static let settings = AdminLogEventsFlags(rawValue: 1 << 10)
|
||||
public static let pinnedMessages = AdminLogEventsFlags(rawValue: 1 << 11)
|
||||
public static let editMessages = AdminLogEventsFlags(rawValue: 1 << 12)
|
||||
public static let deleteMessages = AdminLogEventsFlags(rawValue: 1 << 13)
|
||||
|
||||
public static var all: AdminLogEventsFlags {
|
||||
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .pinnedMessages, .editMessages, .deleteMessages]
|
||||
}
|
||||
public static var flags: AdminLogEventsFlags {
|
||||
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .pinnedMessages, .editMessages, .deleteMessages]
|
||||
}
|
||||
}
|
||||
|
||||
private func boolFromApiValue(_ value: Api.Bool) -> Bool {
|
||||
switch value {
|
||||
case .boolFalse:
|
||||
return false
|
||||
case .boolTrue:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, maxId: AdminLogEventId, minId: AdminLogEventId, limit: Int32 = 100, query: String? = nil, filter: AdminLogEventsFlags? = nil, admins: [PeerId]? = nil) -> Signal<AdminLogEventsResult, ChannelAdminLogEventError> {
|
||||
return postbox.transaction { transaction -> (Peer?, [Peer]?) in
|
||||
return (transaction.getPeer(peerId), admins?.compactMap { transaction.getPeer($0) })
|
||||
}
|
||||
|> introduceError(ChannelAdminLogEventError.self)
|
||||
|> mapToSignal { (peer, admins) -> Signal<AdminLogEventsResult, ChannelAdminLogEventError> in
|
||||
if let peer = peer, let inputChannel = apiInputChannel(peer) {
|
||||
let inputAdmins = admins?.compactMap { apiInputUser($0) }
|
||||
|
||||
var flags: Int32 = 0
|
||||
var eventsFilter: Api.ChannelAdminLogEventsFilter? = nil
|
||||
if let filter = filter {
|
||||
flags += Int32(1 << 0)
|
||||
eventsFilter = Api.ChannelAdminLogEventsFilter.channelAdminLogEventsFilter(flags: Int32(filter.rawValue))
|
||||
}
|
||||
if let _ = inputAdmins {
|
||||
flags += Int32(1 << 1)
|
||||
}
|
||||
return network.request(Api.functions.channels.getAdminLog(flags: flags, channel: inputChannel, q: query ?? "", eventsFilter: eventsFilter, admins: inputAdmins, maxId: maxId, minId: minId, limit: limit)) |> mapToSignal { result in
|
||||
|
||||
switch result {
|
||||
case let .adminLogResults(apiEvents, apiChats, apiUsers):
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
for apiChat in apiChats {
|
||||
if let peer = parseTelegramGroupOrChannel(chat: apiChat) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
for apiUser in apiUsers {
|
||||
let peer = TelegramUser(user: apiUser)
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
|
||||
var events: [AdminLogEvent] = []
|
||||
|
||||
for event in apiEvents {
|
||||
switch event {
|
||||
case let .channelAdminLogEvent(id, date, userId, apiAction):
|
||||
var action: AdminLogEventAction?
|
||||
switch apiAction {
|
||||
case let .channelAdminLogEventActionChangeTitle(prev, new):
|
||||
action = .changeTitle(prev: prev, new: new)
|
||||
case let .channelAdminLogEventActionChangeAbout(prev, new):
|
||||
action = .changeAbout(prev: prev, new: new)
|
||||
case let .channelAdminLogEventActionChangeUsername(prev, new):
|
||||
action = .changeUsername(prev: prev, new: new)
|
||||
case let .channelAdminLogEventActionChangePhoto(prev, new):
|
||||
action = .changePhoto(prev: telegramMediaImageFromApiPhoto(prev)?.representations ?? [], new: telegramMediaImageFromApiPhoto(new)?.representations ?? [])
|
||||
case let .channelAdminLogEventActionToggleInvites(new):
|
||||
action = .toggleInvites(boolFromApiValue(new))
|
||||
case let .channelAdminLogEventActionToggleSignatures(new):
|
||||
action = .toggleSignatures(boolFromApiValue(new))
|
||||
case let .channelAdminLogEventActionUpdatePinned(new):
|
||||
switch new {
|
||||
case .messageEmpty:
|
||||
action = .updatePinned(nil)
|
||||
default:
|
||||
if let message = StoreMessage(apiMessage: new), let rendered = locallyRenderedMessage(message: message, peers: peers) {
|
||||
action = .updatePinned(rendered)
|
||||
}
|
||||
}
|
||||
|
||||
case let .channelAdminLogEventActionEditMessage(prev, new):
|
||||
if let prev = StoreMessage(apiMessage: prev), let prevRendered = locallyRenderedMessage(message: prev, peers: peers), let new = StoreMessage(apiMessage: new), let newRendered = locallyRenderedMessage(message: new, peers: peers) {
|
||||
action = .editMessage(prev: prevRendered, new: newRendered)
|
||||
}
|
||||
case let .channelAdminLogEventActionDeleteMessage(message):
|
||||
if let message = StoreMessage(apiMessage: message), let rendered = locallyRenderedMessage(message: message, peers: peers) {
|
||||
action = .deleteMessage(rendered)
|
||||
}
|
||||
case .channelAdminLogEventActionParticipantJoin:
|
||||
action = .participantJoin
|
||||
case .channelAdminLogEventActionParticipantLeave:
|
||||
action = .participantLeave
|
||||
case let .channelAdminLogEventActionParticipantInvite(participant):
|
||||
let participant = ChannelParticipant(apiParticipant: participant)
|
||||
|
||||
if let peer = peers[participant.peerId] {
|
||||
action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: peer))
|
||||
}
|
||||
case let .channelAdminLogEventActionParticipantToggleBan(prev, new):
|
||||
let prevParticipant = ChannelParticipant(apiParticipant: prev)
|
||||
let newParticipant = ChannelParticipant(apiParticipant: new)
|
||||
|
||||
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
||||
action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
}
|
||||
case let .channelAdminLogEventActionParticipantToggleAdmin(prev, new):
|
||||
let prevParticipant = ChannelParticipant(apiParticipant: prev)
|
||||
let newParticipant = ChannelParticipant(apiParticipant: new)
|
||||
|
||||
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
|
||||
action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
|
||||
}
|
||||
case let .channelAdminLogEventActionChangeStickerSet(prevStickerset, newStickerset):
|
||||
action = .changeStickerPack(prev: StickerPackReference(apiInputSet: prevStickerset), new: StickerPackReference(apiInputSet: newStickerset))
|
||||
case let .channelAdminLogEventActionTogglePreHistoryHidden(value):
|
||||
action = .togglePreHistoryHidden(value == .boolTrue)
|
||||
case let .channelAdminLogEventActionDefaultBannedRights(prevBannedRights, newBannedRights):
|
||||
action = .updateDefaultBannedRights(prev: TelegramChatBannedRights(apiBannedRights: prevBannedRights), new: TelegramChatBannedRights(apiBannedRights: newBannedRights))
|
||||
case let .channelAdminLogEventActionStopPoll(message):
|
||||
if let message = StoreMessage(apiMessage: message), let rendered = locallyRenderedMessage(message: message, peers: peers) {
|
||||
action = .pollStopped(rendered)
|
||||
}
|
||||
case let .channelAdminLogEventActionChangeLinkedChat(prevValue, newValue):
|
||||
action = .linkedPeerUpdated(previous: prevValue == 0 ? nil : peers[PeerId(namespace: Namespaces.Peer.CloudChannel, id: prevValue)], updated: newValue == 0 ? nil : peers[PeerId(namespace: Namespaces.Peer.CloudChannel, id: newValue)])
|
||||
}
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
if let action = action {
|
||||
events.append(AdminLogEvent(id: id, peerId: peerId, date: date, action: action))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> AdminLogEventsResult in
|
||||
updatePeers(transaction: transaction, peers: peers.map { $0.1 }, update: { return $1 })
|
||||
return AdminLogEventsResult(peerId: peerId, peers: peers, events: events)
|
||||
} |> introduceError(MTRpcError.self)
|
||||
}
|
||||
|
||||
} |> mapError {_ in return .generic}
|
||||
}
|
||||
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
90
submodules/TelegramCore/TelegramCore/ChannelAdmins.swift
Normal file
90
submodules/TelegramCore/TelegramCore/ChannelAdmins.swift
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public func channelAdmins(account: Account, peerId: PeerId) -> Signal<[RenderedChannelParticipant], NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<[RenderedChannelParticipant], NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: 0))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[RenderedChannelParticipant], NoError> in
|
||||
switch result {
|
||||
case let .channelParticipants(count, participants, users):
|
||||
var items: [RenderedChannelParticipant] = []
|
||||
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var presences:[PeerId: PeerPresence] = [:]
|
||||
for user in users {
|
||||
let peer = TelegramUser(user: user)
|
||||
peers[peer.id] = peer
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
presences[peer.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
|
||||
if let peer = peers[participant.peerId] {
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> [RenderedChannelParticipant] in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedAdminCount(count))
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
return items
|
||||
}
|
||||
case .channelParticipantsNotModified:
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func channelAdminIds(postbox: Postbox, network: Network, peerId: PeerId, hash: Int32) -> Signal<[PeerId], NoError> {
|
||||
return postbox.transaction { transaction in
|
||||
if let peer = transaction.getPeer(peerId) as? TelegramChannel, case .group = peer.info, let apiChannel = apiInputChannel(peer) {
|
||||
let api = Api.functions.channels.getParticipants(channel: apiChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: hash)
|
||||
return network.request(api) |> retryRequest |> mapToSignal { result in
|
||||
switch result {
|
||||
case let .channelParticipants(_, participants, users):
|
||||
let users = users.filter({ user in
|
||||
return participants.contains(where: { participant in
|
||||
switch participant {
|
||||
case let .channelParticipantAdmin(_, userId, _, _, _, _):
|
||||
return user.peerId.id == userId
|
||||
case let .channelParticipantCreator(userId):
|
||||
return user.peerId.id == userId
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
return .single(users.map({TelegramUser(user: $0).id}))
|
||||
default:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
} |> switchToLatest
|
||||
}
|
||||
289
submodules/TelegramCore/TelegramCore/ChannelBlacklist.swift
Normal file
289
submodules/TelegramCore/TelegramCore/ChannelBlacklist.swift
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private enum ChannelBlacklistFilter {
|
||||
case restricted
|
||||
case banned
|
||||
}
|
||||
|
||||
private func fetchChannelBlacklist(account: Account, peerId: PeerId, filter: ChannelBlacklistFilter) -> Signal<[RenderedChannelParticipant], NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<[RenderedChannelParticipant], NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
let apiFilter: Api.ChannelParticipantsFilter
|
||||
switch filter {
|
||||
case .restricted:
|
||||
apiFilter = .channelParticipantsBanned(q: "")
|
||||
case .banned:
|
||||
apiFilter = .channelParticipantsKicked(q: "")
|
||||
}
|
||||
return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: apiFilter, offset: 0, limit: 100, hash: 0))
|
||||
|> retryRequest
|
||||
|> map { result -> [RenderedChannelParticipant] in
|
||||
var items: [RenderedChannelParticipant] = []
|
||||
switch result {
|
||||
case let .channelParticipants(_, participants, users):
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var presences:[PeerId: PeerPresence] = [:]
|
||||
for user in users {
|
||||
let peer = TelegramUser(user: user)
|
||||
peers[peer.id] = peer
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
presences[peer.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
|
||||
if let peer = peers[participant.peerId] {
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
|
||||
}
|
||||
|
||||
}
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
return items
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public struct ChannelBlacklist {
|
||||
public let banned: [RenderedChannelParticipant]
|
||||
public let restricted: [RenderedChannelParticipant]
|
||||
|
||||
public init(banned: [RenderedChannelParticipant], restricted: [RenderedChannelParticipant]) {
|
||||
self.banned = banned
|
||||
self.restricted = restricted
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
return banned.isEmpty && restricted.isEmpty
|
||||
}
|
||||
|
||||
public func withRemovedPeerId(_ memberId:PeerId) -> ChannelBlacklist {
|
||||
var updatedRestricted = restricted
|
||||
var updatedBanned = banned
|
||||
|
||||
for i in 0 ..< updatedBanned.count {
|
||||
if updatedBanned[i].peer.id == memberId {
|
||||
updatedBanned.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
for i in 0 ..< updatedRestricted.count {
|
||||
if updatedRestricted[i].peer.id == memberId {
|
||||
updatedRestricted.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
return ChannelBlacklist(banned: updatedBanned, restricted: updatedRestricted)
|
||||
}
|
||||
|
||||
public func withRemovedParticipant(_ participant:RenderedChannelParticipant) -> ChannelBlacklist {
|
||||
let updated = self.withRemovedPeerId(participant.participant.peerId)
|
||||
var updatedRestricted = updated.restricted
|
||||
var updatedBanned = updated.banned
|
||||
|
||||
if case .member(_, _, _, let maybeBanInfo) = participant.participant, let banInfo = maybeBanInfo {
|
||||
if banInfo.rights.flags.contains(.banReadMessages) {
|
||||
updatedBanned.insert(participant, at: 0)
|
||||
} else {
|
||||
if !banInfo.rights.flags.isEmpty {
|
||||
updatedRestricted.insert(participant, at: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ChannelBlacklist(banned: updatedBanned, restricted: updatedRestricted)
|
||||
}
|
||||
}
|
||||
|
||||
public func channelBlacklistParticipants(account: Account, peerId: PeerId) -> Signal<ChannelBlacklist, NoError> {
|
||||
return combineLatest(fetchChannelBlacklist(account: account, peerId: peerId, filter: .restricted), fetchChannelBlacklist(account: account, peerId: peerId, filter: .banned))
|
||||
|> map { restricted, banned in
|
||||
var r: [RenderedChannelParticipant] = []
|
||||
var b: [RenderedChannelParticipant] = []
|
||||
var peerIds = Set<PeerId>()
|
||||
for participant in restricted {
|
||||
if !peerIds.contains(participant.peer.id) {
|
||||
peerIds.insert(participant.peer.id)
|
||||
r.append(participant)
|
||||
}
|
||||
}
|
||||
for participant in banned {
|
||||
if !peerIds.contains(participant.peer.id) {
|
||||
peerIds.insert(participant.peer.id)
|
||||
b.append(participant)
|
||||
}
|
||||
}
|
||||
return ChannelBlacklist(banned: b, restricted: r)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChannelMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, rights: TelegramChatBannedRights?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> {
|
||||
return fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId)
|
||||
|> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in
|
||||
return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer), let _ = transaction.getPeer(account.peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
|
||||
let updatedParticipant: ChannelParticipant
|
||||
if let currentParticipant = currentParticipant, case let .member(_, invitedAt, _, currentBanInfo) = currentParticipant {
|
||||
let banInfo: ChannelParticipantBannedInfo?
|
||||
if let rights = rights, !rights.flags.isEmpty {
|
||||
banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: currentBanInfo?.restrictedBy ?? account.peerId, timestamp: currentBanInfo?.timestamp ?? Int32(Date().timeIntervalSince1970), isMember: currentBanInfo?.isMember ?? true)
|
||||
} else {
|
||||
banInfo = nil
|
||||
}
|
||||
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: invitedAt, adminInfo: nil, banInfo: banInfo)
|
||||
} else {
|
||||
let banInfo: ChannelParticipantBannedInfo?
|
||||
if let rights = rights, !rights.flags.isEmpty {
|
||||
banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: account.peerId, timestamp: Int32(Date().timeIntervalSince1970), isMember: false)
|
||||
} else {
|
||||
banInfo = nil
|
||||
}
|
||||
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: nil, banInfo: banInfo)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.editBanned(channel: inputChannel, userId: inputUser, bannedRights: rights?.apiBannedRights ?? Api.ChatBannedRights.chatBannedRights(flags: 0, untilDate: 0)))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
|
||||
var wasKicked = false
|
||||
var wasBanned = false
|
||||
var wasMember = false
|
||||
var wasAdmin = false
|
||||
if let currentParticipant = currentParticipant {
|
||||
switch currentParticipant {
|
||||
case .creator:
|
||||
break
|
||||
case let .member(_, _, adminInfo, banInfo):
|
||||
if let _ = adminInfo {
|
||||
wasAdmin = true
|
||||
}
|
||||
if let banInfo = banInfo, !banInfo.rights.flags.isEmpty {
|
||||
if banInfo.rights.flags.contains(.banReadMessages) {
|
||||
wasKicked = true
|
||||
} else {
|
||||
wasBanned = true
|
||||
wasMember = true
|
||||
}
|
||||
} else {
|
||||
wasMember = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isKicked = false
|
||||
var isBanned = false
|
||||
if let rights = rights, !rights.flags.isEmpty {
|
||||
if rights.flags.contains(.banReadMessages) {
|
||||
isKicked = true
|
||||
} else {
|
||||
isBanned = true
|
||||
}
|
||||
}
|
||||
|
||||
let isMember = !wasKicked && !isKicked
|
||||
|
||||
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant?, Bool) in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
var updatedData = cachedData
|
||||
if isKicked != wasKicked {
|
||||
if let kickedCount = updatedData.participantsSummary.kickedCount {
|
||||
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedKickedCount(max(0, kickedCount + (isKicked ? 1 : -1))))
|
||||
}
|
||||
}
|
||||
|
||||
if isBanned != wasBanned {
|
||||
if let bannedCount = updatedData.participantsSummary.bannedCount {
|
||||
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedBannedCount(max(0, bannedCount + (isBanned ? 1 : -1))))
|
||||
}
|
||||
}
|
||||
|
||||
if wasAdmin {
|
||||
if let adminCount = updatedData.participantsSummary.adminCount {
|
||||
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedAdminCount(max(0, adminCount - 1)))
|
||||
}
|
||||
}
|
||||
|
||||
if isMember != wasMember {
|
||||
if let memberCount = updatedData.participantsSummary.memberCount {
|
||||
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedMemberCount(max(0, memberCount + (isMember ? 1 : -1))))
|
||||
}
|
||||
}
|
||||
|
||||
return updatedData
|
||||
} else {
|
||||
return cachedData
|
||||
}
|
||||
})
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var presences: [PeerId: PeerPresence] = [:]
|
||||
peers[memberPeer.id] = memberPeer
|
||||
if let presence = transaction.getPeerPresence(peerId: memberPeer.id) {
|
||||
presences[memberPeer.id] = presence
|
||||
}
|
||||
if case let .member(_, _, _, maybeBanInfo) = updatedParticipant, let banInfo = maybeBanInfo {
|
||||
if let peer = transaction.getPeer(banInfo.restrictedBy) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
|
||||
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences), isMember)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
public func updateDefaultChannelMemberBannedRights(account: Account, peerId: PeerId, rights: TelegramChatBannedRights) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let _ = transaction.getPeer(account.peerId) else {
|
||||
return .complete()
|
||||
}
|
||||
return account.network.request(Api.functions.messages.editChatDefaultBannedRights(peer: inputPeer, bannedRights: rights.apiBannedRights))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<Never, NoError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
return
|
||||
}
|
||||
if let peer = peer as? TelegramGroup {
|
||||
updatePeers(transaction: transaction, peers: [peer.updateDefaultBannedRights(rights, version: peer.version)], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
updatePeers(transaction: transaction, peers: [peer.withUpdatedDefaultBannedRights(rights)], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
87
submodules/TelegramCore/TelegramCore/ChannelCreation.swift
Normal file
87
submodules/TelegramCore/TelegramCore/ChannelCreation.swift
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private func createChannel(account: Account, title: String, description: String?, isSupergroup:Bool) -> Signal<PeerId, CreateChannelError> {
|
||||
return account.postbox.transaction { transaction -> Signal<PeerId, CreateChannelError> in
|
||||
return account.network.request(Api.functions.channels.createChannel(flags: isSupergroup ? 1 << 1 : 1 << 0, title: title, about: description ?? ""), automaticFloodWait: false)
|
||||
|> mapError { error -> CreateChannelError in
|
||||
if error.errorDescription == "USER_RESTRICTED" {
|
||||
return .restricted
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<PeerId, CreateChannelError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
if let message = updates.messages.first, let peerId = apiMessagePeerId(message) {
|
||||
return account.postbox.multiplePeersView([peerId])
|
||||
|> filter { view in
|
||||
return view.peers[peerId] != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ in
|
||||
return peerId
|
||||
}
|
||||
|> introduceError(CreateChannelError.self)
|
||||
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> introduceError(CreateChannelError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum CreateChannelError {
|
||||
case generic
|
||||
case restricted
|
||||
}
|
||||
|
||||
public func createChannel(account: Account, title: String, description: String?) -> Signal<PeerId, CreateChannelError> {
|
||||
return createChannel(account: account, title: title, description: description, isSupergroup: false)
|
||||
}
|
||||
|
||||
public func createSupergroup(account: Account, title: String, description: String?) -> Signal<PeerId, CreateChannelError> {
|
||||
return createChannel(account: account, title: title, description: description, isSupergroup: true)
|
||||
}
|
||||
|
||||
public enum DeleteChannelError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func deleteChannel(account: Account, peerId: PeerId) -> Signal<Void, DeleteChannelError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> mapError { _ -> DeleteChannelError in return .generic }
|
||||
|> mapToSignal { inputChannel -> Signal<Void, DeleteChannelError> in
|
||||
if let inputChannel = inputChannel {
|
||||
return account.network.request(Api.functions.channels.deleteChannel(channel: inputChannel))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, DeleteChannelError> in
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, DeleteChannelError> in
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public enum ChannelHistoryAvailabilityError {
|
||||
case generic
|
||||
case hasNotPermissions
|
||||
}
|
||||
|
||||
public func updateChannelHistoryAvailabilitySettingsInteractively(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, historyAvailableForNewMembers: Bool) -> Signal<Void, ChannelHistoryAvailabilityError> {
|
||||
return postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> introduceError(ChannelHistoryAvailabilityError.self)
|
||||
|> mapToSignal { peer in
|
||||
|
||||
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return network.request(Api.functions.channels.togglePreHistoryHidden(channel: inputChannel, enabled: historyAvailableForNewMembers ? .boolFalse : .boolTrue))
|
||||
|> `catch` { error -> Signal<Api.Updates, ChannelHistoryAvailabilityError> in
|
||||
if error.errorDescription == "CHAT_ADMIN_REQUIRED" {
|
||||
return .fail(.hasNotPermissions)
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, ChannelHistoryAvailabilityError> in
|
||||
accountStateManager.addUpdates(updates)
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, currentData in
|
||||
if let currentData = currentData as? CachedChannelData {
|
||||
var flags = currentData.flags
|
||||
if historyAvailableForNewMembers {
|
||||
flags.insert(.preHistoryEnabled)
|
||||
} else {
|
||||
flags.remove(.preHistoryEnabled)
|
||||
}
|
||||
return currentData.withUpdatedFlags(flags)
|
||||
} else {
|
||||
return currentData
|
||||
}
|
||||
})
|
||||
} |> introduceError(ChannelHistoryAvailabilityError.self)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
105
submodules/TelegramCore/TelegramCore/ChannelMembers.swift
Normal file
105
submodules/TelegramCore/TelegramCore/ChannelMembers.swift
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum ChannelMembersCategoryFilter {
|
||||
case all
|
||||
case search(String)
|
||||
}
|
||||
|
||||
public enum ChannelMembersCategory {
|
||||
case recent(ChannelMembersCategoryFilter)
|
||||
case admins
|
||||
case contacts(ChannelMembersCategoryFilter)
|
||||
case bots(ChannelMembersCategoryFilter)
|
||||
case restricted(ChannelMembersCategoryFilter)
|
||||
case banned(ChannelMembersCategoryFilter)
|
||||
}
|
||||
|
||||
public func channelMembers(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, category: ChannelMembersCategory = .recent(.all), offset: Int32 = 0, limit: Int32 = 64, hash: Int32 = 0) -> Signal<[RenderedChannelParticipant]?, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<[RenderedChannelParticipant]?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
let apiFilter: Api.ChannelParticipantsFilter
|
||||
switch category {
|
||||
case let .recent(filter):
|
||||
switch filter {
|
||||
case .all:
|
||||
apiFilter = .channelParticipantsRecent
|
||||
case let .search(query):
|
||||
apiFilter = .channelParticipantsSearch(q: query)
|
||||
}
|
||||
case .admins:
|
||||
apiFilter = .channelParticipantsAdmins
|
||||
case let .contacts(filter):
|
||||
switch filter {
|
||||
case .all:
|
||||
apiFilter = .channelParticipantsContacts(q: "")
|
||||
case let .search(query):
|
||||
apiFilter = .channelParticipantsContacts(q: query)
|
||||
}
|
||||
case .bots:
|
||||
apiFilter = .channelParticipantsBots
|
||||
case let .restricted(filter):
|
||||
switch filter {
|
||||
case .all:
|
||||
apiFilter = .channelParticipantsBanned(q: "")
|
||||
case let .search(query):
|
||||
apiFilter = .channelParticipantsBanned(q: query)
|
||||
}
|
||||
case let .banned(filter):
|
||||
switch filter {
|
||||
case .all:
|
||||
apiFilter = .channelParticipantsKicked(q: "")
|
||||
case let .search(query):
|
||||
apiFilter = .channelParticipantsKicked(q: query)
|
||||
}
|
||||
}
|
||||
return network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: apiFilter, offset: offset, limit: limit, hash: hash))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[RenderedChannelParticipant]?, NoError> in
|
||||
return postbox.transaction { transaction -> [RenderedChannelParticipant]? in
|
||||
var items: [RenderedChannelParticipant] = []
|
||||
switch result {
|
||||
case let .channelParticipants(_, participants, users):
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var presences: [PeerId: PeerPresence] = [:]
|
||||
for user in users {
|
||||
let peer = TelegramUser(user: user)
|
||||
peers[peer.id] = peer
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
presences[peer.id] = presence
|
||||
}
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: Array(peers.values), update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: presences)
|
||||
|
||||
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
|
||||
if let peer = peers[participant.peerId] {
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences))
|
||||
}
|
||||
|
||||
}
|
||||
case .channelParticipantsNotModified:
|
||||
return nil
|
||||
}
|
||||
return items
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public class ChannelMessageStateVersionAttribute: MessageAttribute {
|
||||
public let pts: Int32
|
||||
|
||||
public init(pts: Int32) {
|
||||
self.pts = pts
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.pts = decoder.decodeInt32ForKey("p", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.pts, forKey: "p")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import SwiftSignalKitMac
|
||||
import PostboxMac
|
||||
#else
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public enum ChannelOwnershipTransferError {
|
||||
case generic
|
||||
case twoStepAuthMissing
|
||||
case twoStepAuthTooFresh(Int32)
|
||||
case authSessionTooFresh(Int32)
|
||||
case requestPassword
|
||||
case invalidPassword
|
||||
case adminsTooMuch
|
||||
case userPublicChannelsTooMuch
|
||||
case restricted
|
||||
case userBlocked
|
||||
}
|
||||
|
||||
public func updateChannelOwnership(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, channelId: PeerId, memberId: PeerId, password: String?) -> Signal<Never, ChannelOwnershipTransferError> {
|
||||
return postbox.transaction { transaction -> (channel: Peer?, user: Peer?) in
|
||||
return (channel: transaction.getPeer(channelId), user: transaction.getPeer(memberId))
|
||||
}
|
||||
|> introduceError(ChannelOwnershipTransferError.self)
|
||||
|> mapToSignal { channel, user -> Signal<Never, ChannelOwnershipTransferError> in
|
||||
guard let channel = channel, let user = user else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let apiChannel = apiInputChannel(channel) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let apiUser = apiInputUser(user) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
let checkPassword: Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError>
|
||||
if let password = password, !password.isEmpty {
|
||||
checkPassword = twoStepAuthData(network)
|
||||
|> mapError { _ in ChannelOwnershipTransferError.generic }
|
||||
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
|
||||
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
|
||||
guard let kdfResult = passwordKDF(password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return .single(.inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1)))
|
||||
} else {
|
||||
return .fail(.twoStepAuthMissing)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkPassword = .single(.inputCheckPasswordEmpty)
|
||||
}
|
||||
|
||||
return checkPassword
|
||||
|> mapToSignal { password -> Signal<Never, ChannelOwnershipTransferError> in
|
||||
return network.request(Api.functions.channels.editCreator(channel: apiChannel, userId: apiUser, password: password))
|
||||
|> mapError { error -> ChannelOwnershipTransferError in
|
||||
if error.errorDescription == "PASSWORD_HASH_INVALID" {
|
||||
if case .inputCheckPasswordEmpty = password {
|
||||
return .requestPassword
|
||||
} else {
|
||||
return .invalidPassword
|
||||
}
|
||||
} else if error.errorDescription == "PASSWORD_MISSING" {
|
||||
return .twoStepAuthMissing
|
||||
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {
|
||||
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "PASSWORD_TOO_FRESH_".count)...])
|
||||
if let value = Int32(timeout) {
|
||||
return .twoStepAuthTooFresh(value)
|
||||
}
|
||||
} else if error.errorDescription.hasPrefix("SESSION_TOO_FRESH_") {
|
||||
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "SESSION_TOO_FRESH_".count)...])
|
||||
if let value = Int32(timeout) {
|
||||
return .authSessionTooFresh(value)
|
||||
}
|
||||
} else if error.errorDescription == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH" {
|
||||
return .userPublicChannelsTooMuch
|
||||
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
|
||||
return .adminsTooMuch
|
||||
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
|
||||
return .restricted
|
||||
} else if error.errorDescription == "USER_BLOCKED" {
|
||||
return .userBlocked
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Never, ChannelOwnershipTransferError> in
|
||||
accountStateManager.addUpdates(updates)
|
||||
return.complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public struct RenderedChannelParticipant: Equatable {
|
||||
public let participant: ChannelParticipant
|
||||
public let peer: Peer
|
||||
public let peers: [PeerId: Peer]
|
||||
public let presences: [PeerId: PeerPresence]
|
||||
|
||||
public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
|
||||
self.participant = participant
|
||||
self.peer = peer
|
||||
self.peers = peers
|
||||
self.presences = presences
|
||||
}
|
||||
|
||||
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
|
||||
return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer)
|
||||
}
|
||||
}
|
||||
|
||||
func updateChannelParticipantsSummary(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
let admins = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 0, hash: 0))
|
||||
let members = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsRecent, offset: 0, limit: 0, hash: 0))
|
||||
let banned = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsBanned(q: ""), offset: 0, limit: 0, hash: 0))
|
||||
let kicked = account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsKicked(q: ""), offset: 0, limit: 0, hash: 0))
|
||||
return combineLatest(admins, members, banned, kicked)
|
||||
|> mapToSignal { admins, members, banned, kicked -> Signal<Void, MTRpcError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
let adminCount: Int32
|
||||
switch admins {
|
||||
case let .channelParticipants(count, _, _):
|
||||
adminCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
adminCount = 0
|
||||
}
|
||||
let memberCount: Int32
|
||||
switch members {
|
||||
case let .channelParticipants(count, _, _):
|
||||
memberCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
memberCount = 0
|
||||
}
|
||||
let bannedCount: Int32
|
||||
switch banned {
|
||||
case let .channelParticipants(count, _, _):
|
||||
bannedCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
bannedCount = 0
|
||||
}
|
||||
let kickedCount: Int32
|
||||
switch kicked {
|
||||
case let .channelParticipants(count, _, _):
|
||||
kickedCount = count
|
||||
case .channelParticipantsNotModified:
|
||||
assertionFailure()
|
||||
kickedCount = 0
|
||||
}
|
||||
return current.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: memberCount, adminCount: adminCount, bannedCount: bannedCount, kickedCount: kickedCount))
|
||||
}
|
||||
return current
|
||||
})
|
||||
} |> mapError { _ -> MTRpcError in return MTRpcError(errorCode: 0, errorDescription: "") }
|
||||
}
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
88
submodules/TelegramCore/TelegramCore/ChannelState.swift
Normal file
88
submodules/TelegramCore/TelegramCore/ChannelState.swift
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
final class ChannelState: PeerChatState, Equatable, CustomStringConvertible {
|
||||
let pts: Int32
|
||||
let invalidatedPts: Int32?
|
||||
|
||||
init(pts: Int32, invalidatedPts: Int32?) {
|
||||
self.pts = pts
|
||||
self.invalidatedPts = invalidatedPts
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.pts = decoder.decodeInt32ForKey("pts", orElse: 0)
|
||||
self.invalidatedPts = decoder.decodeOptionalInt32ForKey("ipts")
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.pts, forKey: "pts")
|
||||
if let invalidatedPts = self.invalidatedPts {
|
||||
encoder.encodeInt32(invalidatedPts, forKey: "ipts")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "ipts")
|
||||
}
|
||||
}
|
||||
|
||||
func withUpdatedPts(_ pts: Int32) -> ChannelState {
|
||||
return ChannelState(pts: pts, invalidatedPts: self.invalidatedPts)
|
||||
}
|
||||
|
||||
func withUpdatedInvalidatedPts(_ invalidatedPts: Int32?) -> ChannelState {
|
||||
return ChannelState(pts: self.pts, invalidatedPts: invalidatedPts)
|
||||
}
|
||||
|
||||
func equals(_ other: PeerChatState) -> Bool {
|
||||
if let other = other as? ChannelState, other == self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return "(pts: \(self.pts))"
|
||||
}
|
||||
}
|
||||
|
||||
func ==(lhs: ChannelState, rhs: ChannelState) -> Bool {
|
||||
return lhs.pts == rhs.pts && lhs.invalidatedPts == rhs.invalidatedPts
|
||||
}
|
||||
|
||||
struct ChannelUpdate {
|
||||
let update: Api.Update
|
||||
let ptsRange: (Int32, Int32)?
|
||||
}
|
||||
|
||||
func channelUpdatesByPeerId(updates: [ChannelUpdate]) -> [PeerId: [ChannelUpdate]] {
|
||||
var grouped: [PeerId: [ChannelUpdate]] = [:]
|
||||
|
||||
for update in updates {
|
||||
var peerId: PeerId?
|
||||
switch update.update {
|
||||
case let .updateNewChannelMessage(message, _, _):
|
||||
peerId = apiMessagePeerId(message)
|
||||
case let .updateDeleteChannelMessages(channelId, _, _, _):
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
case let .updateEditChannelMessage(message, _, _):
|
||||
peerId = apiMessagePeerId(message)
|
||||
case let .updateChannelWebPage(channelId, _, _, _):
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let peerId = peerId {
|
||||
if grouped[peerId] == nil {
|
||||
grouped[peerId] = [update]
|
||||
} else {
|
||||
grouped[peerId]!.append(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
38
submodules/TelegramCore/TelegramCore/ChannelStats.swift
Normal file
38
submodules/TelegramCore/TelegramCore/ChannelStats.swift
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import SwiftSignalKitMac
|
||||
import PostboxMac
|
||||
#else
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public enum ChannelStatsUrlError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func channelStatsUrl(postbox: Postbox, network: Network, peerId: PeerId, params: String, darkTheme: Bool) -> Signal<String, ChannelStatsUrlError> {
|
||||
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> introduceError(ChannelStatsUrlError.self)
|
||||
|> mapToSignal { inputPeer -> Signal<String, ChannelStatsUrlError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
var flags: Int32 = 0
|
||||
if darkTheme {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
return network.request(Api.functions.messages.getStatsURL(flags: flags, peer: inputPeer, params: params))
|
||||
|> map { result -> String in
|
||||
switch result {
|
||||
case let .statsURL(url):
|
||||
return url
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<String, ChannelStatsUrlError> in
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
462
submodules/TelegramCore/TelegramCore/ChatContextResult.swift
Normal file
462
submodules/TelegramCore/TelegramCore/ChatContextResult.swift
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum ChatContextResultMessage: PostboxCoding, Equatable {
|
||||
case auto(caption: String, entities: TextEntitiesMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?)
|
||||
case text(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, replyMarkup: ReplyMarkupMessageAttribute?)
|
||||
case mapLocation(media: TelegramMediaMap, replyMarkup: ReplyMarkupMessageAttribute?)
|
||||
case contact(media: TelegramMediaContact, replyMarkup: ReplyMarkupMessageAttribute?)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("_v", orElse: 0) {
|
||||
case 0:
|
||||
self = .auto(caption: decoder.decodeStringForKey("c", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
|
||||
case 1:
|
||||
self = .text(text: decoder.decodeStringForKey("t", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, disableUrlPreview: decoder.decodeInt32ForKey("du", orElse: 0) != 0, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
|
||||
case 2:
|
||||
self = .mapLocation(media: decoder.decodeObjectForKey("l") as! TelegramMediaMap, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
|
||||
case 3:
|
||||
self = .contact(media: decoder.decodeObjectForKey("c") as! TelegramMediaContact, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
|
||||
default:
|
||||
self = .auto(caption: "", entities: nil, replyMarkup: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case let .auto(caption, entities, replyMarkup):
|
||||
encoder.encodeInt32(0, forKey: "_v")
|
||||
encoder.encodeString(caption, forKey: "c")
|
||||
if let entities = entities {
|
||||
encoder.encodeObject(entities, forKey: "e")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "e")
|
||||
}
|
||||
if let replyMarkup = replyMarkup {
|
||||
encoder.encodeObject(replyMarkup, forKey: "m")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "m")
|
||||
}
|
||||
case let .text(text, entities, disableUrlPreview, replyMarkup):
|
||||
encoder.encodeInt32(1, forKey: "_v")
|
||||
encoder.encodeString(text, forKey: "t")
|
||||
if let entities = entities {
|
||||
encoder.encodeObject(entities, forKey: "e")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "e")
|
||||
}
|
||||
encoder.encodeInt32(disableUrlPreview ? 1 : 0, forKey: "du")
|
||||
if let replyMarkup = replyMarkup {
|
||||
encoder.encodeObject(replyMarkup, forKey: "m")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "m")
|
||||
}
|
||||
case let .mapLocation(media, replyMarkup):
|
||||
encoder.encodeInt32(2, forKey: "_v")
|
||||
encoder.encodeObject(media, forKey: "l")
|
||||
if let replyMarkup = replyMarkup {
|
||||
encoder.encodeObject(replyMarkup, forKey: "m")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "m")
|
||||
}
|
||||
case let .contact(media, replyMarkup):
|
||||
encoder.encodeInt32(3, forKey: "_v")
|
||||
encoder.encodeObject(media, forKey: "c")
|
||||
if let replyMarkup = replyMarkup {
|
||||
encoder.encodeObject(replyMarkup, forKey: "m")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "m")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatContextResultMessage, rhs: ChatContextResultMessage) -> Bool {
|
||||
switch lhs {
|
||||
case let .auto(lhsCaption, lhsEntities, lhsReplyMarkup):
|
||||
if case let .auto(rhsCaption, rhsEntities, rhsReplyMarkup) = rhs {
|
||||
if lhsCaption != rhsCaption {
|
||||
return false
|
||||
}
|
||||
if lhsEntities != rhsEntities {
|
||||
return false
|
||||
}
|
||||
if lhsReplyMarkup != rhsReplyMarkup {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .text(lhsText, lhsEntities, lhsDisableUrlPreview, lhsReplyMarkup):
|
||||
if case let .text(rhsText, rhsEntities, rhsDisableUrlPreview, rhsReplyMarkup) = rhs {
|
||||
if lhsText != rhsText {
|
||||
return false
|
||||
}
|
||||
if lhsEntities != rhsEntities {
|
||||
return false
|
||||
}
|
||||
if lhsDisableUrlPreview != rhsDisableUrlPreview {
|
||||
return false
|
||||
}
|
||||
if lhsReplyMarkup != rhsReplyMarkup {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .mapLocation(lhsMedia, lhsReplyMarkup):
|
||||
if case let .mapLocation(rhsMedia, rhsReplyMarkup) = rhs {
|
||||
if !lhsMedia.isEqual(to: rhsMedia) {
|
||||
return false
|
||||
}
|
||||
if lhsReplyMarkup != rhsReplyMarkup {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .contact(lhsMedia, lhsReplyMarkup):
|
||||
if case let .contact(rhsMedia, rhsReplyMarkup) = rhs {
|
||||
if !lhsMedia.isEqual(to: rhsMedia) {
|
||||
return false
|
||||
}
|
||||
if lhsReplyMarkup != rhsReplyMarkup {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatContextResult: Equatable {
|
||||
case externalReference(queryId: Int64, id: String, type: String, title: String?, description: String?, url: String?, content: TelegramMediaWebFile?, thumbnail: TelegramMediaWebFile?, message: ChatContextResultMessage)
|
||||
case internalReference(queryId: Int64, id: String, type: String, title: String?, description: String?, image: TelegramMediaImage?, file: TelegramMediaFile?, message: ChatContextResultMessage)
|
||||
|
||||
public var queryId: Int64 {
|
||||
switch self {
|
||||
case let .externalReference(queryId, _, _, _, _, _, _, _, _):
|
||||
return queryId
|
||||
case let .internalReference(queryId, _, _, _, _, _, _, _):
|
||||
return queryId
|
||||
}
|
||||
}
|
||||
|
||||
public var id: String {
|
||||
switch self {
|
||||
case let .externalReference(_, id, _, _, _, _, _, _, _):
|
||||
return id
|
||||
case let .internalReference(_, id, _, _, _, _, _, _):
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
public var type: String {
|
||||
switch self {
|
||||
case let .externalReference(_, _, type, _, _, _, _, _, _):
|
||||
return type
|
||||
case let .internalReference(_, _, type, _, _, _, _, _):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var title: String? {
|
||||
switch self {
|
||||
case let .externalReference(_, _, _, title, _, _, _, _, _):
|
||||
return title
|
||||
case let .internalReference(_, _, _, title, _, _, _, _):
|
||||
return title
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String? {
|
||||
switch self {
|
||||
case let .externalReference(_, _, _, _, description, _, _, _, _):
|
||||
return description
|
||||
case let .internalReference(_, _, _, _, description, _, _, _):
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
public var message: ChatContextResultMessage {
|
||||
switch self {
|
||||
case let .externalReference(_, _, _, _, _, _, _, _, message):
|
||||
return message
|
||||
case let .internalReference(_, _, _, _, _, _, _, message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatContextResult, rhs: ChatContextResult) -> Bool {
|
||||
switch lhs {
|
||||
//id: String, type: String, title: String?, description: String?, url: String?, content: TelegramMediaWebFile?, thumbnail: TelegramMediaWebFile?, message: ChatContextResultMessage
|
||||
case let .externalReference(lhsQueryId, lhsId, lhsType, lhsTitle, lhsDescription, lhsUrl, lhsContent, lhsThumbnail, lhsMessage):
|
||||
if case let .externalReference(rhsQueryId, rhsId, rhsType, rhsTitle, rhsDescription, rhsUrl, rhsContent, rhsThumbnail, rhsMessage) = rhs {
|
||||
if lhsQueryId != rhsQueryId {
|
||||
return false
|
||||
}
|
||||
if lhsId != rhsId {
|
||||
return false
|
||||
}
|
||||
if lhsType != rhsType {
|
||||
return false
|
||||
}
|
||||
if lhsTitle != rhsTitle {
|
||||
return false
|
||||
}
|
||||
if lhsDescription != rhsDescription {
|
||||
return false
|
||||
}
|
||||
if lhsUrl != rhsUrl {
|
||||
return false
|
||||
}
|
||||
if let lhsContent = lhsContent, let rhsContent = rhsContent {
|
||||
if !lhsContent.isEqual(to: rhsContent) {
|
||||
return false
|
||||
}
|
||||
} else if (lhsContent != nil) != (rhsContent != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsThumbnail = lhsThumbnail, let rhsThumbnail = rhsThumbnail {
|
||||
if !lhsThumbnail.isEqual(to: rhsThumbnail) {
|
||||
return false
|
||||
}
|
||||
} else if (lhsThumbnail != nil) != (rhsThumbnail != nil) {
|
||||
return false
|
||||
}
|
||||
if lhsMessage != rhsMessage {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .internalReference(lhsQueryId, lhsId, lhsType, lhsTitle, lhsDescription, lhsImage, lhsFile, lhsMessage):
|
||||
if case let .internalReference(rhsQueryId, rhsId, rhsType, rhsTitle, rhsDescription, rhsImage, rhsFile, rhsMessage) = rhs {
|
||||
if lhsQueryId != rhsQueryId {
|
||||
return false
|
||||
}
|
||||
if lhsId != rhsId {
|
||||
return false
|
||||
}
|
||||
if lhsType != rhsType {
|
||||
return false
|
||||
}
|
||||
if lhsTitle != rhsTitle {
|
||||
return false
|
||||
}
|
||||
if lhsDescription != rhsDescription {
|
||||
return false
|
||||
}
|
||||
if let lhsImage = lhsImage, let rhsImage = rhsImage {
|
||||
if !lhsImage.isEqual(to: rhsImage) {
|
||||
return false
|
||||
}
|
||||
} else if (lhsImage != nil) != (rhsImage != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsFile = lhsFile, let rhsFile = rhsFile {
|
||||
if !lhsFile.isEqual(to: rhsFile) {
|
||||
return false
|
||||
}
|
||||
} else if (lhsFile != nil) != (rhsFile != nil) {
|
||||
return false
|
||||
}
|
||||
if lhsMessage != rhsMessage {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatContextResultCollectionPresentation {
|
||||
case media
|
||||
case list
|
||||
}
|
||||
|
||||
public struct ChatContextResultSwitchPeer: Equatable {
|
||||
public let text: String
|
||||
public let startParam: String
|
||||
|
||||
public static func ==(lhs: ChatContextResultSwitchPeer, rhs: ChatContextResultSwitchPeer) -> Bool {
|
||||
return lhs.text == rhs.text && lhs.startParam == rhs.startParam
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatContextResultCollection: Equatable {
|
||||
public let botId: PeerId
|
||||
public let peerId: PeerId
|
||||
public let query: String
|
||||
public let geoPoint: (Double, Double)?
|
||||
public let queryId: Int64
|
||||
public let nextOffset: String?
|
||||
public let presentation: ChatContextResultCollectionPresentation
|
||||
public let switchPeer: ChatContextResultSwitchPeer?
|
||||
public let results: [ChatContextResult]
|
||||
public let cacheTimeout: Int32
|
||||
|
||||
public init(botId: PeerId, peerId: PeerId, query: String, geoPoint: (Double, Double)?, queryId: Int64, nextOffset: String?, presentation: ChatContextResultCollectionPresentation, switchPeer: ChatContextResultSwitchPeer?, results: [ChatContextResult], cacheTimeout: Int32) {
|
||||
self.botId = botId
|
||||
self.peerId = peerId
|
||||
self.query = query
|
||||
self.geoPoint = geoPoint
|
||||
self.queryId = queryId
|
||||
self.nextOffset = nextOffset
|
||||
self.presentation = presentation
|
||||
self.switchPeer = switchPeer
|
||||
self.results = results
|
||||
self.cacheTimeout = cacheTimeout
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatContextResultCollection, rhs: ChatContextResultCollection) -> Bool {
|
||||
if lhs.botId != rhs.botId {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
if lhs.queryId != rhs.queryId {
|
||||
return false
|
||||
}
|
||||
if lhs.query != rhs.query {
|
||||
return false
|
||||
}
|
||||
if lhs.geoPoint?.0 != rhs.geoPoint?.0 || lhs.geoPoint?.1 != rhs.geoPoint?.1 {
|
||||
return false
|
||||
}
|
||||
if lhs.nextOffset != rhs.nextOffset {
|
||||
return false
|
||||
}
|
||||
if lhs.presentation != rhs.presentation {
|
||||
return false
|
||||
}
|
||||
if lhs.switchPeer != rhs.switchPeer {
|
||||
return false
|
||||
}
|
||||
if lhs.results != rhs.results {
|
||||
return false
|
||||
}
|
||||
if lhs.cacheTimeout != rhs.cacheTimeout {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatContextResultMessage {
|
||||
init(apiMessage: Api.BotInlineMessage) {
|
||||
switch apiMessage {
|
||||
case let .botInlineMessageMediaAuto(_, message, entities, replyMarkup):
|
||||
var parsedEntities: TextEntitiesMessageAttribute?
|
||||
if let entities = entities, !entities.isEmpty {
|
||||
parsedEntities = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
|
||||
}
|
||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||
if let replyMarkup = replyMarkup {
|
||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||
}
|
||||
self = .auto(caption: message, entities: parsedEntities, replyMarkup: parsedReplyMarkup)
|
||||
case let .botInlineMessageText(flags, message, entities, replyMarkup):
|
||||
var parsedEntities: TextEntitiesMessageAttribute?
|
||||
if let entities = entities, !entities.isEmpty {
|
||||
parsedEntities = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
|
||||
}
|
||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||
if let replyMarkup = replyMarkup {
|
||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||
}
|
||||
self = .text(text: message, entities: parsedEntities, disableUrlPreview: (flags & (1 << 0)) != 0, replyMarkup: parsedReplyMarkup)
|
||||
case let .botInlineMessageMediaGeo(_, geo, replyMarkup):
|
||||
let media = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil)
|
||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||
if let replyMarkup = replyMarkup {
|
||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||
}
|
||||
self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup)
|
||||
case let .botInlineMessageMediaVenue(_, geo, title, address, provider, venueId, venueType, replyMarkup):
|
||||
let media = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil)
|
||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||
if let replyMarkup = replyMarkup {
|
||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||
}
|
||||
self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup)
|
||||
case let .botInlineMessageMediaContact(_, phoneNumber, firstName, lastName, vcard, replyMarkup):
|
||||
let media = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: nil, vCardData: vcard.isEmpty ? nil : vcard)
|
||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||
if let replyMarkup = replyMarkup {
|
||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||
}
|
||||
self = .contact(media: media, replyMarkup: parsedReplyMarkup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatContextResult {
|
||||
init(apiResult: Api.BotInlineResult, queryId: Int64) {
|
||||
switch apiResult {
|
||||
case let .botInlineResult(_, id, type, title, description, url, thumb, content, sendMessage):
|
||||
self = .externalReference(queryId: queryId, id: id, type: type, title: title, description: description, url: url, content: content.flatMap(TelegramMediaWebFile.init), thumbnail: thumb.flatMap(TelegramMediaWebFile.init), message: ChatContextResultMessage(apiMessage: sendMessage))
|
||||
case let .botInlineMediaResult(_, id, type, photo, document, title, description, sendMessage):
|
||||
var image: TelegramMediaImage?
|
||||
var file: TelegramMediaFile?
|
||||
if let photo = photo, let parsedImage = telegramMediaImageFromApiPhoto(photo) {
|
||||
image = parsedImage
|
||||
}
|
||||
if let document = document, let parsedFile = telegramMediaFileFromApiDocument(document) {
|
||||
file = parsedFile
|
||||
}
|
||||
self = .internalReference(queryId: queryId, id: id, type: type, title: title, description: description, image: image, file: file, message: ChatContextResultMessage(apiMessage: sendMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatContextResultSwitchPeer {
|
||||
init(apiSwitchPeer: Api.InlineBotSwitchPM) {
|
||||
switch apiSwitchPeer {
|
||||
case let .inlineBotSwitchPM(text, startParam):
|
||||
self.init(text: text, startParam: startParam)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatContextResultCollection {
|
||||
convenience init(apiResults: Api.messages.BotResults, botId: PeerId, peerId: PeerId, query: String, geoPoint: (Double, Double)?) {
|
||||
switch apiResults {
|
||||
case let .botResults(flags, queryId, nextOffset, switchPm, results, cacheTime, _):
|
||||
var switchPeer: ChatContextResultSwitchPeer?
|
||||
if let switchPm = switchPm {
|
||||
switchPeer = ChatContextResultSwitchPeer(apiSwitchPeer: switchPm)
|
||||
}
|
||||
let parsedResults = results.map({ ChatContextResult(apiResult: $0, queryId: queryId) })
|
||||
/*.filter({ result in
|
||||
switch result {
|
||||
case .internalReference:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})*/
|
||||
self.init(botId: botId, peerId: peerId, query: query, geoPoint: geoPoint, queryId: queryId, nextOffset: nextOffset, presentation: (flags & (1 << 0) != 0) ? .media : .list, switchPeer: switchPeer, results: parsedResults, cacheTimeout: cacheTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public struct HistoryPreloadIndex: Comparable {
|
||||
public let index: ChatListIndex?
|
||||
public let hasUnread: Bool
|
||||
public let isMuted: Bool
|
||||
public let isPriority: Bool
|
||||
|
||||
public static func <(lhs: HistoryPreloadIndex, rhs: HistoryPreloadIndex) -> Bool {
|
||||
if lhs.isPriority != rhs.isPriority {
|
||||
if lhs.isPriority {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if lhs.isMuted != rhs.isMuted {
|
||||
if lhs.isMuted {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if lhs.hasUnread != rhs.hasUnread {
|
||||
if lhs.hasUnread {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let lhsIndex = lhs.index, let rhsIndex = rhs.index {
|
||||
return lhsIndex > rhsIndex
|
||||
} else if lhs.index != nil {
|
||||
return true
|
||||
} else if rhs.index != nil {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct HistoryPreloadHole: Hashable, Comparable {
|
||||
let preloadIndex: HistoryPreloadIndex
|
||||
let hole: MessageOfInterestHole
|
||||
|
||||
static func ==(lhs: HistoryPreloadHole, rhs: HistoryPreloadHole) -> Bool {
|
||||
return lhs.preloadIndex == rhs.preloadIndex && lhs.hole == rhs.hole
|
||||
}
|
||||
|
||||
static func <(lhs: HistoryPreloadHole, rhs: HistoryPreloadHole) -> Bool {
|
||||
return lhs.preloadIndex < rhs.preloadIndex
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
return self.preloadIndex.index.hashValue &* 31 &+ self.hole.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
private final class HistoryPreloadEntry: Comparable {
|
||||
var hole: HistoryPreloadHole
|
||||
private var isStarted = false
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
init(hole: HistoryPreloadHole) {
|
||||
self.hole = hole
|
||||
}
|
||||
|
||||
static func ==(lhs: HistoryPreloadEntry, rhs: HistoryPreloadEntry) -> Bool {
|
||||
return lhs.hole == rhs.hole
|
||||
}
|
||||
|
||||
static func <(lhs: HistoryPreloadEntry, rhs: HistoryPreloadEntry) -> Bool {
|
||||
return lhs.hole < rhs.hole
|
||||
}
|
||||
|
||||
func startIfNeeded(postbox: Postbox, accountPeerId: PeerId, download: Signal<Download, NoError>, queue: Queue) {
|
||||
if !self.isStarted {
|
||||
self.isStarted = true
|
||||
|
||||
let hole = self.hole.hole
|
||||
let signal: Signal<Never, NoError> = .complete()
|
||||
|> delay(0.3, queue: queue)
|
||||
|> then(
|
||||
download
|
||||
|> take(1)
|
||||
|> deliverOn(queue)
|
||||
|> mapToSignal { download -> Signal<Never, NoError> in
|
||||
switch hole.hole {
|
||||
case let .peer(peerHole):
|
||||
return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, limit: 60)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.disposable.set(signal.start())
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class HistoryPreloadViewContext {
|
||||
var index: ChatListIndex?
|
||||
var hasUnread: Bool?
|
||||
var isMuted: Bool?
|
||||
var isPriority: Bool
|
||||
let disposable = MetaDisposable()
|
||||
var hole: MessageOfInterestHole?
|
||||
var media: [HolesViewMedia] = []
|
||||
|
||||
var preloadIndex: HistoryPreloadIndex {
|
||||
return HistoryPreloadIndex(index: self.index, hasUnread: self.hasUnread ?? false, isMuted: self.isMuted ?? true, isPriority: self.isPriority)
|
||||
}
|
||||
|
||||
var currentHole: HistoryPreloadHole? {
|
||||
if let hole = self.hole {
|
||||
return HistoryPreloadHole(preloadIndex: self.preloadIndex, hole: hole)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
init(index: ChatListIndex?, hasUnread: Bool?, isMuted: Bool?, isPriority: Bool) {
|
||||
self.index = index
|
||||
self.hasUnread = hasUnread
|
||||
self.isMuted = isMuted
|
||||
self.isPriority = isPriority
|
||||
}
|
||||
|
||||
deinit {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChatHistoryPreloadEntity: Hashable {
|
||||
case peer(PeerId)
|
||||
//case group(PeerGroupId)
|
||||
}
|
||||
|
||||
private struct ChatHistoryPreloadIndex {
|
||||
let index: ChatListIndex
|
||||
let entity: ChatHistoryPreloadEntity
|
||||
}
|
||||
|
||||
public final class ChatHistoryPreloadMediaItem: Comparable {
|
||||
public let preloadIndex: HistoryPreloadIndex
|
||||
public let media: HolesViewMedia
|
||||
|
||||
init(preloadIndex: HistoryPreloadIndex, media: HolesViewMedia) {
|
||||
self.preloadIndex = preloadIndex
|
||||
self.media = media
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatHistoryPreloadMediaItem, rhs: ChatHistoryPreloadMediaItem) -> Bool {
|
||||
if lhs.preloadIndex != rhs.preloadIndex {
|
||||
return false
|
||||
}
|
||||
if lhs.media != rhs.media {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static func <(lhs: ChatHistoryPreloadMediaItem, rhs: ChatHistoryPreloadMediaItem) -> Bool {
|
||||
if lhs.preloadIndex != rhs.preloadIndex {
|
||||
return lhs.preloadIndex > rhs.preloadIndex
|
||||
}
|
||||
return lhs.media.index < rhs.media.index
|
||||
}
|
||||
}
|
||||
|
||||
private final class AdditionalPreloadPeerIdsContext {
|
||||
private let queue: Queue
|
||||
|
||||
private var subscribers: [PeerId: Bag<Void>] = [:]
|
||||
private var additionalPeerIdsValue = ValuePromise<Set<PeerId>>(Set(), ignoreRepeated: true)
|
||||
|
||||
var additionalPeerIds: Signal<Set<PeerId>, NoError> {
|
||||
return self.additionalPeerIdsValue.get()
|
||||
}
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
deinit {
|
||||
assert(self.queue.isCurrent())
|
||||
}
|
||||
|
||||
func add(peerId: PeerId) -> Disposable {
|
||||
let bag: Bag<Void>
|
||||
if let current = self.subscribers[peerId] {
|
||||
bag = current
|
||||
} else {
|
||||
bag = Bag()
|
||||
self.subscribers[peerId] = bag
|
||||
}
|
||||
let wasEmpty = bag.isEmpty
|
||||
let index = bag.add(Void())
|
||||
|
||||
if wasEmpty {
|
||||
self.additionalPeerIdsValue.set(Set(self.subscribers.keys))
|
||||
}
|
||||
let queue = self.queue
|
||||
return ActionDisposable { [weak self, weak bag] in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let current = strongSelf.subscribers[peerId], let bag = bag, current === bag {
|
||||
current.remove(index)
|
||||
if current.isEmpty {
|
||||
strongSelf.subscribers.removeValue(forKey: peerId)
|
||||
strongSelf.additionalPeerIdsValue.set(Set(strongSelf.subscribers.keys))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatHistoryPreloadManager {
|
||||
private let queue = Queue()
|
||||
|
||||
private let postbox: Postbox
|
||||
private let accountPeerId: PeerId
|
||||
private let network: Network
|
||||
private let download = Promise<Download>()
|
||||
|
||||
private var canPreloadHistoryDisposable: Disposable?
|
||||
private var canPreloadHistoryValue = false
|
||||
|
||||
private let automaticChatListDisposable = MetaDisposable()
|
||||
|
||||
private var views: [ChatHistoryPreloadEntity: HistoryPreloadViewContext] = [:]
|
||||
|
||||
private var entries: [HistoryPreloadEntry] = []
|
||||
|
||||
private var orderedMediaValue: [ChatHistoryPreloadMediaItem] = []
|
||||
private let orderedMediaPromise = ValuePromise<[ChatHistoryPreloadMediaItem]>([])
|
||||
var orderedMedia: Signal<[ChatHistoryPreloadMediaItem], NoError> {
|
||||
return self.orderedMediaPromise.get()
|
||||
}
|
||||
|
||||
private let additionalPreloadPeerIdsContext: QueueLocalObject<AdditionalPreloadPeerIdsContext>
|
||||
|
||||
init(postbox: Postbox, network: Network, accountPeerId: PeerId, networkState: Signal<AccountNetworkState, NoError>) {
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.accountPeerId = accountPeerId
|
||||
self.download.set(network.background())
|
||||
|
||||
let queue = Queue.mainQueue()
|
||||
self.additionalPreloadPeerIdsContext = QueueLocalObject(queue: queue, generate: {
|
||||
AdditionalPreloadPeerIdsContext(queue: queue)
|
||||
})
|
||||
|
||||
self.canPreloadHistoryDisposable = (networkState
|
||||
|> map { state -> Bool in
|
||||
switch state {
|
||||
case .online:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] value in
|
||||
guard let strongSelf = self, strongSelf.canPreloadHistoryValue != value else {
|
||||
return
|
||||
}
|
||||
strongSelf.canPreloadHistoryValue = value
|
||||
if value {
|
||||
for i in 0 ..< min(3, strongSelf.entries.count) {
|
||||
strongSelf.entries[i].startIfNeeded(postbox: strongSelf.postbox, accountPeerId: strongSelf.accountPeerId, download: strongSelf.download.get() |> take(1), queue: strongSelf.queue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.canPreloadHistoryDisposable?.dispose()
|
||||
}
|
||||
|
||||
func addAdditionalPeerId(peerId: PeerId) -> Disposable {
|
||||
let disposable = MetaDisposable()
|
||||
self.additionalPreloadPeerIdsContext.with { context in
|
||||
disposable.set(context.add(peerId: peerId))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
|
||||
func start() {
|
||||
let additionalPreloadPeerIdsContext = self.additionalPreloadPeerIdsContext
|
||||
let additionalPeerIds = Signal<Set<PeerId>, NoError> { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
additionalPreloadPeerIdsContext.with { context in
|
||||
disposable.set(context.additionalPeerIds.start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
self.automaticChatListDisposable.set((combineLatest(queue: .mainQueue(), postbox.tailChatListView(groupId: .root, count: 20, summaryComponents: ChatListEntrySummaryComponents()), additionalPeerIds)
|
||||
|> delay(1.0, queue: .mainQueue())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] view, additionalPeerIds in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
#if DEBUG
|
||||
//return;
|
||||
#endif
|
||||
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
|
||||
for entry in view.0.entries {
|
||||
if case let .MessageEntry(index, _, readState, notificationSettings, _, _, _, _) = entry {
|
||||
var hasUnread = false
|
||||
if let readState = readState {
|
||||
hasUnread = readState.count != 0
|
||||
}
|
||||
var isMuted = false
|
||||
if let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings {
|
||||
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||
isMuted = true
|
||||
}
|
||||
}
|
||||
indices.append((ChatHistoryPreloadIndex(index: index, entity: .peer(index.messageIndex.id.peerId)), hasUnread, isMuted))
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.update(indices: indices, additionalPeerIds: additionalPeerIds)
|
||||
}))
|
||||
}
|
||||
|
||||
private func update(indices: [(ChatHistoryPreloadIndex, Bool, Bool)], additionalPeerIds: Set<PeerId>) {
|
||||
self.queue.async {
|
||||
var validEntityIds = Set(indices.map { $0.0.entity })
|
||||
for peerId in additionalPeerIds {
|
||||
validEntityIds.insert(.peer(peerId))
|
||||
}
|
||||
|
||||
var removedEntityIds: [ChatHistoryPreloadEntity] = []
|
||||
for (entityId, view) in self.views {
|
||||
if !validEntityIds.contains(entityId) {
|
||||
removedEntityIds.append(entityId)
|
||||
if let hole = view.currentHole {
|
||||
self.update(from: hole, to: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
for entityId in removedEntityIds {
|
||||
self.views.removeValue(forKey: entityId)
|
||||
}
|
||||
|
||||
var combinedIndices: [(ChatHistoryPreloadIndex, Bool, Bool, Bool)] = []
|
||||
var existingPeerIds = Set<PeerId>()
|
||||
for (index, hasUnread, isMuted) in indices {
|
||||
existingPeerIds.insert(index.index.messageIndex.id.peerId)
|
||||
combinedIndices.append((index, hasUnread, isMuted, additionalPeerIds.contains(index.index.messageIndex.id.peerId)))
|
||||
}
|
||||
for peerId in additionalPeerIds {
|
||||
if !existingPeerIds.contains(peerId) {
|
||||
combinedIndices.append((ChatHistoryPreloadIndex(index: ChatListIndex.absoluteLowerBound, entity: .peer(peerId)), false, true, true))
|
||||
}
|
||||
}
|
||||
|
||||
for (index, hasUnread, isMuted, isPriority) in combinedIndices {
|
||||
if let view = self.views[index.entity] {
|
||||
if view.index != index.index || view.hasUnread != hasUnread || view.isMuted != isMuted {
|
||||
let previousHole = view.currentHole
|
||||
view.index = index.index
|
||||
view.hasUnread = hasUnread
|
||||
view.isMuted = isMuted
|
||||
|
||||
let updatedHole = view.currentHole
|
||||
if previousHole != updatedHole {
|
||||
self.update(from: previousHole, to: updatedHole)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let view = HistoryPreloadViewContext(index: index.index, hasUnread: hasUnread, isMuted: isMuted, isPriority: isPriority)
|
||||
self.views[index.entity] = view
|
||||
let key: PostboxViewKey
|
||||
switch index.entity {
|
||||
case let .peer(peerId):
|
||||
key = .messageOfInterestHole(location: .peer(peerId), namespace: Namespaces.Message.Cloud, count: 60)
|
||||
}
|
||||
view.disposable.set((self.postbox.combinedView(keys: [key])
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] next in
|
||||
if let strongSelf = self, let value = next.views[key] as? MessageOfInterestHolesView {
|
||||
if let view = strongSelf.views[index.entity] {
|
||||
let previousHole = view.currentHole
|
||||
view.hole = value.closestHole
|
||||
|
||||
var mediaUpdated = false
|
||||
if view.media.count != value.closestLaterMedia.count {
|
||||
mediaUpdated = true
|
||||
} else {
|
||||
for i in 0 ..< view.media.count {
|
||||
if view.media[i] != value.closestLaterMedia[i] {
|
||||
mediaUpdated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if mediaUpdated {
|
||||
view.media = value.closestLaterMedia
|
||||
strongSelf.updateMedia()
|
||||
}
|
||||
|
||||
let updatedHole = view.currentHole
|
||||
if previousHole != updatedHole {
|
||||
strongSelf.update(from: previousHole, to: updatedHole)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateMedia() {
|
||||
var result: [ChatHistoryPreloadMediaItem] = []
|
||||
for (_, view) in self.views {
|
||||
for media in view.media {
|
||||
result.append(ChatHistoryPreloadMediaItem(preloadIndex: view.preloadIndex, media: media))
|
||||
}
|
||||
}
|
||||
result.sort()
|
||||
if result != self.orderedMediaValue {
|
||||
self.orderedMediaValue = result
|
||||
self.orderedMediaPromise.set(result)
|
||||
}
|
||||
}
|
||||
|
||||
private func update(from previousHole: HistoryPreloadHole?, to updatedHole: HistoryPreloadHole?) {
|
||||
assert(self.queue.isCurrent())
|
||||
if previousHole == updatedHole {
|
||||
return
|
||||
}
|
||||
|
||||
var skipUpdated = false
|
||||
if let previousHole = previousHole {
|
||||
for i in (0 ..< self.entries.count).reversed() {
|
||||
if self.entries[i].hole == previousHole {
|
||||
if let updatedHole = updatedHole, updatedHole.hole == self.entries[i].hole.hole {
|
||||
self.entries[i].hole = updatedHole
|
||||
skipUpdated = true
|
||||
} else {
|
||||
self.entries.remove(at: i)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedHole = updatedHole, !skipUpdated {
|
||||
var found = false
|
||||
for i in 0 ..< self.entries.count {
|
||||
if self.entries[i].hole == updatedHole {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
self.entries.append(HistoryPreloadEntry(hole: updatedHole))
|
||||
self.entries.sort()
|
||||
}
|
||||
}
|
||||
|
||||
if self.canPreloadHistoryValue {
|
||||
for i in 0 ..< min(3, self.entries.count) {
|
||||
self.entries[i].startIfNeeded(postbox: self.postbox, accountPeerId: self.accountPeerId, download: self.download.get() |> take(1), queue: self.queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
submodules/TelegramCore/TelegramCore/ChatOnlineMembers.swift
Normal file
29
submodules/TelegramCore/TelegramCore/ChatOnlineMembers.swift
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import SwiftSignalKitMac
|
||||
import PostboxMac
|
||||
#else
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public func chatOnlineMembers(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<Int32, NoError> {
|
||||
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<Int32, NoError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .single(0)
|
||||
}
|
||||
return network.request(Api.functions.messages.getOnlines(peer: inputPeer))
|
||||
|> map { value -> Int32 in
|
||||
switch value {
|
||||
case let .chatOnlines(onlines):
|
||||
return onlines
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<Int32, NoError> in
|
||||
return .single(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.applyMarkUnread(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, value: false, interactive: true)
|
||||
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
||||
let updatedState = secretChatCheckLayerNegotiationIfNeeded(transaction: transaction, peerId: peerId, state: state)
|
||||
if state != updatedState {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
submodules/TelegramCore/TelegramCore/ClearCloudDrafts.swift
Normal file
76
submodules/TelegramCore/TelegramCore/ClearCloudDrafts.swift
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public func clearCloudDraftsInteractively(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
return network.request(Api.functions.messages.getAllDrafts())
|
||||
|> retryRequest
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
var peerIds = Set<PeerId>()
|
||||
switch updates {
|
||||
case let .updates(updates, users, chats, _, _):
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
for update in updates {
|
||||
switch update {
|
||||
case let .updateDraftMessage(peer, _):
|
||||
peerIds.insert(peer.peerId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
var signals: [Signal<Void, NoError>] = []
|
||||
for peerId in peerIds {
|
||||
transaction.updatePeerChatInterfaceState(peerId, update: { current in
|
||||
if let current = current as? SynchronizeableChatInterfaceState {
|
||||
return current.withUpdatedSynchronizeableInputState(nil)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
signals.append(network.request(Api.functions.messages.saveDraft(flags: 0, replyToMsgId: nil, peer: inputPeer, message: "", entities: nil))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return combineLatest(signals)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .complete()
|
||||
} |> switchToLatest
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
enum CloudChatRemoveMessagesType: Int32 {
|
||||
case forLocalPeer
|
||||
case forEveryone
|
||||
}
|
||||
|
||||
extension CloudChatRemoveMessagesType {
|
||||
init(_ type: InteractiveMessagesDeletionType) {
|
||||
switch type {
|
||||
case .forLocalPeer:
|
||||
self = .forLocalPeer
|
||||
case .forEveryone:
|
||||
self = .forEveryone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class CloudChatRemoveMessagesOperation: PostboxCoding {
|
||||
let messageIds: [MessageId]
|
||||
let type: CloudChatRemoveMessagesType
|
||||
|
||||
init(messageIds: [MessageId], type: CloudChatRemoveMessagesType) {
|
||||
self.messageIds = messageIds
|
||||
self.type = type
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.messageIds = MessageId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("i")!)
|
||||
self.type = CloudChatRemoveMessagesType(rawValue: decoder.decodeInt32ForKey("t", orElse: 0))!
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
let buffer = WriteBuffer()
|
||||
MessageId.encodeArrayToBuffer(self.messageIds, buffer: buffer)
|
||||
encoder.encodeBytes(buffer, forKey: "i")
|
||||
encoder.encodeInt32(self.type.rawValue, forKey: "t")
|
||||
}
|
||||
}
|
||||
|
||||
final class CloudChatRemoveChatOperation: PostboxCoding {
|
||||
let peerId: PeerId
|
||||
let reportChatSpam: Bool
|
||||
let deleteGloballyIfPossible: Bool
|
||||
let topMessageId: MessageId?
|
||||
|
||||
init(peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool, topMessageId: MessageId?) {
|
||||
self.peerId = peerId
|
||||
self.reportChatSpam = reportChatSpam
|
||||
self.deleteGloballyIfPossible = deleteGloballyIfPossible
|
||||
self.topMessageId = topMessageId
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||
self.reportChatSpam = decoder.decodeInt32ForKey("r", orElse: 0) != 0
|
||||
self.deleteGloballyIfPossible = decoder.decodeInt32ForKey("deleteGloballyIfPossible", orElse: 0) != 0
|
||||
if let messageIdPeerId = decoder.decodeOptionalInt64ForKey("m.p"), let messageIdNamespace = decoder.decodeOptionalInt32ForKey("m.n"), let messageIdId = decoder.decodeOptionalInt32ForKey("m.i") {
|
||||
self.topMessageId = MessageId(peerId: PeerId(messageIdPeerId), namespace: messageIdNamespace, id: messageIdId)
|
||||
} else {
|
||||
self.topMessageId = nil
|
||||
}
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.peerId.toInt64(), forKey: "p")
|
||||
encoder.encodeInt32(self.reportChatSpam ? 1 : 0, forKey: "r")
|
||||
encoder.encodeInt32(self.deleteGloballyIfPossible ? 1 : 0, forKey: "deleteGloballyIfPossible")
|
||||
if let topMessageId = self.topMessageId {
|
||||
encoder.encodeInt64(topMessageId.peerId.toInt64(), forKey: "m.p")
|
||||
encoder.encodeInt32(topMessageId.namespace, forKey: "m.n")
|
||||
encoder.encodeInt32(topMessageId.id, forKey: "m.i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "m.p")
|
||||
encoder.encodeNil(forKey: "m.n")
|
||||
encoder.encodeNil(forKey: "m.i")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class CloudChatClearHistoryOperation: PostboxCoding {
|
||||
let peerId: PeerId
|
||||
let topMessageId: MessageId
|
||||
let type: InteractiveMessagesDeletionType
|
||||
|
||||
init(peerId: PeerId, topMessageId: MessageId, type: InteractiveMessagesDeletionType) {
|
||||
self.peerId = peerId
|
||||
self.topMessageId = topMessageId
|
||||
self.type = type
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||
self.topMessageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0))
|
||||
self.type = InteractiveMessagesDeletionType(rawValue: decoder.decodeInt32ForKey("type", orElse: 0)) ?? .forLocalPeer
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.peerId.toInt64(), forKey: "p")
|
||||
encoder.encodeInt64(self.topMessageId.peerId.toInt64(), forKey: "m.p")
|
||||
encoder.encodeInt32(self.topMessageId.namespace, forKey: "m.n")
|
||||
encoder.encodeInt32(self.topMessageId.id, forKey: "m.i")
|
||||
encoder.encodeInt32(self.type.rawValue, forKey: "type")
|
||||
}
|
||||
}
|
||||
|
||||
func cloudChatAddRemoveMessagesOperation(transaction: Transaction, peerId: PeerId, messageIds: [MessageId], type: CloudChatRemoveMessagesType) {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatRemoveMessagesOperation(messageIds: messageIds, type: type))
|
||||
}
|
||||
|
||||
func cloudChatAddRemoveChatOperation(transaction: Transaction, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool) {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatRemoveChatOperation(peerId: peerId, reportChatSpam: reportChatSpam, deleteGloballyIfPossible: deleteGloballyIfPossible, topMessageId: transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud)))
|
||||
}
|
||||
|
||||
func cloudChatAddClearHistoryOperation(transaction: Transaction, peerId: PeerId, explicitTopMessageId: MessageId?, type: InteractiveMessagesDeletionType) {
|
||||
let topMessageId: MessageId?
|
||||
if let explicitTopMessageId = explicitTopMessageId {
|
||||
topMessageId = explicitTopMessageId
|
||||
} else {
|
||||
topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||
}
|
||||
if let topMessageId = topMessageId {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: topMessageId, type: type))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,899 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
protocol TelegramCloudMediaResource: TelegramMediaResource {
|
||||
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation?
|
||||
}
|
||||
|
||||
protocol TelegramMultipartFetchableResource: TelegramMediaResource {
|
||||
var datacenterId: Int { get }
|
||||
}
|
||||
|
||||
public struct CloudFileMediaResourceId: MediaResourceId {
|
||||
let datacenterId: Int
|
||||
let volumeId: Int64
|
||||
let localId: Int32
|
||||
let secret: Int64
|
||||
|
||||
init(datacenterId: Int, volumeId: Int64, localId: Int32, secret: Int64) {
|
||||
self.datacenterId = datacenterId
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
self.secret = secret
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-cloud-file-\(self.datacenterId)-\(self.volumeId)-\(self.localId)-\(self.secret)"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.secret.hashValue
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? CloudFileMediaResourceId {
|
||||
return self.datacenterId == to.datacenterId && self.volumeId == to.volumeId && self.localId == to.localId && self.secret == to.secret
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol TelegramCloudMediaResourceWithFileReference {
|
||||
var fileReference: Data? { get }
|
||||
}
|
||||
|
||||
public class CloudFileMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
|
||||
public let datacenterId: Int
|
||||
public let volumeId: Int64
|
||||
public let localId: Int32
|
||||
public let secret: Int64
|
||||
public let size: Int?
|
||||
public let fileReference: Data?
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return CloudFileMediaResourceId(datacenterId: self.datacenterId, volumeId: self.volumeId, localId: self.localId, secret: self.secret)
|
||||
}
|
||||
|
||||
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
|
||||
return Api.InputFileLocation.inputFileLocation(volumeId: self.volumeId, localId: self.localId, secret: self.secret, fileReference: Buffer(data: fileReference ?? Data()))
|
||||
}
|
||||
|
||||
public init(datacenterId: Int, volumeId: Int64, localId: Int32, secret: Int64, size: Int?, fileReference: Data?) {
|
||||
self.datacenterId = datacenterId
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
self.secret = secret
|
||||
self.size = size
|
||||
self.fileReference = fileReference
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.volumeId = decoder.decodeInt64ForKey("v", orElse: 0)
|
||||
self.localId = decoder.decodeInt32ForKey("l", orElse: 0)
|
||||
self.secret = decoder.decodeInt64ForKey("s", orElse: 0)
|
||||
if let size = decoder.decodeOptionalInt32ForKey("n") {
|
||||
self.size = Int(size)
|
||||
} else {
|
||||
self.size = nil
|
||||
}
|
||||
self.fileReference = decoder.decodeBytesForKey("fr")?.makeData()
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeInt64(self.volumeId, forKey: "v")
|
||||
encoder.encodeInt32(self.localId, forKey: "l")
|
||||
encoder.encodeInt64(self.secret, forKey: "s")
|
||||
if let size = self.size {
|
||||
encoder.encodeInt32(Int32(size), forKey: "n")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "n")
|
||||
}
|
||||
if let fileReference = self.fileReference {
|
||||
encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "fr")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? CloudFileMediaResource {
|
||||
return self.datacenterId == to.datacenterId && self.volumeId == to.volumeId && self.localId == to.localId && self.secret == to.secret && self.size == to.size && self.fileReference == to.fileReference
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CloudPhotoSizeMediaResourceId: MediaResourceId, Hashable {
|
||||
let datacenterId: Int32
|
||||
let photoId: Int64
|
||||
let sizeSpec: String
|
||||
|
||||
init(datacenterId: Int32, photoId: Int64, sizeSpec: String) {
|
||||
self.datacenterId = datacenterId
|
||||
self.photoId = photoId
|
||||
self.sizeSpec = sizeSpec
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-cloud-photo-size-\(self.datacenterId)-\(self.photoId)-\(self.sizeSpec)"
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? CloudPhotoSizeMediaResourceId {
|
||||
return self.datacenterId == to.datacenterId && self.photoId == to.photoId && self.sizeSpec == to.sizeSpec
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CloudPhotoSizeMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
|
||||
public let datacenterId: Int
|
||||
public let photoId: Int64
|
||||
public let accessHash: Int64
|
||||
public let sizeSpec: String
|
||||
public let volumeId: Int64
|
||||
public let localId: Int32
|
||||
public let fileReference: Data?
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return CloudPhotoSizeMediaResourceId(datacenterId: Int32(self.datacenterId), photoId: self.photoId, sizeSpec: self.sizeSpec)
|
||||
}
|
||||
|
||||
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
|
||||
return Api.InputFileLocation.inputPhotoFileLocation(id: self.photoId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference ?? Data()), thumbSize: self.sizeSpec)
|
||||
}
|
||||
|
||||
public init(datacenterId: Int32, photoId: Int64, accessHash: Int64, sizeSpec: String, volumeId: Int64, localId: Int32, fileReference: Data?) {
|
||||
self.datacenterId = Int(datacenterId)
|
||||
self.photoId = photoId
|
||||
self.accessHash = accessHash
|
||||
self.sizeSpec = sizeSpec
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
self.fileReference = fileReference
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.photoId = decoder.decodeInt64ForKey("i", orElse: 0)
|
||||
self.accessHash = decoder.decodeInt64ForKey("h", orElse: 0)
|
||||
self.sizeSpec = decoder.decodeStringForKey("s", orElse: "")
|
||||
self.volumeId = decoder.decodeInt64ForKey("v", orElse: 0)
|
||||
self.localId = decoder.decodeInt32ForKey("l", orElse: 0)
|
||||
self.fileReference = decoder.decodeBytesForKey("fr")?.makeData()
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeInt64(self.photoId, forKey: "i")
|
||||
encoder.encodeInt64(self.accessHash, forKey: "h")
|
||||
encoder.encodeString(self.sizeSpec, forKey: "s")
|
||||
encoder.encodeInt64(self.volumeId, forKey: "v")
|
||||
encoder.encodeInt32(self.localId, forKey: "l")
|
||||
if let fileReference = self.fileReference {
|
||||
encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "fr")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? CloudPhotoSizeMediaResource {
|
||||
return self.datacenterId == to.datacenterId && self.photoId == to.photoId && self.accessHash == to.accessHash && self.sizeSpec == to.sizeSpec && self.volumeId == to.volumeId && self.localId == to.localId && self.fileReference == to.fileReference
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CloudDocumentSizeMediaResourceId: MediaResourceId, Hashable {
|
||||
let datacenterId: Int32
|
||||
let documentId: Int64
|
||||
let sizeSpec: String
|
||||
|
||||
init(datacenterId: Int32, documentId: Int64, sizeSpec: String) {
|
||||
self.datacenterId = datacenterId
|
||||
self.documentId = documentId
|
||||
self.sizeSpec = sizeSpec
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-cloud-document-size-\(self.datacenterId)-\(self.documentId)-\(self.sizeSpec)"
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? CloudDocumentSizeMediaResourceId {
|
||||
return self.datacenterId == to.datacenterId && self.documentId == to.documentId && self.sizeSpec == to.sizeSpec
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CloudDocumentSizeMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
|
||||
public let datacenterId: Int
|
||||
public let documentId: Int64
|
||||
public let accessHash: Int64
|
||||
public let sizeSpec: String
|
||||
public let volumeId: Int64
|
||||
public let localId: Int32
|
||||
public let fileReference: Data?
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return CloudDocumentSizeMediaResourceId(datacenterId: Int32(self.datacenterId), documentId: self.documentId, sizeSpec: self.sizeSpec)
|
||||
}
|
||||
|
||||
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
|
||||
return Api.InputFileLocation.inputDocumentFileLocation(id: self.documentId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference ?? Data()), thumbSize: self.sizeSpec)
|
||||
}
|
||||
|
||||
public init(datacenterId: Int32, documentId: Int64, accessHash: Int64, sizeSpec: String, volumeId: Int64, localId: Int32, fileReference: Data?) {
|
||||
self.datacenterId = Int(datacenterId)
|
||||
self.documentId = documentId
|
||||
self.accessHash = accessHash
|
||||
self.sizeSpec = sizeSpec
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
self.fileReference = fileReference
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.documentId = decoder.decodeInt64ForKey("i", orElse: 0)
|
||||
self.accessHash = decoder.decodeInt64ForKey("h", orElse: 0)
|
||||
self.sizeSpec = decoder.decodeStringForKey("s", orElse: "")
|
||||
self.volumeId = decoder.decodeInt64ForKey("v", orElse: 0)
|
||||
self.localId = decoder.decodeInt32ForKey("l", orElse: 0)
|
||||
self.fileReference = decoder.decodeBytesForKey("fr")?.makeData()
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeInt64(self.documentId, forKey: "i")
|
||||
encoder.encodeInt64(self.accessHash, forKey: "h")
|
||||
encoder.encodeString(self.sizeSpec, forKey: "s")
|
||||
encoder.encodeInt64(self.volumeId, forKey: "v")
|
||||
encoder.encodeInt32(self.localId, forKey: "l")
|
||||
if let fileReference = self.fileReference {
|
||||
encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "fr")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? CloudDocumentSizeMediaResource {
|
||||
return self.datacenterId == to.datacenterId && self.documentId == to.documentId && self.accessHash == to.accessHash && self.sizeSpec == to.sizeSpec && self.volumeId == to.volumeId && self.localId == to.localId && self.fileReference == to.fileReference
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum CloudPeerPhotoSizeSpec: Int32 {
|
||||
case small
|
||||
case fullSize
|
||||
}
|
||||
|
||||
public struct CloudPeerPhotoSizeMediaResourceId: MediaResourceId, Hashable {
|
||||
let datacenterId: Int32
|
||||
let sizeSpec: CloudPeerPhotoSizeSpec
|
||||
let volumeId: Int64
|
||||
let localId: Int32
|
||||
|
||||
init(datacenterId: Int32, sizeSpec: CloudPeerPhotoSizeSpec, volumeId: Int64, localId: Int32) {
|
||||
self.datacenterId = datacenterId
|
||||
self.sizeSpec = sizeSpec
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-peer-photo-size-\(self.datacenterId)-\(self.sizeSpec.rawValue)-\(self.volumeId)-\(self.localId)"
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? CloudPeerPhotoSizeMediaResourceId {
|
||||
return self.datacenterId == to.datacenterId && self.sizeSpec == to.sizeSpec && self.volumeId == to.volumeId && self.localId == to.localId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CloudPeerPhotoSizeMediaResource: TelegramMultipartFetchableResource {
|
||||
public let datacenterId: Int
|
||||
let sizeSpec: CloudPeerPhotoSizeSpec
|
||||
let volumeId: Int64
|
||||
let localId: Int32
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return CloudPeerPhotoSizeMediaResourceId(datacenterId: Int32(self.datacenterId), sizeSpec: self.sizeSpec, volumeId: self.volumeId, localId: self.localId)
|
||||
}
|
||||
|
||||
func apiInputLocation(peerReference: PeerReference) -> Api.InputFileLocation? {
|
||||
let flags: Int32
|
||||
switch self.sizeSpec {
|
||||
case .small:
|
||||
flags = 0
|
||||
case .fullSize:
|
||||
flags = 1 << 0
|
||||
}
|
||||
return Api.InputFileLocation.inputPeerPhotoFileLocation(flags: flags, peer: peerReference.inputPeer, volumeId: self.volumeId, localId: self.localId)
|
||||
}
|
||||
|
||||
public init(datacenterId: Int32, sizeSpec: CloudPeerPhotoSizeSpec, volumeId: Int64, localId: Int32) {
|
||||
self.datacenterId = Int(datacenterId)
|
||||
self.sizeSpec = sizeSpec
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.sizeSpec = CloudPeerPhotoSizeSpec(rawValue: decoder.decodeInt32ForKey("s", orElse: 0)) ?? .small
|
||||
self.volumeId = decoder.decodeInt64ForKey("v", orElse: 0)
|
||||
self.localId = decoder.decodeInt32ForKey("l", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeInt32(self.sizeSpec.rawValue, forKey: "s")
|
||||
encoder.encodeInt64(self.volumeId, forKey: "v")
|
||||
encoder.encodeInt32(self.localId, forKey: "l")
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? CloudPeerPhotoSizeMediaResource {
|
||||
return self.datacenterId == to.datacenterId && self.sizeSpec == to.sizeSpec && self.volumeId == to.volumeId && self.localId == to.localId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CloudStickerPackThumbnailMediaResourceId: MediaResourceId, Hashable {
|
||||
let datacenterId: Int32
|
||||
let volumeId: Int64
|
||||
let localId: Int32
|
||||
|
||||
init(datacenterId: Int32, volumeId: Int64, localId: Int32) {
|
||||
self.datacenterId = datacenterId
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-stickerpackthumbnail-\(self.datacenterId)-\(self.volumeId)-\(self.localId)"
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? CloudStickerPackThumbnailMediaResourceId {
|
||||
return self.datacenterId == to.datacenterId && self.volumeId == to.volumeId && self.localId == to.localId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CloudStickerPackThumbnailMediaResource: TelegramMultipartFetchableResource {
|
||||
public let datacenterId: Int
|
||||
let volumeId: Int64
|
||||
let localId: Int32
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return CloudStickerPackThumbnailMediaResourceId(datacenterId: Int32(self.datacenterId), volumeId: self.volumeId, localId: self.localId)
|
||||
}
|
||||
|
||||
func apiInputLocation(packReference: StickerPackReference) -> Api.InputFileLocation? {
|
||||
return Api.InputFileLocation.inputStickerSetThumb(stickerset: packReference.apiInputStickerSet, volumeId: self.volumeId, localId: self.localId)
|
||||
}
|
||||
|
||||
public init(datacenterId: Int32, volumeId: Int64, localId: Int32) {
|
||||
self.datacenterId = Int(datacenterId)
|
||||
self.volumeId = volumeId
|
||||
self.localId = localId
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.volumeId = decoder.decodeInt64ForKey("v", orElse: 0)
|
||||
self.localId = decoder.decodeInt32ForKey("l", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeInt64(self.volumeId, forKey: "v")
|
||||
encoder.encodeInt32(self.localId, forKey: "l")
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? CloudPeerPhotoSizeMediaResource {
|
||||
return self.datacenterId == to.datacenterId && self.volumeId == to.volumeId && self.localId == to.localId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CloudDocumentMediaResourceId: MediaResourceId {
|
||||
let datacenterId: Int
|
||||
let fileId: Int64
|
||||
|
||||
init(datacenterId: Int, fileId: Int64) {
|
||||
self.datacenterId = datacenterId
|
||||
self.fileId = fileId
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-cloud-document-\(self.datacenterId)-\(self.fileId)"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.fileId.hashValue
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? CloudDocumentMediaResourceId {
|
||||
return self.datacenterId == to.datacenterId && self.fileId == to.fileId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CloudDocumentMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
|
||||
public let datacenterId: Int
|
||||
public let fileId: Int64
|
||||
public let accessHash: Int64
|
||||
public let size: Int?
|
||||
public let fileReference: Data?
|
||||
public let fileName: String?
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return CloudDocumentMediaResourceId(datacenterId: self.datacenterId, fileId: self.fileId)
|
||||
}
|
||||
|
||||
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
|
||||
return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference ?? Data()), thumbSize: "")
|
||||
}
|
||||
|
||||
public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int?, fileReference: Data?, fileName: String?) {
|
||||
self.datacenterId = datacenterId
|
||||
self.fileId = fileId
|
||||
self.accessHash = accessHash
|
||||
self.size = size
|
||||
self.fileReference = fileReference
|
||||
self.fileName = fileName
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.fileId = decoder.decodeInt64ForKey("f", orElse: 0)
|
||||
self.accessHash = decoder.decodeInt64ForKey("a", orElse: 0)
|
||||
if let size = decoder.decodeOptionalInt32ForKey("n") {
|
||||
self.size = Int(size)
|
||||
} else {
|
||||
self.size = nil
|
||||
}
|
||||
self.fileReference = decoder.decodeBytesForKey("fr")?.makeData()
|
||||
self.fileName = decoder.decodeOptionalStringForKey("fn")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeInt64(self.fileId, forKey: "f")
|
||||
encoder.encodeInt64(self.accessHash, forKey: "a")
|
||||
if let size = self.size {
|
||||
encoder.encodeInt32(Int32(size), forKey: "n")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "n")
|
||||
}
|
||||
if let fileReference = self.fileReference {
|
||||
encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "fr")
|
||||
}
|
||||
if let fileName = self.fileName {
|
||||
encoder.encodeString(fileName, forKey: "fn")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "fn")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? CloudDocumentMediaResource {
|
||||
return self.datacenterId == to.datacenterId && self.fileId == to.fileId && self.accessHash == to.accessHash && self.size == to.size && self.fileReference == to.fileReference
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalFileMediaResourceId: MediaResourceId {
|
||||
public let fileId: Int64
|
||||
|
||||
public var uniqueId: String {
|
||||
return "telegram-local-file-\(self.fileId)"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.fileId.hashValue
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? LocalFileMediaResourceId {
|
||||
return self.fileId == to.fileId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalFileMediaResource: TelegramMediaResource {
|
||||
public let fileId: Int64
|
||||
public let size: Int?
|
||||
|
||||
public init(fileId: Int64, size: Int? = nil) {
|
||||
self.fileId = fileId
|
||||
self.size = size
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.fileId = decoder.decodeInt64ForKey("f", orElse: 0)
|
||||
if let size = decoder.decodeOptionalInt32ForKey("s") {
|
||||
self.size = Int(size)
|
||||
} else {
|
||||
self.size = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.fileId, forKey: "f")
|
||||
if let size = self.size {
|
||||
encoder.encodeInt32(Int32(size), forKey: "s")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "s")
|
||||
}
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return LocalFileMediaResourceId(fileId: self.fileId)
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? LocalFileMediaResource {
|
||||
return self.fileId == to.fileId && self.size == to.size
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LocalFileReferenceMediaResourceId: MediaResourceId {
|
||||
public let randomId: Int64
|
||||
|
||||
public var uniqueId: String {
|
||||
return "local-file-\(self.randomId)"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.randomId.hashValue
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? LocalFileReferenceMediaResourceId {
|
||||
return self.randomId == to.randomId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalFileReferenceMediaResource: TelegramMediaResource {
|
||||
public let localFilePath: String
|
||||
let randomId: Int64
|
||||
let isUniquelyReferencedTemporaryFile: Bool
|
||||
public let size: Int32?
|
||||
|
||||
public init(localFilePath: String, randomId: Int64, isUniquelyReferencedTemporaryFile: Bool = false, size: Int32? = nil) {
|
||||
self.localFilePath = localFilePath
|
||||
self.randomId = randomId
|
||||
self.isUniquelyReferencedTemporaryFile = isUniquelyReferencedTemporaryFile
|
||||
self.size = size
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.localFilePath = decoder.decodeStringForKey("p", orElse: "")
|
||||
self.randomId = decoder.decodeInt64ForKey("r", orElse: 0)
|
||||
self.isUniquelyReferencedTemporaryFile = decoder.decodeInt32ForKey("t", orElse: 0) != 0
|
||||
self.size = decoder.decodeOptionalInt32ForKey("s")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.localFilePath, forKey: "p")
|
||||
encoder.encodeInt64(self.randomId, forKey: "r")
|
||||
encoder.encodeInt32(self.isUniquelyReferencedTemporaryFile ? 1 : 0, forKey: "t")
|
||||
if let size = self.size {
|
||||
encoder.encodeInt32(size, forKey: "s")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "s")
|
||||
}
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return LocalFileReferenceMediaResourceId(randomId: self.randomId)
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? LocalFileReferenceMediaResource {
|
||||
return self.localFilePath == to.localFilePath && self.randomId == to.randomId && self.size == to.size && self.isUniquelyReferencedTemporaryFile == to.isUniquelyReferencedTemporaryFile
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct HttpReferenceMediaResourceId: MediaResourceId {
|
||||
public let url: String
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? HttpReferenceMediaResourceId {
|
||||
return self.url == to.url
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.url.hashValue
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "http-\(persistentHash32(self.url))"
|
||||
}
|
||||
}
|
||||
|
||||
public final class HttpReferenceMediaResource: TelegramMediaResource {
|
||||
public let url: String
|
||||
public let size: Int?
|
||||
|
||||
public init(url: String, size: Int?) {
|
||||
self.url = url
|
||||
self.size = size
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.url = decoder.decodeStringForKey("u", orElse: "")
|
||||
if let size = decoder.decodeOptionalInt32ForKey("s") {
|
||||
self.size = Int(size)
|
||||
} else {
|
||||
self.size = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.url, forKey: "u")
|
||||
if let size = self.size {
|
||||
encoder.encodeInt32(Int32(size), forKey: "s")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "s")
|
||||
}
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return HttpReferenceMediaResourceId(url: self.url)
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? HttpReferenceMediaResource {
|
||||
return to.url == self.url
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct WebFileReferenceMediaResourceId: MediaResourceId {
|
||||
public let url: String
|
||||
public let accessHash: Int64
|
||||
public let size: Int32
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? WebFileReferenceMediaResourceId {
|
||||
return self.url == to.url && size == to.size && accessHash == to.accessHash
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.url.hashValue
|
||||
}
|
||||
|
||||
public var uniqueId: String {
|
||||
return "proxy-\(persistentHash32(self.url))-\(size)-\(accessHash)"
|
||||
}
|
||||
}
|
||||
|
||||
public final class WebFileReferenceMediaResource: TelegramMediaResource {
|
||||
public let url: String
|
||||
public let size: Int32
|
||||
public let accessHash: Int64
|
||||
|
||||
public init(url: String, size: Int32, accessHash: Int64) {
|
||||
self.url = url
|
||||
self.size = size
|
||||
self.accessHash = accessHash
|
||||
}
|
||||
|
||||
var apiInputLocation: Api.InputWebFileLocation {
|
||||
return Api.InputWebFileLocation.inputWebFileLocation(url: url, accessHash: accessHash)
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.url = decoder.decodeStringForKey("u", orElse: "")
|
||||
self.size = decoder.decodeInt32ForKey("s", orElse: 0)
|
||||
self.accessHash = decoder.decodeInt64ForKey("h", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.url, forKey: "u")
|
||||
encoder.encodeInt32(self.size, forKey: "s")
|
||||
encoder.encodeInt64(self.accessHash, forKey: "h")
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return WebFileReferenceMediaResourceId(url: self.url, accessHash: accessHash, size: self.size)
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? WebFileReferenceMediaResource {
|
||||
return to.url == self.url && to.size == self.size && to.accessHash == self.accessHash
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct SecretFileMediaResourceId: MediaResourceId {
|
||||
public let fileId: Int64
|
||||
public let datacenterId: Int32
|
||||
|
||||
public var uniqueId: String {
|
||||
return "secret-file-\(self.fileId)-\(self.datacenterId)"
|
||||
}
|
||||
|
||||
public init(fileId: Int64, datacenterId: Int32) {
|
||||
self.fileId = fileId
|
||||
self.datacenterId = datacenterId
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.fileId.hashValue
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if let to = to as? SecretFileMediaResourceId {
|
||||
return self.fileId == to.fileId && self.datacenterId == to.datacenterId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct SecretFileMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource {
|
||||
public let fileId: Int64
|
||||
public let accessHash: Int64
|
||||
public var size: Int? {
|
||||
return Int(self.decryptedSize)
|
||||
}
|
||||
public let containerSize: Int32
|
||||
public let decryptedSize: Int32
|
||||
public let datacenterId: Int
|
||||
public let key: SecretFileEncryptionKey
|
||||
|
||||
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
|
||||
return .inputEncryptedFileLocation(id: self.fileId, accessHash: self.accessHash)
|
||||
}
|
||||
|
||||
public init(fileId: Int64, accessHash: Int64, containerSize: Int32, decryptedSize: Int32, datacenterId: Int, key: SecretFileEncryptionKey) {
|
||||
self.fileId = fileId
|
||||
self.accessHash = accessHash
|
||||
self.containerSize = containerSize
|
||||
self.decryptedSize = decryptedSize
|
||||
self.datacenterId = datacenterId
|
||||
self.key = key
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.fileId = decoder.decodeInt64ForKey("i", orElse: 0)
|
||||
self.accessHash = decoder.decodeInt64ForKey("a", orElse: 0)
|
||||
self.containerSize = decoder.decodeInt32ForKey("s", orElse: 0)
|
||||
self.decryptedSize = decoder.decodeInt32ForKey("ds", orElse: 0)
|
||||
self.datacenterId = Int(decoder.decodeInt32ForKey("d", orElse: 0))
|
||||
self.key = decoder.decodeObjectForKey("k", decoder: { SecretFileEncryptionKey(decoder: $0) }) as! SecretFileEncryptionKey
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.fileId, forKey: "i")
|
||||
encoder.encodeInt64(self.accessHash, forKey: "a")
|
||||
encoder.encodeInt32(self.containerSize, forKey: "s")
|
||||
encoder.encodeInt32(self.decryptedSize, forKey: "ds")
|
||||
encoder.encodeInt32(Int32(self.datacenterId), forKey: "d")
|
||||
encoder.encodeObject(self.key, forKey: "k")
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return SecretFileMediaResourceId(fileId: self.fileId, datacenterId: Int32(self.datacenterId))
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? SecretFileMediaResource {
|
||||
if self.fileId != to.fileId {
|
||||
return false
|
||||
}
|
||||
if self.accessHash != to.accessHash {
|
||||
return false
|
||||
}
|
||||
if self.containerSize != to.containerSize {
|
||||
return false
|
||||
}
|
||||
if self.decryptedSize != to.decryptedSize {
|
||||
return false
|
||||
}
|
||||
if self.datacenterId != to.datacenterId {
|
||||
return false
|
||||
}
|
||||
if self.key != to.key {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct EmptyMediaResourceId: MediaResourceId {
|
||||
public var uniqueId: String {
|
||||
return "empty-resource"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
return to is EmptyMediaResourceId
|
||||
}
|
||||
}
|
||||
|
||||
public final class EmptyMediaResource: TelegramMediaResource {
|
||||
public init() {
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return EmptyMediaResourceId()
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
return to is EmptyMediaResource
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
|
||||
public enum CloudMediaResourceLocation: Equatable {
|
||||
case photo(id: Int64, accessHash: Int64, fileReference: Data, thumbSize: String)
|
||||
case file(id: Int64, accessHash: Int64, fileReference: Data, thumbSize: String)
|
||||
case peerPhoto(peer: PeerReference, fullSize: Bool, volumeId: Int64, localId: Int64)
|
||||
case stickerPackThumbnail(packReference: StickerPackReference, volumeId: Int64, localId: Int64)
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public enum PeerCacheUsageCategory: Int32 {
|
||||
case image = 0
|
||||
case video
|
||||
case audio
|
||||
case file
|
||||
}
|
||||
|
||||
public struct CacheUsageStats {
|
||||
public let media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]]
|
||||
public let mediaResourceIds: [MediaId: [MediaResourceId]]
|
||||
public let peers: [PeerId: Peer]
|
||||
public let otherSize: Int64
|
||||
public let otherPaths: [String]
|
||||
public let cacheSize: Int64
|
||||
public let tempPaths: [String]
|
||||
public let tempSize: Int64
|
||||
public let immutableSize: Int64
|
||||
|
||||
public init(media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], mediaResourceIds: [MediaId: [MediaResourceId]], peers: [PeerId: Peer], otherSize: Int64, otherPaths: [String], cacheSize: Int64, tempPaths: [String], tempSize: Int64, immutableSize: Int64) {
|
||||
self.media = media
|
||||
self.mediaResourceIds = mediaResourceIds
|
||||
self.peers = peers
|
||||
self.otherSize = otherSize
|
||||
self.otherPaths = otherPaths
|
||||
self.cacheSize = cacheSize
|
||||
self.tempPaths = tempPaths
|
||||
self.tempSize = tempSize
|
||||
self.immutableSize = immutableSize
|
||||
}
|
||||
}
|
||||
|
||||
public enum CacheUsageStatsResult {
|
||||
case progress(Float)
|
||||
case result(CacheUsageStats)
|
||||
}
|
||||
|
||||
private enum CollectCacheUsageStatsError {
|
||||
case done(CacheUsageStats)
|
||||
case generic
|
||||
}
|
||||
|
||||
private final class CacheUsageStatsState {
|
||||
var media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]] = [:]
|
||||
var mediaResourceIds: [MediaId: [MediaResourceId]] = [:]
|
||||
var allResourceIds = Set<WrappedMediaResourceId>()
|
||||
var lowerBound: MessageIndex?
|
||||
}
|
||||
|
||||
public func collectCacheUsageStats(account: Account, additionalCachePaths: [String], logFilesPath: String) -> Signal<CacheUsageStatsResult, NoError> {
|
||||
let state = Atomic<CacheUsageStatsState>(value: CacheUsageStatsState())
|
||||
|
||||
let excludeResourceIds = account.postbox.transaction { transaction -> Set<WrappedMediaResourceId> in
|
||||
var result = Set<WrappedMediaResourceId>()
|
||||
transaction.enumeratePreferencesEntries({ entry in
|
||||
result.formUnion(entry.relatedResources.map(WrappedMediaResourceId.init))
|
||||
return true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
return excludeResourceIds
|
||||
|> mapToSignal { excludeResourceIds -> Signal<CacheUsageStatsResult, NoError> in
|
||||
let fetch = account.postbox.transaction { transaction -> ([PeerId : Set<MediaId>], [MediaId : Media], MessageIndex?) in
|
||||
return transaction.enumerateMedia(lowerBound: state.with { $0.lowerBound }, limit: 1000)
|
||||
}
|
||||
|> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() }
|
||||
|
||||
let process: ([PeerId : Set<MediaId>], [MediaId : Media], MessageIndex?) -> Signal<CacheUsageStatsResult, CollectCacheUsageStatsError> = { mediaByPeer, mediaRefs, updatedLowerBound in
|
||||
var mediaIdToPeerId: [MediaId: PeerId] = [:]
|
||||
for (peerId, mediaIds) in mediaByPeer {
|
||||
for id in mediaIds {
|
||||
mediaIdToPeerId[id] = peerId
|
||||
}
|
||||
}
|
||||
|
||||
var resourceIdToMediaId: [WrappedMediaResourceId: (MediaId, PeerCacheUsageCategory)] = [:]
|
||||
var mediaResourceIds: [MediaId: [MediaResourceId]] = [:]
|
||||
var resourceIds: [MediaResourceId] = []
|
||||
for (id, media) in mediaRefs {
|
||||
mediaResourceIds[id] = []
|
||||
var parsedMedia: [Media] = []
|
||||
switch media {
|
||||
case let image as TelegramMediaImage:
|
||||
parsedMedia.append(image)
|
||||
case let file as TelegramMediaFile:
|
||||
parsedMedia.append(file)
|
||||
case let webpage as TelegramMediaWebpage:
|
||||
if case let .Loaded(content) = webpage.content {
|
||||
if let image = content.image {
|
||||
parsedMedia.append(image)
|
||||
}
|
||||
if let file = content.file {
|
||||
parsedMedia.append(file)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
for media in parsedMedia {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
for representation in image.representations {
|
||||
resourceIds.append(representation.resource.id)
|
||||
resourceIdToMediaId[WrappedMediaResourceId(representation.resource.id)] = (id, .image)
|
||||
mediaResourceIds[id]!.append(representation.resource.id)
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
var category: PeerCacheUsageCategory = .file
|
||||
loop: for attribute in file.attributes {
|
||||
switch attribute {
|
||||
case .Video:
|
||||
category = .video
|
||||
break loop
|
||||
case .Audio:
|
||||
category = .audio
|
||||
break loop
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
for representation in file.previewRepresentations {
|
||||
resourceIds.append(representation.resource.id)
|
||||
resourceIdToMediaId[WrappedMediaResourceId(representation.resource.id)] = (id, category)
|
||||
mediaResourceIds[id]!.append(representation.resource.id)
|
||||
}
|
||||
resourceIds.append(file.resource.id)
|
||||
resourceIdToMediaId[WrappedMediaResourceId(file.resource.id)] = (id, category)
|
||||
mediaResourceIds[id]!.append(file.resource.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return account.postbox.mediaBox.collectResourceCacheUsage(resourceIds)
|
||||
|> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() }
|
||||
|> mapToSignal { result -> Signal<CacheUsageStatsResult, CollectCacheUsageStatsError> in
|
||||
state.with { state -> Void in
|
||||
state.lowerBound = updatedLowerBound
|
||||
for (wrappedId, size) in result {
|
||||
if let (id, category) = resourceIdToMediaId[wrappedId] {
|
||||
if let peerId = mediaIdToPeerId[id] {
|
||||
if state.media[peerId] == nil {
|
||||
state.media[peerId] = [:]
|
||||
}
|
||||
if state.media[peerId]![category] == nil {
|
||||
state.media[peerId]![category] = [:]
|
||||
}
|
||||
var currentSize: Int64 = 0
|
||||
if let current = state.media[peerId]![category]![id] {
|
||||
currentSize = current
|
||||
}
|
||||
state.media[peerId]![category]![id] = currentSize + size
|
||||
}
|
||||
}
|
||||
}
|
||||
for (id, ids) in mediaResourceIds {
|
||||
state.mediaResourceIds[id] = ids
|
||||
for resourceId in ids {
|
||||
state.allResourceIds.insert(WrappedMediaResourceId(resourceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
if updatedLowerBound == nil {
|
||||
let (finalMedia, finalMediaResourceIds, allResourceIds) = state.with { state -> ([PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], [MediaId: [MediaResourceId]], Set<WrappedMediaResourceId>) in
|
||||
return (state.media, state.mediaResourceIds, state.allResourceIds)
|
||||
}
|
||||
|
||||
return account.postbox.mediaBox.collectOtherResourceUsage(excludeIds: excludeResourceIds, combinedExcludeIds: allResourceIds.union(excludeResourceIds))
|
||||
|> mapError { _ in return CollectCacheUsageStatsError.generic }
|
||||
|> mapToSignal { otherSize, otherPaths, cacheSize in
|
||||
var tempPaths: [String] = []
|
||||
var tempSize: Int64 = 0
|
||||
#if os(iOS)
|
||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: NSTemporaryDirectory()), includingPropertiesForKeys: [.isDirectoryKey, .fileAllocatedSizeKey, .isSymbolicLinkKey]) {
|
||||
for url in enumerator {
|
||||
if let url = url as? URL {
|
||||
if let isDirectoryValue = (try? url.resourceValues(forKeys: Set([.isDirectoryKey])))?.isDirectory, isDirectoryValue {
|
||||
tempPaths.append(url.path)
|
||||
} else if let fileSizeValue = (try? url.resourceValues(forKeys: Set([.fileAllocatedSizeKey])))?.fileAllocatedSize {
|
||||
tempPaths.append(url.path)
|
||||
|
||||
if let isSymbolicLinkValue = (try? url.resourceValues(forKeys: Set([.isSymbolicLinkKey])))?.isSymbolicLink, isSymbolicLinkValue {
|
||||
} else {
|
||||
tempSize += Int64(fileSizeValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var immutableSize: Int64 = 0
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: account.basePath + "/postbox/db"), includingPropertiesForKeys: [URLResourceKey.fileSizeKey], options: []) {
|
||||
for url in files {
|
||||
if let fileSize = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize {
|
||||
immutableSize += Int64(fileSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logFilesPath), includingPropertiesForKeys: [URLResourceKey.fileSizeKey], options: []) {
|
||||
for url in files {
|
||||
if let fileSize = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize {
|
||||
immutableSize += Int64(fileSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for additionalPath in additionalCachePaths {
|
||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: additionalPath), includingPropertiesForKeys: [.isDirectoryKey, .fileAllocatedSizeKey, .isSymbolicLinkKey]) {
|
||||
for url in enumerator {
|
||||
if let url = url as? URL {
|
||||
if let isDirectoryValue = (try? url.resourceValues(forKeys: Set([.isDirectoryKey])))?.isDirectory, isDirectoryValue {
|
||||
} else if let fileSizeValue = (try? url.resourceValues(forKeys: Set([.fileAllocatedSizeKey])))?.fileAllocatedSize {
|
||||
tempPaths.append(url.path)
|
||||
|
||||
if let isSymbolicLinkValue = (try? url.resourceValues(forKeys: Set([.isSymbolicLinkKey])))?.isSymbolicLink, isSymbolicLinkValue {
|
||||
} else {
|
||||
tempSize += Int64(fileSizeValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> CacheUsageStats in
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
for peerId in finalMedia.keys {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
peers[peer.id] = peer
|
||||
if let associatedPeerId = peer.associatedPeerId, let associatedPeer = transaction.getPeer(associatedPeerId) {
|
||||
peers[associatedPeer.id] = associatedPeer
|
||||
}
|
||||
}
|
||||
}
|
||||
return CacheUsageStats(media: finalMedia, mediaResourceIds: finalMediaResourceIds, peers: peers, otherSize: otherSize, otherPaths: otherPaths, cacheSize: cacheSize, tempPaths: tempPaths, tempSize: tempSize, immutableSize: immutableSize)
|
||||
} |> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() }
|
||||
|> mapToSignal { stats -> Signal<CacheUsageStatsResult, CollectCacheUsageStatsError> in
|
||||
return .fail(.done(stats))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let signal = (fetch |> mapToSignal { mediaByPeer, mediaRefs, updatedLowerBound -> Signal<CacheUsageStatsResult, CollectCacheUsageStatsError> in
|
||||
return process(mediaByPeer, mediaRefs, updatedLowerBound)
|
||||
}) |> restart
|
||||
|
||||
return signal |> `catch` { error in
|
||||
switch error {
|
||||
case let .done(result):
|
||||
return .single(.result(result))
|
||||
case .generic:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func clearCachedMediaResources(account: Account, mediaResourceIds: Set<WrappedMediaResourceId>) -> Signal<Void, NoError> {
|
||||
return account.postbox.mediaBox.removeCachedResources(mediaResourceIds)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
SWIFT_INCLUDE_PATHS = $(SRCROOT)/TelegramCore
|
||||
MODULEMAP_PRIVATE_FILE = $(SRCROOT)/TelegramCore/module.private.modulemap
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum ConfirmTwoStepRecoveryEmailError {
|
||||
case invalidEmail
|
||||
case invalidCode
|
||||
case flood
|
||||
case expired
|
||||
case generic
|
||||
}
|
||||
|
||||
public func confirmTwoStepRecoveryEmail(network: Network, code: String) -> Signal<Never, ConfirmTwoStepRecoveryEmailError> {
|
||||
return network.request(Api.functions.account.confirmPasswordEmail(code: code), automaticFloodWait: false)
|
||||
|> mapError { error -> ConfirmTwoStepRecoveryEmailError in
|
||||
if error.errorDescription == "EMAIL_INVALID" {
|
||||
return .invalidEmail
|
||||
} else if error.errorDescription == "CODE_INVALID" {
|
||||
return .invalidCode
|
||||
} else if error.errorDescription == "EMAIL_HASH_EXPIRED" {
|
||||
return .expired
|
||||
} else if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .flood
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public enum ResendTwoStepRecoveryEmailError {
|
||||
case flood
|
||||
case generic
|
||||
}
|
||||
|
||||
public func resendTwoStepRecoveryEmail(network: Network) -> Signal<Never, ResendTwoStepRecoveryEmailError> {
|
||||
return network.request(Api.functions.account.resendPasswordEmail(), automaticFloodWait: false)
|
||||
|> mapError { error -> ResendTwoStepRecoveryEmailError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .flood
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public enum CancelTwoStepRecoveryEmailError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func cancelTwoStepRecoveryEmail(network: Network) -> Signal<Never, CancelTwoStepRecoveryEmailError> {
|
||||
return network.request(Api.functions.account.cancelPasswordEmail(), automaticFloodWait: false)
|
||||
|> mapError { _ -> CancelTwoStepRecoveryEmailError in
|
||||
return .generic
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public class ConsumableContentMessageAttribute: MessageAttribute {
|
||||
public let consumed: Bool
|
||||
|
||||
public init(consumed: Bool) {
|
||||
self.consumed = consumed
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.consumed = decoder.decodeInt32ForKey("c", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.consumed ? 1 : 0, forKey: "c")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public class ConsumablePersonalMentionMessageAttribute: MessageAttribute {
|
||||
public let consumed: Bool
|
||||
public let pending: Bool
|
||||
|
||||
public init(consumed: Bool, pending: Bool) {
|
||||
self.consumed = consumed
|
||||
self.pending = pending
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.consumed = decoder.decodeInt32ForKey("c", orElse: 0) != 0
|
||||
self.pending = decoder.decodeInt32ForKey("p", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.consumed ? 1 : 0, forKey: "c")
|
||||
encoder.encodeInt32(self.pending ? 1 : 0, forKey: "p")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
final class ConsumePersonalMessageAction: PendingMessageActionData {
|
||||
init() {
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
}
|
||||
|
||||
func isEqual(to: PendingMessageActionData) -> Bool {
|
||||
if let _ = to as? ConsumePersonalMessageAction {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
180
submodules/TelegramCore/TelegramCore/ContactManagement.swift
Normal file
180
submodules/TelegramCore/TelegramCore/ContactManagement.swift
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
import TelegramCorePrivateModule
|
||||
|
||||
private func md5(_ data: Data) -> Data {
|
||||
return data.withUnsafeBytes { bytes -> Data in
|
||||
return CryptoMD5(bytes, Int32(data.count))
|
||||
}
|
||||
}
|
||||
|
||||
private func updatedRemoteContactPeers(network: Network, hash: Int32) -> Signal<([Peer], [PeerId: PeerPresence], Int32)?, NoError> {
|
||||
return network.request(Api.functions.contacts.getContacts(hash: hash), automaticFloodWait: false)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.contacts.Contacts?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> ([Peer], [PeerId: PeerPresence], Int32)? in
|
||||
guard let result = result else {
|
||||
return nil
|
||||
}
|
||||
switch result {
|
||||
case .contactsNotModified:
|
||||
return nil
|
||||
case let .contacts(_, savedCount, users):
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
return (peers, peerPresences, savedCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func hashForCountAndIds(count: Int32, ids: [Int32]) -> Int32 {
|
||||
var acc: Int64 = 0
|
||||
|
||||
acc = (acc &* 20261) &+ Int64(count)
|
||||
|
||||
for id in ids {
|
||||
acc = (acc &* 20261) &+ Int64(id)
|
||||
acc = acc & Int64(0x7FFFFFFF)
|
||||
}
|
||||
return Int32(acc & Int64(0x7FFFFFFF))
|
||||
}
|
||||
|
||||
func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId) -> Signal<Never, NoError> {
|
||||
let initialContactPeerIdsHash = postbox.transaction { transaction -> Int32 in
|
||||
let contactPeerIds = transaction.getContactPeerIds()
|
||||
let totalCount = transaction.getRemoteContactCount()
|
||||
let peerIds = Set(contactPeerIds.filter({ $0.namespace == Namespaces.Peer.CloudUser }))
|
||||
return hashForCountAndIds(count: totalCount, ids: peerIds.map({ $0.id }).sorted())
|
||||
}
|
||||
|
||||
let updatedPeers = initialContactPeerIdsHash
|
||||
|> mapToSignal { hash -> Signal<([Peer], [PeerId: PeerPresence], Int32)?, NoError> in
|
||||
return updatedRemoteContactPeers(network: network, hash: hash)
|
||||
}
|
||||
|
||||
let appliedUpdatedPeers = updatedPeers
|
||||
|> mapToSignal { peersAndPresences -> Signal<Never, NoError> in
|
||||
if let (peers, peerPresences, totalCount) = peersAndPresences {
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
let previousIds = transaction.getContactPeerIds()
|
||||
let wasEmpty = previousIds.isEmpty
|
||||
|
||||
transaction.replaceRemoteContactCount(totalCount)
|
||||
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
|
||||
if wasEmpty {
|
||||
var insertSignal: Signal<Void, NoError> = .complete()
|
||||
for s in stride(from: 0, to: peers.count, by: 100) {
|
||||
let partPeers = Array(peers[s ..< min(s + 100, peers.count)])
|
||||
let partSignal = postbox.transaction { transaction -> Void in
|
||||
updatePeers(transaction: transaction, peers: partPeers, update: { return $1 })
|
||||
var updatedIds = transaction.getContactPeerIds()
|
||||
updatedIds.formUnion(partPeers.map { $0.id })
|
||||
transaction.replaceContactPeerIds(updatedIds)
|
||||
}
|
||||
|> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||
insertSignal = insertSignal |> then(partSignal)
|
||||
}
|
||||
|
||||
return insertSignal
|
||||
} else {
|
||||
transaction.replaceContactPeerIds(Set(peers.map { $0.id }))
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> ignoreValues
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
return appliedUpdatedPeers
|
||||
}
|
||||
|
||||
public func deleteContactPeerInteractively(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
return account.network.request(Api.functions.contacts.deleteContacts(id: [inputUser]))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
var peerIds = transaction.getContactPeerIds()
|
||||
if peerIds.contains(peerId) {
|
||||
peerIds.remove(peerId)
|
||||
transaction.replaceContactPeerIds(peerIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public func deleteAllContacts(account: Account) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> [Api.InputUser] in
|
||||
return transaction.getContactPeerIds().compactMap(transaction.getPeer).compactMap({ apiInputUser($0) }).compactMap({ $0 })
|
||||
}
|
||||
|> mapToSignal { users -> Signal<Never, NoError> in
|
||||
let deleteContacts = account.network.request(Api.functions.contacts.deleteContacts(id: users))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
let deleteImported = account.network.request(Api.functions.contacts.resetSaved())
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
return combineLatest(deleteContacts, deleteImported)
|
||||
|> mapToSignal { updates, _ -> Signal<Never, NoError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.replaceContactPeerIds(Set())
|
||||
transaction.clearDeviceContactImportInfoIdentifiers()
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
account.restartContactManagement()
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
|
||||
return .complete()
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func resetSavedContacts(network: Network) -> Signal<Void, NoError> {
|
||||
return network.request(Api.functions.contacts.resetSaved())
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
425
submodules/TelegramCore/TelegramCore/ContactSyncManager.swift
Normal file
425
submodules/TelegramCore/TelegramCore/ContactSyncManager.swift
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private final class ContactSyncOperation {
|
||||
let id: Int32
|
||||
var isRunning: Bool = false
|
||||
let content: ContactSyncOperationContent
|
||||
let disposable = DisposableSet()
|
||||
|
||||
init(id: Int32, content: ContactSyncOperationContent) {
|
||||
self.id = id
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
private enum ContactSyncOperationContent {
|
||||
case waitForUpdatedState
|
||||
case updatePresences
|
||||
case sync(importableContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]?)
|
||||
case updateIsContact([(PeerId, Bool)])
|
||||
}
|
||||
|
||||
private final class ContactSyncManagerImpl {
|
||||
private let queue: Queue
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let accountPeerId: PeerId
|
||||
private let stateManager: AccountStateManager
|
||||
private var nextId: Int32 = 0
|
||||
private var operations: [ContactSyncOperation] = []
|
||||
|
||||
private var lastContactPresencesRequestTimestamp: Double?
|
||||
private var reimportAttempts: [TelegramDeviceContactImportIdentifier: Double] = [:]
|
||||
|
||||
private let importableContactsDisposable = MetaDisposable()
|
||||
private let significantStateUpdateCompletedDisposable = MetaDisposable()
|
||||
|
||||
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager) {
|
||||
self.queue = queue
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.accountPeerId = accountPeerId
|
||||
self.stateManager = stateManager
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.importableContactsDisposable.dispose()
|
||||
}
|
||||
|
||||
func beginSync(importableContacts: Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError>) {
|
||||
self.importableContactsDisposable.set((importableContacts
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] importableContacts in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.addOperation(.waitForUpdatedState)
|
||||
strongSelf.addOperation(.updatePresences)
|
||||
strongSelf.addOperation(.sync(importableContacts: importableContacts))
|
||||
}))
|
||||
self.significantStateUpdateCompletedDisposable.set((self.stateManager.significantStateUpdateCompleted
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
let shouldUpdate: Bool
|
||||
if let lastContactPresencesRequestTimestamp = strongSelf.lastContactPresencesRequestTimestamp {
|
||||
if timestamp > lastContactPresencesRequestTimestamp + 2.0 * 60.0 {
|
||||
shouldUpdate = true
|
||||
} else {
|
||||
shouldUpdate = false
|
||||
}
|
||||
} else {
|
||||
shouldUpdate = true
|
||||
}
|
||||
if shouldUpdate {
|
||||
strongSelf.lastContactPresencesRequestTimestamp = timestamp
|
||||
var found = false
|
||||
for operation in strongSelf.operations {
|
||||
if case .updatePresences = operation.content {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
strongSelf.addOperation(.updatePresences)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func addIsContactUpdates(_ updates: [(PeerId, Bool)]) {
|
||||
self.addOperation(.updateIsContact(updates))
|
||||
}
|
||||
|
||||
func addOperation(_ content: ContactSyncOperationContent) {
|
||||
let id = self.nextId
|
||||
self.nextId += 1
|
||||
let operation = ContactSyncOperation(id: id, content: content)
|
||||
switch content {
|
||||
case .waitForUpdatedState:
|
||||
self.operations.append(operation)
|
||||
case .updatePresences:
|
||||
for i in (0 ..< self.operations.count).reversed() {
|
||||
if case .updatePresences = self.operations[i].content {
|
||||
if !self.operations[i].isRunning {
|
||||
self.operations.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.operations.append(operation)
|
||||
case .sync:
|
||||
for i in (0 ..< self.operations.count).reversed() {
|
||||
if case .sync = self.operations[i].content {
|
||||
if !self.operations[i].isRunning {
|
||||
self.operations.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.operations.append(operation)
|
||||
case let .updateIsContact(updates):
|
||||
var mergedUpdates: [(PeerId, Bool)] = []
|
||||
var removeIndices: [Int] = []
|
||||
for i in 0 ..< self.operations.count {
|
||||
if case let .updateIsContact(operationUpdates) = self.operations[i].content {
|
||||
if !self.operations[i].isRunning {
|
||||
mergedUpdates.append(contentsOf: operationUpdates)
|
||||
removeIndices.append(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
mergedUpdates.append(contentsOf: updates)
|
||||
for index in removeIndices.reversed() {
|
||||
self.operations.remove(at: index)
|
||||
}
|
||||
if self.operations.isEmpty || !self.operations[0].isRunning {
|
||||
self.operations.insert(operation, at: 0)
|
||||
} else {
|
||||
self.operations.insert(operation, at: 1)
|
||||
}
|
||||
}
|
||||
self.updateOperations()
|
||||
}
|
||||
|
||||
func updateOperations() {
|
||||
if let first = self.operations.first, !first.isRunning {
|
||||
first.isRunning = true
|
||||
let id = first.id
|
||||
let queue = self.queue
|
||||
self.startOperation(first.content, disposable: first.disposable, completion: { [weak self] in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let currentFirst = strongSelf.operations.first, currentFirst.id == id {
|
||||
strongSelf.operations.remove(at: 0)
|
||||
strongSelf.updateOperations()
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func startOperation(_ operation: ContactSyncOperationContent, disposable: DisposableSet, completion: @escaping () -> Void) {
|
||||
switch operation {
|
||||
case .waitForUpdatedState:
|
||||
disposable.add((self.stateManager.pollStateUpdateCompletion()
|
||||
|> deliverOn(self.queue)).start(next: { _ in
|
||||
completion()
|
||||
}))
|
||||
case .updatePresences:
|
||||
disposable.add(updateContactPresences(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId).start(completed: {
|
||||
completion()
|
||||
}))
|
||||
case let .sync(importableContacts):
|
||||
let importSignal: Signal<PushDeviceContactsResult, NoError>
|
||||
if let importableContacts = importableContacts {
|
||||
importSignal = pushDeviceContacts(postbox: self.postbox, network: self.network, importableContacts: importableContacts, reimportAttempts: self.reimportAttempts)
|
||||
} else {
|
||||
importSignal = .single(PushDeviceContactsResult(addedReimportAttempts: [:]))
|
||||
}
|
||||
disposable.add(
|
||||
(syncContactsOnce(network: self.network, postbox: self.postbox, accountPeerId: self.accountPeerId)
|
||||
|> mapToSignal { _ -> Signal<PushDeviceContactsResult, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> then(importSignal)
|
||||
|> deliverOn(self.queue)
|
||||
).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
for (identifier, timestamp) in result.addedReimportAttempts {
|
||||
strongSelf.reimportAttempts[identifier] = timestamp
|
||||
}
|
||||
|
||||
completion()
|
||||
}))
|
||||
case let .updateIsContact(updates):
|
||||
disposable.add((self.postbox.transaction { transaction -> Void in
|
||||
var contactPeerIds = transaction.getContactPeerIds()
|
||||
for (peerId, isContact) in updates {
|
||||
if isContact {
|
||||
contactPeerIds.insert(peerId)
|
||||
} else {
|
||||
contactPeerIds.remove(peerId)
|
||||
}
|
||||
}
|
||||
transaction.replaceContactPeerIds(contactPeerIds)
|
||||
}
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completion()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PushDeviceContactsResult {
|
||||
let addedReimportAttempts: [TelegramDeviceContactImportIdentifier: Double]
|
||||
}
|
||||
|
||||
private func pushDeviceContacts(postbox: Postbox, network: Network, importableContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], reimportAttempts: [TelegramDeviceContactImportIdentifier: Double]) -> Signal<PushDeviceContactsResult, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<PushDeviceContactsResult, NoError> in
|
||||
var noLongerImportedIdentifiers = Set<TelegramDeviceContactImportIdentifier>()
|
||||
var updatedDataIdentifiers = Set<TelegramDeviceContactImportIdentifier>()
|
||||
var addedIdentifiers = Set<TelegramDeviceContactImportIdentifier>()
|
||||
var retryLaterIdentifiers = Set<TelegramDeviceContactImportIdentifier>()
|
||||
|
||||
addedIdentifiers.formUnion(importableContacts.keys.map(TelegramDeviceContactImportIdentifier.phoneNumber))
|
||||
transaction.enumerateDeviceContactImportInfoItems({ key, value in
|
||||
if let identifier = TelegramDeviceContactImportIdentifier(key: key) {
|
||||
addedIdentifiers.remove(identifier)
|
||||
switch identifier {
|
||||
case let .phoneNumber(number):
|
||||
if let updatedData = importableContacts[number] {
|
||||
if let value = value as? TelegramDeviceContactImportedData {
|
||||
switch value {
|
||||
case let .imported(imported):
|
||||
if imported.data != updatedData {
|
||||
updatedDataIdentifiers.insert(identifier)
|
||||
}
|
||||
case .retryLater:
|
||||
retryLaterIdentifiers.insert(identifier)
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
} else {
|
||||
noLongerImportedIdentifiers.insert(identifier)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
for identifier in noLongerImportedIdentifiers {
|
||||
transaction.setDeviceContactImportInfo(identifier.key, value: nil)
|
||||
}
|
||||
|
||||
var orderedPushIdentifiers: [TelegramDeviceContactImportIdentifier] = []
|
||||
orderedPushIdentifiers.append(contentsOf: addedIdentifiers.sorted())
|
||||
orderedPushIdentifiers.append(contentsOf: updatedDataIdentifiers.sorted())
|
||||
orderedPushIdentifiers.append(contentsOf: retryLaterIdentifiers.sorted())
|
||||
|
||||
var currentContactDetails: [TelegramDeviceContactImportIdentifier: TelegramUser] = [:]
|
||||
for peerId in transaction.getContactPeerIds() {
|
||||
if let user = transaction.getPeer(peerId) as? TelegramUser, let phone = user.phone, !phone.isEmpty {
|
||||
currentContactDetails[.phoneNumber(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))] = user
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
outer: for i in (0 ..< orderedPushIdentifiers.count).reversed() {
|
||||
if let user = currentContactDetails[orderedPushIdentifiers[i]], case let .phoneNumber(number) = orderedPushIdentifiers[i], let data = importableContacts[number] {
|
||||
if (user.firstName ?? "") == data.firstName && (user.lastName ?? "") == data.lastName {
|
||||
transaction.setDeviceContactImportInfo(orderedPushIdentifiers[i].key, value: TelegramDeviceContactImportedData.imported(data: data, importedByCount: 0))
|
||||
orderedPushIdentifiers.remove(at: i)
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
if let attemptTimestamp = reimportAttempts[orderedPushIdentifiers[i]], attemptTimestamp + 60.0 * 60.0 * 24.0 > timestamp {
|
||||
orderedPushIdentifiers.remove(at: i)
|
||||
}
|
||||
}
|
||||
|
||||
var preparedContactData: [(DeviceContactNormalizedPhoneNumber, ImportableDeviceContactData)] = []
|
||||
for identifier in orderedPushIdentifiers {
|
||||
if case let .phoneNumber(number) = identifier, let value = importableContacts[number] {
|
||||
preparedContactData.append((number, value))
|
||||
}
|
||||
}
|
||||
|
||||
return pushDeviceContactData(postbox: postbox, network: network, contacts: preparedContactData)
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
private let importBatchCount: Int = 100
|
||||
|
||||
private func pushDeviceContactData(postbox: Postbox, network: Network, contacts: [(DeviceContactNormalizedPhoneNumber, ImportableDeviceContactData)]) -> Signal<PushDeviceContactsResult, NoError> {
|
||||
var batches: Signal<PushDeviceContactsResult, NoError> = .single(PushDeviceContactsResult(addedReimportAttempts: [:]))
|
||||
for s in stride(from: 0, to: contacts.count, by: importBatchCount) {
|
||||
let batch = Array(contacts[s ..< min(s + importBatchCount, contacts.count)])
|
||||
batches = batches
|
||||
|> mapToSignal { intermediateResult -> Signal<PushDeviceContactsResult, NoError> in
|
||||
return network.request(Api.functions.contacts.importContacts(contacts: zip(0 ..< batch.count, batch).map { index, item -> Api.InputContact in
|
||||
return .inputPhoneContact(clientId: Int64(index), phone: item.0.rawValue, firstName: item.1.firstName, lastName: item.1.lastName)
|
||||
}))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.contacts.ImportedContacts?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<PushDeviceContactsResult, NoError> in
|
||||
return postbox.transaction { transaction -> PushDeviceContactsResult in
|
||||
var addedReimportAttempts: [TelegramDeviceContactImportIdentifier: Double] = intermediateResult.addedReimportAttempts
|
||||
if let result = result {
|
||||
var addedContactPeerIds = Set<PeerId>()
|
||||
var retryIndices = Set<Int>()
|
||||
var importedCounts: [Int: Int32] = [:]
|
||||
switch result {
|
||||
case let .importedContacts(imported, popularInvites, retryContacts, users):
|
||||
let peers = users.map { TelegramUser(user: $0) as Peer }
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
for item in imported {
|
||||
switch item {
|
||||
case let .importedContact(userId, _):
|
||||
addedContactPeerIds.insert(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
|
||||
}
|
||||
}
|
||||
for item in retryContacts {
|
||||
retryIndices.insert(Int(item))
|
||||
}
|
||||
for item in popularInvites {
|
||||
switch item {
|
||||
case let .popularContact(clientId, importers):
|
||||
importedCounts[Int(clientId)] = importers
|
||||
}
|
||||
}
|
||||
}
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
for i in 0 ..< batch.count {
|
||||
let importedData: TelegramDeviceContactImportedData
|
||||
if retryIndices.contains(i) {
|
||||
importedData = .retryLater
|
||||
addedReimportAttempts[.phoneNumber(batch[i].0)] = timestamp
|
||||
} else {
|
||||
importedData = .imported(data: batch[i].1, importedByCount: importedCounts[i] ?? 0)
|
||||
}
|
||||
transaction.setDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(batch[i].0).key, value: importedData)
|
||||
}
|
||||
var contactPeerIds = transaction.getContactPeerIds()
|
||||
contactPeerIds.formUnion(addedContactPeerIds)
|
||||
transaction.replaceContactPeerIds(contactPeerIds)
|
||||
} else {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
for (number, _) in batch {
|
||||
addedReimportAttempts[.phoneNumber(number)] = timestamp
|
||||
transaction.setDeviceContactImportInfo(TelegramDeviceContactImportIdentifier.phoneNumber(number).key, value: TelegramDeviceContactImportedData.retryLater)
|
||||
}
|
||||
}
|
||||
|
||||
return PushDeviceContactsResult(addedReimportAttempts: addedReimportAttempts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return batches
|
||||
}
|
||||
|
||||
private func updateContactPresences(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Never, NoError> {
|
||||
return network.request(Api.functions.contacts.getStatuses())
|
||||
|> `catch` { _ -> Signal<[Api.ContactStatus], NoError> in
|
||||
return .single([])
|
||||
}
|
||||
|> mapToSignal { statuses -> Signal<Never, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
for status in statuses {
|
||||
switch status {
|
||||
case let .contactStatus(userId, status):
|
||||
peerPresences[PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)] = TelegramUserPresence(apiStatus: status)
|
||||
}
|
||||
}
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
||||
final class ContactSyncManager {
|
||||
private let queue = Queue()
|
||||
private let impl: QueueLocalObject<ContactSyncManagerImpl>
|
||||
|
||||
init(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return ContactSyncManagerImpl(queue: queue, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager)
|
||||
})
|
||||
}
|
||||
|
||||
func beginSync(importableContacts: Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError>) {
|
||||
self.impl.with { impl in
|
||||
impl.beginSync(importableContacts: importableContacts)
|
||||
}
|
||||
}
|
||||
|
||||
func addIsContactUpdates(_ updates: [(PeerId, Bool)]) {
|
||||
self.impl.with { impl in
|
||||
impl.addIsContactUpdates(updates)
|
||||
}
|
||||
}
|
||||
}
|
||||
34
submodules/TelegramCore/TelegramCore/ContactsSettings.swift
Normal file
34
submodules/TelegramCore/TelegramCore/ContactsSettings.swift
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public struct ContactsSettings: Equatable, PreferencesEntry {
|
||||
public var synchronizeContacts: Bool
|
||||
|
||||
public static var defaultSettings: ContactsSettings {
|
||||
return ContactsSettings(synchronizeContacts: true)
|
||||
}
|
||||
|
||||
public init(synchronizeContacts: Bool) {
|
||||
self.synchronizeContacts = synchronizeContacts
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.synchronizeContacts = decoder.decodeInt32ForKey("synchronizeContacts", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.synchronizeContacts ? 1 : 0, forKey: "synchronizeContacts")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? ContactsSettings {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public final class ContentPrivacySettings: PreferencesEntry, Equatable {
|
||||
public let enableSecretChatWebpagePreviews: Bool?
|
||||
|
||||
public static var defaultSettings = ContentPrivacySettings(enableSecretChatWebpagePreviews: nil)
|
||||
|
||||
public init(enableSecretChatWebpagePreviews: Bool?) {
|
||||
self.enableSecretChatWebpagePreviews = enableSecretChatWebpagePreviews
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.enableSecretChatWebpagePreviews = decoder.decodeOptionalInt32ForKey("enableSecretChatWebpagePreviews").flatMap { $0 != 0 }
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let enableSecretChatWebpagePreviews = self.enableSecretChatWebpagePreviews {
|
||||
encoder.encodeInt32(enableSecretChatWebpagePreviews ? 1 : 0, forKey: "enableSecretChatWebpagePreviews")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "enableSecretChatWebpagePreviews")
|
||||
}
|
||||
}
|
||||
|
||||
public func withUpdatedEnableSecretChatWebpagePreviews(_ enableSecretChatWebpagePreviews: Bool) -> ContentPrivacySettings {
|
||||
return ContentPrivacySettings(enableSecretChatWebpagePreviews: enableSecretChatWebpagePreviews)
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
guard let to = to as? ContentPrivacySettings else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self == to
|
||||
}
|
||||
|
||||
public static func ==(lhs: ContentPrivacySettings, rhs: ContentPrivacySettings) -> Bool {
|
||||
if lhs.enableSecretChatWebpagePreviews != rhs.enableSecretChatWebpagePreviews {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func updateContentPrivacySettings(postbox: Postbox, _ f: @escaping (ContentPrivacySettings) -> ContentPrivacySettings) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var updated: ContentPrivacySettings?
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.contentPrivacySettings, { current in
|
||||
if let current = current as? ContentPrivacySettings {
|
||||
updated = f(current)
|
||||
return updated
|
||||
} else {
|
||||
updated = f(ContentPrivacySettings.defaultSettings)
|
||||
return updated
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public class ContentRequiresValidationMessageAttribute: MessageAttribute {
|
||||
public init() {
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum ConvertGroupToSupergroupError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func convertGroupToSupergroup(account: Account, peerId: PeerId) -> Signal<PeerId, ConvertGroupToSupergroupError> {
|
||||
return account.network.request(Api.functions.messages.migrateChat(chatId: peerId.id))
|
||||
|> mapError { _ -> ConvertGroupToSupergroupError in
|
||||
return .generic
|
||||
}
|
||||
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
||||
|> mapToSignal { updates -> Signal<PeerId, ConvertGroupToSupergroupError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
var createdPeerId: PeerId?
|
||||
for message in updates.messages {
|
||||
if apiMessagePeerId(message) != peerId {
|
||||
createdPeerId = apiMessagePeerId(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let createdPeerId = createdPeerId {
|
||||
return account.postbox.multiplePeersView([createdPeerId])
|
||||
|> filter { view in
|
||||
return view.peers[createdPeerId] != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ in
|
||||
return createdPeerId
|
||||
}
|
||||
|> mapError { _ -> ConvertGroupToSupergroupError in
|
||||
return .generic
|
||||
}
|
||||
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
67
submodules/TelegramCore/TelegramCore/CoreSettings.swift
Normal file
67
submodules/TelegramCore/TelegramCore/CoreSettings.swift
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public final class CoreSettings: PreferencesEntry, Equatable {
|
||||
public let fastForward: Bool
|
||||
|
||||
public static var defaultSettings = CoreSettings(fastForward: true)
|
||||
|
||||
public init(fastForward: Bool) {
|
||||
self.fastForward = fastForward
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.fastForward = decoder.decodeInt32ForKey("fastForward", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.fastForward ? 1 : 0, forKey: "fastForward")
|
||||
}
|
||||
|
||||
public func withUpdatedFastForward(_ fastForward: Bool) -> CoreSettings {
|
||||
return CoreSettings(fastForward: fastForward)
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
guard let to = to as? CoreSettings else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self == to
|
||||
}
|
||||
|
||||
public static func ==(lhs: CoreSettings, rhs: CoreSettings) -> Bool {
|
||||
if lhs.fastForward != rhs.fastForward {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func updateCoreSettings(postbox: Postbox, _ f: @escaping (CoreSettings) -> CoreSettings) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var updated: CoreSettings?
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.coreSettings, { current in
|
||||
if let current = current as? CoreSettings {
|
||||
updated = f(current)
|
||||
return updated
|
||||
} else {
|
||||
updated = f(CoreSettings.defaultSettings)
|
||||
return updated
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
58
submodules/TelegramCore/TelegramCore/CreateGroup.swift
Normal file
58
submodules/TelegramCore/TelegramCore/CreateGroup.swift
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum CreateGroupError {
|
||||
case generic
|
||||
case privacy
|
||||
case restricted
|
||||
}
|
||||
|
||||
public func createGroup(account: Account, title: String, peerIds: [PeerId]) -> Signal<PeerId?, CreateGroupError> {
|
||||
return account.postbox.transaction { transaction -> Signal<PeerId?, CreateGroupError> in
|
||||
var inputUsers: [Api.InputUser] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
inputUsers.append(inputUser)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
return account.network.request(Api.functions.messages.createChat(users: inputUsers, title: title))
|
||||
|> mapError { error -> CreateGroupError in
|
||||
if error.errorDescription == "USERS_TOO_FEW" {
|
||||
return .privacy
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<PeerId?, CreateGroupError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
if let message = updates.messages.first, let peerId = apiMessagePeerId(message) {
|
||||
return account.postbox.multiplePeersView([peerId])
|
||||
|> filter { view in
|
||||
return view.peers[peerId] != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> introduceError(CreateGroupError.self)
|
||||
|> map { _ in
|
||||
return peerId
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> introduceError(CreateGroupError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
57
submodules/TelegramCore/TelegramCore/CreateSecretChat.swift
Normal file
57
submodules/TelegramCore/TelegramCore/CreateSecretChat.swift
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum CreateSecretChatError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId, CreateSecretChatError> {
|
||||
return account.postbox.transaction { transaction -> Signal<PeerId, CreateSecretChatError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
return validatedEncryptionConfig(postbox: account.postbox, network: account.network)
|
||||
|> mapError { _ -> CreateSecretChatError in return .generic }
|
||||
|> mapToSignal { config -> Signal<PeerId, CreateSecretChatError> in
|
||||
let aBytes = malloc(256)!
|
||||
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||
|
||||
var gValue: Int32 = config.g.byteSwapped
|
||||
let g = Data(bytes: &gValue, count: 4)
|
||||
let p = config.p.makeData()
|
||||
|
||||
let aData = a.makeData()
|
||||
let ga = MTExp(g, aData, p)!
|
||||
|
||||
if !MTCheckIsSafeGAOrB(ga, p) {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)))
|
||||
|> mapError { _ -> CreateSecretChatError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<PeerId, CreateSecretChatError> in
|
||||
return account.postbox.transaction { transaction -> PeerId in
|
||||
updateSecretChat(accountPeerId: account.peerId, transaction: transaction, chat: result, requestData: SecretChatRequestData(g: config.g, p: config.p, a: a))
|
||||
|
||||
return result.peerId
|
||||
} |> mapError { _ -> CreateSecretChatError in return .generic }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} |> mapError { _ -> CreateSecretChatError in return .generic } |> switchToLatest
|
||||
}
|
||||
22
submodules/TelegramCore/TelegramCore/Crypto.h
Normal file
22
submodules/TelegramCore/TelegramCore/Crypto.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef __CRYPTO_H_
|
||||
#define __CRYPTO_H_
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NSData * _Nonnull CryptoMD5(const void *bytes, int count);
|
||||
NSData * _Nonnull CryptoSHA1(const void *bytes, int count);
|
||||
NSData * _Nonnull CryptoSHA256(const void *bytes, int count);
|
||||
NSData * _Nonnull CryptoSHA512(const void *bytes, int count);
|
||||
|
||||
@interface IncrementalMD5 : NSObject
|
||||
|
||||
- (instancetype _Nonnull)init;
|
||||
- (void)update:(NSData * _Nonnull)data;
|
||||
- (void)update:(const void * _Nonnull)bytes count:(int)count;
|
||||
- (NSData * _Nonnull)complete;
|
||||
|
||||
@end
|
||||
|
||||
NSData * _Nullable CryptoAES(bool encrypt, NSData * _Nonnull key, NSData * _Nonnull iv, NSData * _Nonnull data);
|
||||
|
||||
#endif
|
||||
78
submodules/TelegramCore/TelegramCore/Crypto.m
Normal file
78
submodules/TelegramCore/TelegramCore/Crypto.m
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "Crypto.h"
|
||||
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
|
||||
NSData * _Nonnull CryptoMD5(const void *bytes, int count) {
|
||||
NSMutableData *result = [[NSMutableData alloc] initWithLength:(NSUInteger)CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5(bytes, (CC_LONG)count, result.mutableBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
NSData * _Nonnull CryptoSHA1(const void *bytes, int count) {
|
||||
NSMutableData *result = [[NSMutableData alloc] initWithLength:(NSUInteger)CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1(bytes, (CC_LONG)count, result.mutableBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
NSData * _Nonnull CryptoSHA256(const void *bytes, int count) {
|
||||
NSMutableData *result = [[NSMutableData alloc] initWithLength:(NSUInteger)CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(bytes, (CC_LONG)count, result.mutableBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
NSData * _Nonnull CryptoSHA512(const void *bytes, int count) {
|
||||
NSMutableData *result = [[NSMutableData alloc] initWithLength:(NSUInteger)CC_SHA512_DIGEST_LENGTH];
|
||||
CC_SHA512(bytes, (CC_LONG)count, result.mutableBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
@interface IncrementalMD5 () {
|
||||
CC_MD5_CTX _ctx;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation IncrementalMD5
|
||||
|
||||
- (instancetype _Nonnull)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
CC_MD5_Init(&_ctx);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)update:(NSData * _Nonnull)data {
|
||||
CC_MD5_Update(&_ctx, data.bytes, (CC_LONG)data.length);
|
||||
}
|
||||
|
||||
- (void)update:(const void *)bytes count:(int)count {
|
||||
CC_MD5_Update(&_ctx, bytes, (CC_LONG)count);
|
||||
}
|
||||
|
||||
- (NSData *)complete {
|
||||
NSMutableData *result = [[NSMutableData alloc] initWithLength:(NSUInteger)CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5_Final(result.mutableBytes, &_ctx);
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSData * _Nullable CryptoAES(bool encrypt, NSData * _Nonnull key, NSData * _Nonnull iv, NSData * _Nonnull data) {
|
||||
if (key.length != 32) {
|
||||
return nil;
|
||||
}
|
||||
if (iv.length != 16) {
|
||||
return nil;
|
||||
}
|
||||
NSMutableData *processedData = [[NSMutableData alloc] initWithLength:data.length];
|
||||
size_t processedCount = 0;
|
||||
CCStatus status = CCCrypt(encrypt ? kCCEncrypt : kCCDecrypt, kCCAlgorithmAES128, 0, key.bytes, key.length, iv.bytes, data.bytes, data.length, processedData.mutableBytes, processedData.length, &processedCount);
|
||||
if (status != kCCSuccess) {
|
||||
return nil;
|
||||
}
|
||||
if (processedCount != (size_t)processedData.length) {
|
||||
return nil;
|
||||
}
|
||||
return processedData;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public func decryptedResourceData(data: MediaResourceData, resource: MediaResource, params: Any) -> Data? {
|
||||
guard data.complete else {
|
||||
return nil
|
||||
}
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) else {
|
||||
return nil
|
||||
}
|
||||
if let resource = resource as? EncryptedMediaResource {
|
||||
return resource.decrypt(data: data, params: params)
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
24
submodules/TelegramCore/TelegramCore/DeepLinkInfo.swift
Normal file
24
submodules/TelegramCore/TelegramCore/DeepLinkInfo.swift
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public struct DeepLinkInfo {
|
||||
public let message: String
|
||||
public let entities: [MessageTextEntity]
|
||||
public let updateApp: Bool
|
||||
}
|
||||
|
||||
public func getDeepLinkInfo(network: Network, path: String) -> Signal<DeepLinkInfo?, NoError> {
|
||||
return network.request(Api.functions.help.getDeepLinkInfo(path: path)) |> retryRequest
|
||||
|> map { value -> DeepLinkInfo? in
|
||||
switch value {
|
||||
case .deepLinkInfoEmpty:
|
||||
return nil
|
||||
case let .deepLinkInfo(flags, message, entities):
|
||||
return DeepLinkInfo(message: message, entities: entities != nil ? messageTextEntitiesFromApiEntities(entities!) : [], updateApp: (flags & (1 << 0)) != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
40
submodules/TelegramCore/TelegramCore/DeleteMessages.swift
Normal file
40
submodules/TelegramCore/TelegramCore/DeleteMessages.swift
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private func removeMessageMedia(message: Message, mediaBox: MediaBox) {
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
let _ = mediaBox.removeCachedResources(Set(image.representations.map({ WrappedMediaResourceId($0.resource.id) }))).start()
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let _ = mediaBox.removeCachedResources(Set(file.previewRepresentations.map({ WrappedMediaResourceId($0.resource.id) }))).start()
|
||||
let _ = mediaBox.removeCachedResources(Set([WrappedMediaResourceId(file.resource.id)])).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId]) {
|
||||
for id in ids {
|
||||
if id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
if let message = transaction.getMessage(id) {
|
||||
removeMessageMedia(message: message, mediaBox: mediaBox)
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.deleteMessages(ids)
|
||||
}
|
||||
|
||||
public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId) {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
transaction.withAllMessages(peerId: peerId, { message in
|
||||
removeMessageMedia(message: message, mediaBox: mediaBox)
|
||||
return true
|
||||
})
|
||||
}
|
||||
transaction.clearHistory(peerId)
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum InteractiveMessagesDeletionType: Int32 {
|
||||
case forLocalPeer = 0
|
||||
case forEveryone = 1
|
||||
}
|
||||
|
||||
public func deleteMessagesInteractively(postbox: Postbox, messageIds: [MessageId], type: InteractiveMessagesDeletionType) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var messageIdsByPeerId: [PeerId: [MessageId]] = [:]
|
||||
for id in messageIds {
|
||||
if messageIdsByPeerId[id.peerId] == nil {
|
||||
messageIdsByPeerId[id.peerId] = [id]
|
||||
} else {
|
||||
messageIdsByPeerId[id.peerId]!.append(id)
|
||||
}
|
||||
}
|
||||
for (peerId, peerMessageIds) in messageIdsByPeerId {
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, messageIds: peerMessageIds, type: CloudChatRemoveMessagesType(type))
|
||||
} else if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
||||
var layer: SecretChatLayer?
|
||||
switch state.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
break
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
if let layer = layer {
|
||||
var globallyUniqueIds: [Int64] = []
|
||||
for messageId in peerMessageIds {
|
||||
if let message = transaction.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId {
|
||||
globallyUniqueIds.append(globallyUniqueId)
|
||||
}
|
||||
}
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: SecretChatOutgoingOperationContents.deleteMessages(layer: layer, actionGloballyUniqueId: arc4random64(), globallyUniqueIds: globallyUniqueIds), state: state)
|
||||
if updatedState != state {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: messageIds)
|
||||
}
|
||||
}
|
||||
|
||||
public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId, type: InteractiveMessagesDeletionType) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
cloudChatAddClearHistoryOperation(transaction: transaction, peerId: peerId, explicitTopMessageId: nil, type: type)
|
||||
var topIndex: MessageIndex?
|
||||
if let topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud), let topMessage = transaction.getMessage(topMessageId) {
|
||||
topIndex = topMessage.index
|
||||
}
|
||||
clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId)
|
||||
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, let migrationReference = cachedData.migrationReference {
|
||||
cloudChatAddClearHistoryOperation(transaction: transaction, peerId: migrationReference.maxMessageId.peerId, explicitTopMessageId: MessageId(peerId: migrationReference.maxMessageId.peerId, namespace: migrationReference.maxMessageId.namespace, id: migrationReference.maxMessageId.id + 1), type: type)
|
||||
clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: migrationReference.maxMessageId.peerId)
|
||||
}
|
||||
if let topIndex = topIndex {
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random)
|
||||
} else {
|
||||
updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false)
|
||||
}
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId)
|
||||
|
||||
if let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
||||
var layer: SecretChatLayer?
|
||||
switch state.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
break
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
|
||||
if let layer = layer {
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: SecretChatOutgoingOperationContents.clearHistory(layer: layer, actionGloballyUniqueId: arc4random64()), state: state)
|
||||
if updatedState != state {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func clearAuthorHistory(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputChannel = apiInputChannel(peer), let inputUser = apiInputUser(memberPeer) {
|
||||
|
||||
let signal = account.network.request(Api.functions.channels.deleteUserHistory(channel: inputChannel, userId: inputUser))
|
||||
|> map { result -> Api.messages.AffectedHistory? in
|
||||
return result
|
||||
}
|
||||
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, Bool> in
|
||||
return .fail(false)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, Bool> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .affectedHistory(pts, ptsCount, offset):
|
||||
account.stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
|
||||
if offset == 0 {
|
||||
return .fail(true)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .fail(true)
|
||||
}
|
||||
}
|
||||
return (signal
|
||||
|> restart)
|
||||
|> `catch` { success -> Signal<Void, NoError> in
|
||||
if success {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud)
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import Foundation
|
||||
|
||||
public final class FunctionDescription {
|
||||
let name: String
|
||||
let parameters: [(String, Any)]
|
||||
|
||||
init(name: String, parameters: [(String, Any)]) {
|
||||
self.name = name
|
||||
self.parameters = parameters
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeserializeFunctionResponse<T> {
|
||||
private let f: (Buffer) -> T?
|
||||
|
||||
public init(_ f: @escaping (Buffer) -> T?) {
|
||||
self.f = f
|
||||
}
|
||||
|
||||
public func parse(_ buffer: Buffer) -> T? {
|
||||
return self.f(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
protocol TypeConstructorDescription {
|
||||
func descriptionFields() -> (String, [(String, Any)])
|
||||
}
|
||||
108
submodules/TelegramCore/TelegramCore/DeviceContact.swift
Normal file
108
submodules/TelegramCore/TelegramCore/DeviceContact.swift
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public struct DeviceContactNormalizedPhoneNumber: Hashable, RawRepresentable {
|
||||
public let rawValue: String
|
||||
|
||||
public init(rawValue: String) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeviceContactPhoneNumberValue: Equatable {
|
||||
public let plain: String
|
||||
public let normalized: DeviceContactNormalizedPhoneNumber
|
||||
|
||||
public init(plain: String, normalized: DeviceContactNormalizedPhoneNumber) {
|
||||
self.plain = plain
|
||||
self.normalized = normalized
|
||||
}
|
||||
|
||||
public static func ==(lhs: DeviceContactPhoneNumberValue, rhs: DeviceContactPhoneNumberValue) -> Bool {
|
||||
if lhs.plain != rhs.plain {
|
||||
return false
|
||||
}
|
||||
if lhs.normalized != rhs.normalized {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeviceContactPhoneNumber: Equatable {
|
||||
public let label: String
|
||||
public let number: DeviceContactPhoneNumberValue
|
||||
|
||||
public init(label: String, number: DeviceContactPhoneNumberValue) {
|
||||
self.label = label
|
||||
self.number = number
|
||||
}
|
||||
|
||||
public static func ==(lhs: DeviceContactPhoneNumber, rhs: DeviceContactPhoneNumber) -> Bool {
|
||||
return lhs.label == rhs.label && lhs.number == rhs.number
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeviceContact: Equatable {
|
||||
public let id: String
|
||||
public let firstName: String
|
||||
public let lastName: String
|
||||
public let phoneNumbers: [DeviceContactPhoneNumber]
|
||||
|
||||
public init(id: String, firstName: String, lastName: String, phoneNumbers: [DeviceContactPhoneNumber]) {
|
||||
self.id = id
|
||||
self.firstName = firstName
|
||||
self.lastName = lastName
|
||||
self.phoneNumbers = phoneNumbers
|
||||
}
|
||||
|
||||
public static func ==(lhs: DeviceContact, rhs: DeviceContact) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
return false
|
||||
}
|
||||
if lhs.firstName != rhs.firstName {
|
||||
return false
|
||||
}
|
||||
if lhs.lastName != rhs.lastName {
|
||||
return false
|
||||
}
|
||||
if lhs.phoneNumbers != rhs.phoneNumbers {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class ImportableDeviceContactData: Equatable, PostboxCoding {
|
||||
public let firstName: String
|
||||
public let lastName: String
|
||||
|
||||
public init(firstName: String, lastName: String) {
|
||||
self.firstName = firstName
|
||||
self.lastName = lastName
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.firstName = decoder.decodeStringForKey("f", orElse: "")
|
||||
self.lastName = decoder.decodeStringForKey("l", orElse: "")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.firstName, forKey: "f")
|
||||
encoder.encodeString(self.lastName, forKey: "l")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ImportableDeviceContactData, rhs: ImportableDeviceContactData) -> Bool {
|
||||
if lhs.firstName != rhs.firstName {
|
||||
return false
|
||||
}
|
||||
if lhs.lastName != rhs.lastName {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
307
submodules/TelegramCore/TelegramCore/Download.swift
Normal file
307
submodules/TelegramCore/TelegramCore/Download.swift
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import MtProtoKitMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private func roundUp(_ value: Int, to multiple: Int) -> Int {
|
||||
if multiple == 0 {
|
||||
return value
|
||||
}
|
||||
|
||||
let remainder = value % multiple
|
||||
if remainder == 0 {
|
||||
return value
|
||||
}
|
||||
|
||||
return value + multiple - remainder
|
||||
}
|
||||
|
||||
enum UploadPartError {
|
||||
case generic
|
||||
case invalidMedia
|
||||
}
|
||||
|
||||
|
||||
class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
let datacenterId: Int
|
||||
let isCdn: Bool
|
||||
let context: MTContext
|
||||
let mtProto: MTProto
|
||||
let requestService: MTRequestMessageService
|
||||
|
||||
private var shouldKeepConnectionDisposable: Disposable?
|
||||
|
||||
init(queue: Queue, datacenterId: Int, isMedia: Bool, isCdn: Bool, context: MTContext, masterDatacenterId: Int, usageInfo: MTNetworkUsageCalculationInfo?, shouldKeepConnection: Signal<Bool, NoError>) {
|
||||
self.datacenterId = datacenterId
|
||||
self.isCdn = isCdn
|
||||
self.context = context
|
||||
|
||||
self.mtProto = MTProto(context: self.context, datacenterId: datacenterId, usageCalculationInfo: usageInfo)
|
||||
self.mtProto.cdn = isCdn
|
||||
self.mtProto.useTempAuthKeys = self.context.useTempAuthKeys && !isCdn
|
||||
self.mtProto.media = isMedia
|
||||
if !isCdn && datacenterId != masterDatacenterId {
|
||||
self.mtProto.authTokenMasterDatacenterId = masterDatacenterId
|
||||
self.mtProto.requiredAuthToken = Int(datacenterId) as NSNumber
|
||||
}
|
||||
self.requestService = MTRequestMessageService(context: self.context)
|
||||
self.requestService.forceBackgroundRequests = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.requestService.delegate = self
|
||||
self.mtProto.add(self.requestService)
|
||||
|
||||
let mtProto = self.mtProto
|
||||
self.shouldKeepConnectionDisposable = (shouldKeepConnection |> distinctUntilChanged |> deliverOn(queue)).start(next: { [weak mtProto] value in
|
||||
if let mtProto = mtProto {
|
||||
if value {
|
||||
Logger.shared.log("Network", "Resume worker network connection")
|
||||
mtProto.resume()
|
||||
} else {
|
||||
Logger.shared.log("Network", "Pause worker network connection")
|
||||
mtProto.pause()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.mtProto.remove(self.requestService)
|
||||
self.mtProto.stop()
|
||||
self.mtProto.finalizeSession()
|
||||
self.shouldKeepConnectionDisposable?.dispose()
|
||||
}
|
||||
|
||||
func requestMessageServiceAuthorizationRequired(_ requestMessageService: MTRequestMessageService!) {
|
||||
self.context.updateAuthTokenForDatacenter(withId: self.datacenterId, authToken: nil)
|
||||
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
|
||||
}
|
||||
|
||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil) -> Signal<Void, UploadPartError> {
|
||||
return Signal<Void, MTRpcError> { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||
if asBigPart {
|
||||
let totalParts: Int32
|
||||
if let bigTotalParts = bigTotalParts {
|
||||
totalParts = Int32(bigTotalParts)
|
||||
} else {
|
||||
totalParts = -1
|
||||
}
|
||||
saveFilePart = Api.functions.upload.saveBigFilePart(fileId: fileId, filePart: Int32(index), fileTotalParts: totalParts, bytes: Buffer(data: data))
|
||||
} else {
|
||||
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
||||
}
|
||||
|
||||
request.setPayload(saveFilePart.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(saveFilePart.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(saveFilePart.0)), responseParser: { response in
|
||||
if let result = saveFilePart.2.parse(Buffer(data: response)) {
|
||||
return BoxedMessage(result)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.completed = { (boxedResponse, timestamp, error) -> () in
|
||||
if let error = error {
|
||||
subscriber.putError(error)
|
||||
} else {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
let internalId: Any! = request.internalId
|
||||
|
||||
self.requestService.add(request)
|
||||
|
||||
return ActionDisposable {
|
||||
self.requestService.removeRequest(byInternalId: internalId)
|
||||
}
|
||||
} |> `catch` { value -> Signal<Void, UploadPartError> in
|
||||
if value.errorCode == 400 {
|
||||
return .fail(.invalidMedia)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func webFilePart(location: Api.InputWebFileLocation, offset: Int, length: Int) -> Signal<Data, NoError> {
|
||||
return Signal<Data, MTRpcError> { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
var updatedLength = roundUp(length, to: 4096)
|
||||
while updatedLength % 4096 != 0 || 1048576 % updatedLength != 0 {
|
||||
updatedLength += 1
|
||||
}
|
||||
|
||||
let data = Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(updatedLength))
|
||||
|
||||
request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedFunctionDescription(data.0)), responseParser: { response in
|
||||
if let result = data.2.parse(Buffer(data: response)) {
|
||||
return BoxedMessage(result)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.completed = { (boxedResponse, timestamp, error) -> () in
|
||||
if let error = error {
|
||||
subscriber.putError(error)
|
||||
} else {
|
||||
if let result = (boxedResponse as! BoxedMessage).body as? Api.upload.WebFile {
|
||||
switch result {
|
||||
case .webFile(_, _, _, _, let bytes):
|
||||
subscriber.putNext(bytes.makeData())
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
else {
|
||||
subscriber.putError(MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let internalId: Any! = request.internalId
|
||||
|
||||
self.requestService.add(request)
|
||||
|
||||
return ActionDisposable {
|
||||
self.requestService.removeRequest(byInternalId: internalId)
|
||||
}
|
||||
} |> retryRequest
|
||||
}
|
||||
|
||||
func part(location: Api.InputFileLocation, offset: Int, length: Int) -> Signal<Data, NoError> {
|
||||
return Signal<Data, MTRpcError> { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
var updatedLength = roundUp(length, to: 4096)
|
||||
while updatedLength % 4096 != 0 || 1048576 % updatedLength != 0 {
|
||||
updatedLength += 1
|
||||
}
|
||||
|
||||
let data = Api.functions.upload.getFile(location: location, offset: Int32(offset), limit: Int32(updatedLength))
|
||||
|
||||
request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in
|
||||
if let result = data.2.parse(Buffer(data: response)) {
|
||||
return BoxedMessage(result)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.completed = { (boxedResponse, timestamp, error) -> () in
|
||||
if let error = error {
|
||||
subscriber.putError(error)
|
||||
} else {
|
||||
if let result = (boxedResponse as! BoxedMessage).body as? Api.upload.File {
|
||||
switch result {
|
||||
case let .file(_, _, bytes):
|
||||
subscriber.putNext(bytes.makeData())
|
||||
case .fileCdnRedirect:
|
||||
break
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
else {
|
||||
subscriber.putError(MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let internalId: Any! = request.internalId
|
||||
|
||||
self.requestService.add(request)
|
||||
|
||||
return ActionDisposable {
|
||||
self.requestService.removeRequest(byInternalId: internalId)
|
||||
}
|
||||
}
|
||||
|> retryRequest
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>)) -> Signal<T, MTRpcError> {
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in
|
||||
if let result = data.2.parse(Buffer(data: response)) {
|
||||
return BoxedMessage(result)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.completed = { (boxedResponse, timestamp, error) -> () in
|
||||
if let error = error {
|
||||
subscriber.putError(error)
|
||||
} else {
|
||||
if let result = (boxedResponse as! BoxedMessage).body as? T {
|
||||
subscriber.putNext(result)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
else {
|
||||
subscriber.putError(MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let internalId: Any! = request.internalId
|
||||
|
||||
self.requestService.add(request)
|
||||
|
||||
return ActionDisposable {
|
||||
self.requestService.removeRequest(byInternalId: internalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?)) -> Signal<Any, MTRpcError> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in
|
||||
if let result = data.2(Buffer(data: response)) {
|
||||
return BoxedMessage(result)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.completed = { (boxedResponse, timestamp, error) -> () in
|
||||
if let error = error {
|
||||
subscriber.putError(error)
|
||||
} else {
|
||||
subscriber.putNext((boxedResponse as! BoxedMessage).body)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
let internalId: Any! = request.internalId
|
||||
|
||||
requestService.add(request)
|
||||
|
||||
return ActionDisposable { [weak requestService] in
|
||||
requestService?.removeRequest(byInternalId: internalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public enum EarliestUnseenPersonalMentionMessageResult: Equatable {
|
||||
case loading
|
||||
case result(MessageId?)
|
||||
}
|
||||
|
||||
public func earliestUnseenPersonalMentionMessage(account: Account, peerId: PeerId) -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> {
|
||||
return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenPersonalMessage, additionalData: [.peerChatState(peerId)])
|
||||
|> mapToSignal { view -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> in
|
||||
if view.0.isLoading {
|
||||
return .single(.loading)
|
||||
}
|
||||
if let message = view.0.entries.first?.message {
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
var invalidatedPts: Int32?
|
||||
for data in view.0.additionalData {
|
||||
switch data {
|
||||
case let .peerChatState(_, state):
|
||||
if let state = state as? ChannelState {
|
||||
invalidatedPts = state.invalidatedPts
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if let invalidatedPts = invalidatedPts {
|
||||
var messagePts: Int32?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ChannelMessageStateVersionAttribute {
|
||||
messagePts = attribute.pts
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let messagePts = messagePts {
|
||||
if messagePts < invalidatedPts {
|
||||
return .single(.loading)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(.result(message.id))
|
||||
} else {
|
||||
return .single(.result(message.id))
|
||||
}
|
||||
} else {
|
||||
return .single(.result(nil))
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> take(until: { value in
|
||||
if case .result = value {
|
||||
return SignalTakeAction(passthrough: true, complete: true)
|
||||
} else {
|
||||
return SignalTakeAction(passthrough: true, complete: false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public class EditedMessageAttribute: MessageAttribute {
|
||||
public let date: Int32
|
||||
|
||||
init(date: Int32) {
|
||||
self.date = date
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.date = decoder.decodeInt32ForKey("d", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.date, forKey: "d")
|
||||
}
|
||||
}
|
||||
6
submodules/TelegramCore/TelegramCore/Either.swift
Normal file
6
submodules/TelegramCore/TelegramCore/Either.swift
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
public enum Either<Left, Right> {
|
||||
case left(value: Left)
|
||||
case right(value: Right)
|
||||
}
|
||||
188
submodules/TelegramCore/TelegramCore/EmojiKeywords.swift
Normal file
188
submodules/TelegramCore/TelegramCore/EmojiKeywords.swift
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
func emojiKeywordColletionIdForCode(_ code: String) -> ItemCollectionId {
|
||||
return ItemCollectionId(namespace: Namespaces.ItemCollection.EmojiKeywords, id: Int64(HashFunctions.murMurHash32(code)))
|
||||
}
|
||||
|
||||
public final class EmojiKeywordCollectionInfo: ItemCollectionInfo, Equatable {
|
||||
public let id: ItemCollectionId
|
||||
public let languageCode: String
|
||||
public let inputLanguageCode: String
|
||||
public let version: Int32
|
||||
public let timestamp: Int32
|
||||
|
||||
public init(languageCode: String, inputLanguageCode: String, version: Int32, timestamp: Int32) {
|
||||
self.id = emojiKeywordColletionIdForCode(inputLanguageCode)
|
||||
self.languageCode = languageCode
|
||||
self.inputLanguageCode = inputLanguageCode
|
||||
self.version = version
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = ItemCollectionId(namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt64ForKey("i.i", orElse: 0))
|
||||
self.languageCode = decoder.decodeStringForKey("lc", orElse: "")
|
||||
self.inputLanguageCode = decoder.decodeStringForKey("ilc", orElse: "")
|
||||
self.version = decoder.decodeInt32ForKey("v", orElse: 0)
|
||||
self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.id.namespace, forKey: "i.n")
|
||||
encoder.encodeInt64(self.id.id, forKey: "i.i")
|
||||
encoder.encodeString(self.languageCode, forKey: "lc")
|
||||
encoder.encodeString(self.inputLanguageCode, forKey: "ilc")
|
||||
encoder.encodeInt32(self.version, forKey: "v")
|
||||
encoder.encodeInt32(self.timestamp, forKey: "t")
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiKeywordCollectionInfo, rhs: EmojiKeywordCollectionInfo) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
return false
|
||||
}
|
||||
if lhs.languageCode != rhs.languageCode {
|
||||
return false
|
||||
}
|
||||
if lhs.inputLanguageCode != rhs.inputLanguageCode {
|
||||
return false
|
||||
}
|
||||
if lhs.version != rhs.version {
|
||||
return false
|
||||
}
|
||||
if lhs.timestamp != rhs.timestamp {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class EmojiKeywordItem: ItemCollectionItem, Equatable {
|
||||
public let index: ItemCollectionItemIndex
|
||||
public let collectionId: ItemCollectionId.Id
|
||||
public let keyword: String
|
||||
public let emoticons: [String]
|
||||
public let indexKeys: [MemoryBuffer]
|
||||
|
||||
public init(index: ItemCollectionItemIndex, collectionId: ItemCollectionId.Id, keyword: String, emoticons: [String], indexKeys: [MemoryBuffer]) {
|
||||
self.index = index
|
||||
self.collectionId = collectionId
|
||||
self.keyword = keyword
|
||||
self.emoticons = emoticons
|
||||
self.indexKeys = indexKeys
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.index = ItemCollectionItemIndex(index: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt64ForKey("i.i", orElse: 0))
|
||||
self.collectionId = decoder.decodeInt64ForKey("c", orElse: 0)
|
||||
self.keyword = decoder.decodeStringForKey("k", orElse: "")
|
||||
self.emoticons = decoder.decodeStringArrayForKey("e")
|
||||
self.indexKeys = decoder.decodeBytesArrayForKey("s")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.index.index, forKey: "i.n")
|
||||
encoder.encodeInt64(self.index.id, forKey: "i.i")
|
||||
encoder.encodeInt64(self.collectionId, forKey: "c")
|
||||
encoder.encodeString(self.keyword, forKey: "k")
|
||||
encoder.encodeStringArray(self.emoticons, forKey: "e")
|
||||
encoder.encodeBytesArray(self.indexKeys, forKey: "s")
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiKeywordItem, rhs: EmojiKeywordItem) -> Bool {
|
||||
return lhs.index == rhs.index && lhs.collectionId == rhs.collectionId && lhs.keyword == rhs.keyword && lhs.emoticons == rhs.emoticons && lhs.indexKeys == rhs.indexKeys
|
||||
}
|
||||
}
|
||||
|
||||
private let refreshTimeout: Int32 = 60 * 60
|
||||
|
||||
private enum SearchEmojiKeywordsIntermediateResult {
|
||||
case updating(timestamp: Int32?)
|
||||
case completed([EmojiKeywordItem])
|
||||
}
|
||||
|
||||
public func searchEmojiKeywords(postbox: Postbox, inputLanguageCode: String, query: String, completeMatch: Bool) -> Signal<[EmojiKeywordItem], NoError> {
|
||||
guard !query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
return .single([])
|
||||
}
|
||||
let collectionId = emojiKeywordColletionIdForCode(inputLanguageCode)
|
||||
|
||||
let search: (Transaction) -> [EmojiKeywordItem] = { transaction in
|
||||
let queryTokens = stringIndexTokens(query, transliteration: .none)
|
||||
if let firstQueryToken = queryTokens.first {
|
||||
let query: ItemCollectionSearchQuery = completeMatch ? .exact(firstQueryToken) : .matching(queryTokens)
|
||||
let items = transaction.searchItemCollection(namespace: Namespaces.ItemCollection.EmojiKeywords, query: query).filter { item -> Bool in
|
||||
if let item = item as? EmojiKeywordItem, item.collectionId == collectionId.id {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} as? [EmojiKeywordItem]
|
||||
|
||||
if let items = items {
|
||||
return items.sorted(by: { lhs, rhs -> Bool in
|
||||
if lhs.keyword.count == rhs.keyword.count {
|
||||
return lhs.keyword < rhs.keyword
|
||||
} else {
|
||||
return lhs.keyword.count < rhs.keyword.count
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> Signal<SearchEmojiKeywordsIntermediateResult, NoError> in
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent())
|
||||
let info = transaction.getItemCollectionInfo(collectionId: collectionId)
|
||||
if let info = info as? EmojiKeywordCollectionInfo {
|
||||
if info.timestamp + refreshTimeout < currentTime {
|
||||
addSynchronizeEmojiKeywordsOperation(transaction: transaction, inputLanguageCode: inputLanguageCode, languageCode: info.languageCode, fromVersion: info.version)
|
||||
return .single(.updating(timestamp: info.timestamp))
|
||||
} else {
|
||||
return .single(.completed(search(transaction)))
|
||||
}
|
||||
} else {
|
||||
addSynchronizeEmojiKeywordsOperation(transaction: transaction, inputLanguageCode: inputLanguageCode, languageCode: nil, fromVersion: nil)
|
||||
return .single(.updating(timestamp: nil))
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapToSignal { intermediateResult -> Signal<[EmojiKeywordItem], NoError> in
|
||||
switch intermediateResult {
|
||||
case let .updating(timestamp):
|
||||
return postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.EmojiKeywords], aroundIndex: nil, count: 10)
|
||||
|> filter { view -> Bool in
|
||||
for info in view.collectionInfos {
|
||||
if let info = info.1 as? EmojiKeywordCollectionInfo, info.id == collectionId {
|
||||
if let timestamp = timestamp {
|
||||
return timestamp < info.timestamp
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { view -> Signal<[EmojiKeywordItem], NoError> in
|
||||
for info in view.collectionInfos {
|
||||
if let info = info.1 as? EmojiKeywordCollectionInfo, info.id == collectionId {
|
||||
return postbox.transaction { transaction -> [EmojiKeywordItem] in
|
||||
return search(transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
case let .completed(items):
|
||||
return .single(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import Foundation
|
||||
|
||||
public protocol EncryptedMediaResource {
|
||||
func decrypt(data: Data, params: Any) -> Data?
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue