Add 'submodules/TelegramCore/' from commit '9561227540'

git-subtree-dir: submodules/TelegramCore
git-subtree-mainline: 971273e8f8
git-subtree-split: 9561227540
This commit is contained in:
Peter 2019-06-11 18:59:08 +01:00
commit 5c1613d104
419 changed files with 128415 additions and 0 deletions

25
submodules/TelegramCore/.gitignore vendored Normal file
View 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/*

View 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',
],
)

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

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

View file

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

View 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

File diff suppressed because it is too large Load diff

View 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)))
}
}
}
}
}
}*/

File diff suppressed because it is too large Load diff

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

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

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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

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

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

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

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

View file

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

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

View file

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

View file

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

View file

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

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

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

View file

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

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

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

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

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

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

View file

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

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

View file

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

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

View file

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

View file

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

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

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

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

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

View file

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

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

View file

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

View file

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

View file

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

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

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

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,2 @@
SWIFT_INCLUDE_PATHS = $(SRCROOT)/TelegramCore
MODULEMAP_PRIVATE_FILE = $(SRCROOT)/TelegramCore/module.private.modulemap

View file

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

View file

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

View file

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

View file

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

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

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

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

View file

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

View file

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

View file

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

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

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

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

View 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

View 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;
}

View file

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

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

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
import Foundation
public enum Either<Left, Right> {
case left(value: Left)
case right(value: Right)
}

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

View file

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