Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,393 @@
import Foundation
import Postbox
import SwiftSignalKit
import MtProtoKit
public struct ActiveSessionsContextState: Equatable {
public var isLoadingMore: Bool
public var sessions: [RecentAccountSession]
public var ttlDays: Int32
}
private final class ActiveSessionsContextImpl {
private let account: Account
private var _state: ActiveSessionsContextState {
didSet {
if self._state != oldValue {
self._statePromise.set(.single(self._state))
}
}
}
private let _statePromise = Promise<ActiveSessionsContextState>()
var state: Signal<ActiveSessionsContextState, NoError> {
return self._statePromise.get()
}
private let disposable = MetaDisposable()
private var authorizationListUpdatesDisposable: Disposable?
init(account: Account) {
assert(Queue.mainQueue().isCurrent())
self.account = account
self._state = ActiveSessionsContextState(isLoadingMore: false, sessions: [], ttlDays: 1)
self._statePromise.set(.single(self._state))
self.loadMore()
self.authorizationListUpdatesDisposable = (account.stateManager.authorizationListUpdates
|> deliverOnMainQueue).start(next: { [weak self] _ in
self?.loadMore()
})
}
deinit {
assert(Queue.mainQueue().isCurrent())
self.disposable.dispose()
self.authorizationListUpdatesDisposable?.dispose()
}
func loadMore() {
assert(Queue.mainQueue().isCurrent())
if self._state.isLoadingMore {
return
}
self._state = ActiveSessionsContextState(isLoadingMore: true, sessions: self._state.sessions, ttlDays: self._state.ttlDays)
self.disposable.set((requestRecentAccountSessions(account: self.account)
|> map { result -> (sessions: [RecentAccountSession], ttlDays: Int32, canLoadMore: Bool) in
return (result.0, result.1, false)
}
|> deliverOnMainQueue).start(next: { [weak self] (sessions, ttlDays, canLoadMore) in
guard let strongSelf = self else {
return
}
strongSelf._state = ActiveSessionsContextState(isLoadingMore: false, sessions: sessions, ttlDays: ttlDays)
}))
}
func addSession(_ session: RecentAccountSession) {
var mergedSessions = self._state.sessions
var found = false
for i in 0 ..< mergedSessions.count {
if mergedSessions[i].hash == session.hash {
found = true
break
}
}
if !found {
mergedSessions.insert(session, at: 0)
}
self._state = ActiveSessionsContextState(isLoadingMore: self._state.isLoadingMore, sessions: mergedSessions, ttlDays: self._state.ttlDays)
}
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, ttlDays: strongSelf._state.ttlDays)
return .complete()
}
}
func removeOther() -> Signal<Never, TerminateSessionError> {
return terminateOtherAccountSessions(account: self.account)
|> deliverOnMainQueue
|> mapToSignal { [weak self] _ -> Signal<Never, TerminateSessionError> in
guard let strongSelf = self else {
return .complete()
}
let mergedSessions = strongSelf._state.sessions.filter({ $0.hash == 0 })
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions, ttlDays: strongSelf._state.ttlDays)
return .complete()
}
}
func updateSessionAcceptsSecretChats(_ session: RecentAccountSession, accepts: Bool) -> Signal<Never, UpdateSessionError> {
var mergedSessions = self._state.sessions
for i in 0 ..< mergedSessions.count {
if mergedSessions[i].hash == session.hash {
let updatedSession = mergedSessions[i].withUpdatedAcceptsSecretChats(accepts)
mergedSessions.remove(at: i)
mergedSessions.insert(updatedSession, at: i)
break
}
}
self._state = ActiveSessionsContextState(isLoadingMore: self._state.isLoadingMore, sessions: mergedSessions, ttlDays: self._state.ttlDays)
return updateAccountSessionAcceptsSecretChats(account: self.account, hash: session.hash, accepts: accepts)
|> deliverOnMainQueue
|> mapToSignal { [weak self] _ -> Signal<Never, UpdateSessionError> in
if let strongSelf = self {
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions, ttlDays: strongSelf._state.ttlDays)
}
return .complete()
}
}
func updateSessionAcceptsIncomingCalls(_ session: RecentAccountSession, accepts: Bool) -> Signal<Never, UpdateSessionError> {
var mergedSessions = self._state.sessions
for i in 0 ..< mergedSessions.count {
if mergedSessions[i].hash == session.hash {
let updatedSession = mergedSessions[i].withUpdatedAcceptsIncomingCalls(accepts)
mergedSessions.remove(at: i)
mergedSessions.insert(updatedSession, at: i)
break
}
}
self._state = ActiveSessionsContextState(isLoadingMore: self._state.isLoadingMore, sessions: mergedSessions, ttlDays: self._state.ttlDays)
return updateAccountSessionAcceptsIncomingCalls(account: self.account, hash: session.hash, accepts: accepts)
|> deliverOnMainQueue
|> mapToSignal { [weak self] _ -> Signal<Never, UpdateSessionError> in
if let strongSelf = self {
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions, ttlDays: strongSelf._state.ttlDays)
}
return .complete()
}
}
func updateAuthorizationTTL(days: Int32) -> Signal<Never, UpadteAuthorizationTTLError> {
self._state = ActiveSessionsContextState(isLoadingMore: self._state.isLoadingMore, sessions: self._state.sessions, ttlDays: days)
return setAuthorizationTTL(account: self.account, ttl: days)
|> deliverOnMainQueue
|> mapToSignal { [weak self] _ -> Signal<Never, UpadteAuthorizationTTLError> in
if let strongSelf = self {
strongSelf._state = ActiveSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: strongSelf._state.sessions, ttlDays: days)
}
return .complete()
}
}
}
public final class ActiveSessionsContext {
private let impl: QueueLocalObject<ActiveSessionsContextImpl>
public var state: Signal<ActiveSessionsContextState, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.state.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(account: Account) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return ActiveSessionsContextImpl(account: account)
})
}
public func loadMore() {
self.impl.with { impl in
impl.loadMore()
}
}
func addSession(_ session: RecentAccountSession) {
self.impl.with { impl in
impl.addSession(session)
}
}
public func remove(hash: Int64) -> Signal<Never, TerminateSessionError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.remove(hash: hash).start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
}
}
public func removeOther() -> Signal<Never, TerminateSessionError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.removeOther().start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
}
}
public func updateSessionAcceptsSecretChats(_ session: RecentAccountSession, accepts: Bool) -> Signal<Never, UpdateSessionError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.updateSessionAcceptsSecretChats(session, accepts: accepts).start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
}
}
public func updateSessionAcceptsIncomingCalls(_ session: RecentAccountSession, accepts: Bool) -> Signal<Never, UpdateSessionError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.updateSessionAcceptsIncomingCalls(session, accepts: accepts).start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
}
}
public func updateAuthorizationTTL(days: Int32) -> Signal<Never, UpadteAuthorizationTTLError> {
let days = max(1, min(365, days))
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.updateAuthorizationTTL(days: days).start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
}
}
}
public struct WebSessionsContextState: Equatable {
public var isLoadingMore: Bool
public var sessions: [WebAuthorization]
public var peers: [PeerId: Peer]
public static func ==(lhs: WebSessionsContextState, rhs: WebSessionsContextState) -> Bool {
if lhs.isLoadingMore != rhs.isLoadingMore {
return false
}
if lhs.sessions != rhs.sessions {
return false
}
if !arePeerDictionariesEqual(lhs.peers, rhs.peers) {
return false
}
return true
}
}
public final class WebSessionsContext {
private let account: Account
private var _state: WebSessionsContextState {
didSet {
if self._state != oldValue {
self._statePromise.set(.single(self._state))
}
}
}
private let _statePromise = Promise<WebSessionsContextState>()
public var state: Signal<WebSessionsContextState, NoError> {
return self._statePromise.get()
}
private let disposable = MetaDisposable()
init(account: Account) {
assert(Queue.mainQueue().isCurrent())
self.account = account
self._state = WebSessionsContextState(isLoadingMore: false, sessions: [], 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 {
return
}
self._state = WebSessionsContextState(isLoadingMore: true, sessions: self._state.sessions, peers: self._state.peers)
self.disposable.set((webSessions(network: account.network)
|> map { result -> (sessions: [WebAuthorization], peers: [PeerId: Peer], canLoadMore: Bool) in
return (result.0, result.1, false)
}
|> deliverOnMainQueue).start(next: { [weak self] (sessions, peers, canLoadMore) in
guard let strongSelf = self else {
return
}
strongSelf._state = WebSessionsContextState(isLoadingMore: false, sessions: sessions, peers: peers)
}))
}
public func remove(hash: Int64) -> Signal<Never, NoError> {
assert(Queue.mainQueue().isCurrent())
return terminateWebSession(network: self.account.network, hash: hash)
|> 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 {
if mergedSessions[i].hash == hash {
mergedSessions.remove(at: i)
break
}
}
strongSelf._state = WebSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: mergedSessions, peers: strongSelf._state.peers)
return .complete()
}
}
public func removeAll() -> Signal<Never, NoError> {
return terminateAllWebSessions(network: self.account.network)
|> deliverOnMainQueue
|> mapToSignal { [weak self] _ -> Signal<Never, NoError> in
guard let strongSelf = self else {
return .complete()
}
strongSelf._state = WebSessionsContextState(isLoadingMore: strongSelf._state.isLoadingMore, sessions: [], peers: [:])
return .complete()
}
}
}
@@ -0,0 +1,82 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_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 inputPeer = apiInputPeer(peer) {
let signal: Signal<Api.Bool, MTRpcError>
if isBlocked {
signal = account.network.request(Api.functions.contacts.block(flags: 0, id: inputPeer))
} else {
signal = account.network.request(Api.functions.contacts.unblock(flags: 0, id: inputPeer))
}
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
}
func _internal_requestUpdatePeerIsBlockedFromStories(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 inputPeer = apiInputPeer(peer) {
let flags: Int32 = 1 << 0
let signal: Signal<Api.Bool, MTRpcError>
if isBlocked {
signal = account.network.request(Api.functions.contacts.block(flags: flags, id: inputPeer))
} else {
signal = account.network.request(Api.functions.contacts.unblock(flags: flags, id: inputPeer))
}
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()
}
var userFlags = previous.flags
if isBlocked {
userFlags.insert(.isBlockedFromStories)
} else {
userFlags.remove(.isBlockedFromStories)
}
return previous.withUpdatedFlags(userFlags)
})
}
}
}
} else {
return .complete()
}
} |> switchToLatest
}
@@ -0,0 +1,378 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
import MtProtoKit
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 {
public enum Subject {
case blocked
case stories
}
private let account: Account
private let subject: Subject
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, subject: Subject) {
assert(Queue.mainQueue().isCurrent())
self.account = account
self.subject = subject
self._state = BlockedPeersContextState(isLoadingMore: false, canLoadMore: true, totalCount: nil, peers: [])
self._statePromise.set(.single(self._state))
self.loadMore()
}
deinit {
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
let accountPeerId = self.account.peerId
var flags: Int32 = 0
if case .stories = self.subject {
flags |= 1 << 0
}
var limit: Int32 = 200
if self._state.peers.count > 0 {
limit = 100
}
self.disposable.set((self.account.network.request(Api.functions.contacts.getBlocked(flags: flags, offset: Int32(self._state.peers.count), limit: limit))
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<(peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?), NoError> in
guard let result else {
return .single((peers: [], canLoadMore: false, totalCount: 0))
}
return postbox.transaction { transaction -> (peers: [RenderedPeer], canLoadMore: Bool, totalCount: Int?) in
switch result {
case let .blocked(blocked, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var renderedPeers: [RenderedPeer] = []
for blockedPeer in blocked {
switch blockedPeer {
case let .peerBlocked(peerId, _):
if let peer = transaction.getPeer(peerId.peerId) {
renderedPeers.append(RenderedPeer(peer: peer))
}
}
}
return (renderedPeers, false, nil)
case let .blockedSlice(count, blocked, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var renderedPeers: [RenderedPeer] = []
for blockedPeer in blocked {
switch blockedPeer {
case let .peerBlocked(peerId, _):
if let peer = transaction.getPeer(peerId.peerId) {
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 updatePeerIds(_ peerIds: [EnginePeer.Id]) -> Signal<Never, BlockedPeersContextAddError> {
assert(Queue.mainQueue().isCurrent())
let network = self.account.network
let subject = self.subject
let currentPeers = self._state.peers
var flags: Int32 = 0
if case .stories = self.subject {
flags |= 1 << 0
}
return self.account.postbox.transaction { transaction -> [Peer] in
var peers: [Peer] = []
var removedPeerIds = Set<EnginePeer.Id>()
var validPeerIds = Set<EnginePeer.Id>()
var allPeerIds = Set<EnginePeer.Id>()
for peerId in peerIds {
if let peer = transaction.getPeer(peerId) {
peers.append(peer)
}
validPeerIds.insert(peerId)
allPeerIds.insert(peerId)
}
for peer in currentPeers {
if !validPeerIds.contains(peer.peerId) {
removedPeerIds.insert(peer.peerId)
allPeerIds.insert(peer.peerId)
}
}
transaction.updatePeerCachedData(peerIds: allPeerIds, update: { peerId, current in
let previous: CachedUserData
if let current = current as? CachedUserData {
previous = current
} else {
previous = CachedUserData()
}
if case .stories = subject {
var userFlags = previous.flags
if validPeerIds.contains(peerId) {
userFlags.insert(.isBlockedFromStories)
} else if removedPeerIds.contains(peerId) {
userFlags.remove(.isBlockedFromStories)
}
return previous.withUpdatedFlags(userFlags)
} else {
if validPeerIds.contains(peerId) {
return previous.withUpdatedIsBlocked(true)
} else if removedPeerIds.contains(peerId) {
return previous.withUpdatedIsBlocked(false)
} else {
return previous
}
}
})
return peers
}
|> castError(BlockedPeersContextAddError.self)
|> mapToSignal { [weak self] peers -> Signal<Never, BlockedPeersContextAddError> in
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf._state = BlockedPeersContextState(isLoadingMore: strongSelf._state.isLoadingMore, canLoadMore: strongSelf._state.canLoadMore, totalCount: peers.count, peers: peers.map(RenderedPeer.init))
}
}
let inputPeers = peers.compactMap { apiInputPeer($0) }
return network.request(Api.functions.contacts.setBlocked(flags: flags, id: inputPeers, limit: Int32(max(currentPeers.count, peers.count))))
|> mapError { _ -> BlockedPeersContextAddError in
return .generic
}
|> mapToSignal { _ -> Signal<Never, BlockedPeersContextAddError> in
return .complete()
}
}
}
public func add(peerId: PeerId) -> Signal<Never, BlockedPeersContextAddError> {
assert(Queue.mainQueue().isCurrent())
let postbox = self.account.postbox
let network = self.account.network
let subject = self.subject
var flags: Int32 = 0
if case .stories = self.subject {
flags |= 1 << 0
}
return self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> castError(BlockedPeersContextAddError.self)
|> mapToSignal { [weak self] inputPeer -> Signal<Never, BlockedPeersContextAddError> in
guard let inputPeer = inputPeer else {
return .fail(.generic)
}
return network.request(Api.functions.contacts.block(flags: flags, id: inputPeer))
|> mapError { _ -> BlockedPeersContextAddError in
return .generic
}
|> mapToSignal { _ -> Signal<Peer?, BlockedPeersContextAddError> in
return postbox.transaction { transaction -> Peer? in
if peerId.namespace == Namespaces.Peer.CloudUser {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
let previous: CachedUserData
if let current = current as? CachedUserData {
previous = current
} else {
previous = CachedUserData()
}
if case .stories = subject {
var userFlags = previous.flags
userFlags.insert(.isBlockedFromStories)
return previous.withUpdatedFlags(userFlags)
} else {
return previous.withUpdatedIsBlocked(true)
}
})
}
return transaction.getPeer(peerId)
}
|> castError(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 postbox = self.account.postbox
let network = self.account.network
let subject = self.subject
var flags: Int32 = 0
if case .stories = self.subject {
flags |= 1 << 0
}
return self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> castError(BlockedPeersContextRemoveError.self)
|> mapToSignal { [weak self] inputPeer -> Signal<Never, BlockedPeersContextRemoveError> in
guard let inputPeer = inputPeer else {
return .fail(.generic)
}
return network.request(Api.functions.contacts.unblock(flags: flags, id: inputPeer))
|> mapError { _ -> BlockedPeersContextRemoveError in
return .generic
}
|> mapToSignal { value in
return postbox.transaction { transaction -> Peer? in
if peerId.namespace == Namespaces.Peer.CloudUser {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
let previous: CachedUserData
if let current = current as? CachedUserData {
previous = current
} else {
previous = CachedUserData()
}
if case .stories = subject {
var userFlags = previous.flags
userFlags.remove(.isBlockedFromStories)
return previous.withUpdatedFlags(userFlags)
} else {
return previous.withUpdatedIsBlocked(false)
}
})
}
return transaction.getPeer(peerId)
}
|> castError(BlockedPeersContextRemoveError.self)
}
|> 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()
}
}
}
}
@@ -0,0 +1,36 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public func _internal_updateCloseFriends(account: Account, peerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
let ids: [Int64] = peerIds.map { $0.id._internalGetInt64Value() }
return account.network.request(Api.functions.contacts.editCloseFriends(id: ids))
|> retryRequest
|> mapToSignal { result -> Signal<Void, NoError> in
return account.postbox.transaction { transaction in
let contactPeerIds = transaction.getContactPeerIds()
var updatedPeers: [Peer] = []
for peerId in contactPeerIds {
if let peer = transaction.getPeer(peerId) as? TelegramUser {
if peerIds.contains(peerId) {
var updatedFlags = peer.flags
updatedFlags.insert(.isCloseFriend)
let updatedPeer = peer.withUpdatedFlags(updatedFlags)
updatedPeers.append(updatedPeer)
} else if peer.flags.contains(.isCloseFriend) {
var updatedFlags = peer.flags
updatedFlags.remove(.isCloseFriend)
let updatedPeer = peer.withUpdatedFlags(updatedFlags)
updatedPeers.append(updatedPeer)
}
}
}
updatePeersCustom(transaction: transaction, peers: updatedPeers, update: { _, updated in
return updated
})
}
}
|> ignoreValues
}
@@ -0,0 +1,124 @@
import Foundation
import TelegramApi
public struct AccountSessionFlags: OptionSet {
public var rawValue: Int32
public init() {
self.rawValue = 0
}
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let isOfficial = AccountSessionFlags(rawValue: (1 << 1))
public static let passwordPending = AccountSessionFlags(rawValue: (1 << 2))
public static let acceptsSecretChats = AccountSessionFlags(rawValue: (1 << 3))
public static let acceptsIncomingCalls = AccountSessionFlags(rawValue: (1 << 4))
}
public struct RecentAccountSession: Equatable {
public let hash: Int64
public let deviceModel: String
public let platform: String
public let systemVersion: String
public let apiId: Int32
public let appName: String
public let appVersion: String
public let creationDate: Int32
public let activityDate: Int32
public let ip: String
public let country: String
public let region: String
public let flags: AccountSessionFlags
public var isCurrent: Bool {
return self.hash == 0
}
public static func ==(lhs: RecentAccountSession, rhs: RecentAccountSession) -> Bool {
if lhs.hash != rhs.hash {
return false
}
if lhs.deviceModel != rhs.deviceModel {
return false
}
if lhs.platform != rhs.platform {
return false
}
if lhs.systemVersion != rhs.systemVersion {
return false
}
if lhs.apiId != rhs.apiId {
return false
}
if lhs.appName != rhs.appName {
return false
}
if lhs.appVersion != rhs.appVersion {
return false
}
if lhs.creationDate != rhs.creationDate {
return false
}
if lhs.activityDate != rhs.activityDate {
return false
}
if lhs.ip != rhs.ip {
return false
}
if lhs.country != rhs.country {
return false
}
if lhs.region != rhs.region {
return false
}
if lhs.flags != rhs.flags {
return false
}
return true
}
func withUpdatedAcceptsSecretChats(_ accepts: Bool) -> RecentAccountSession {
var flags = self.flags
if accepts {
flags.insert(.acceptsSecretChats)
} else {
flags.remove(.acceptsSecretChats)
}
return RecentAccountSession(hash: self.hash, deviceModel: self.deviceModel, platform: self.platform, systemVersion: self.systemVersion, apiId: self.apiId, appName: self.appName, appVersion: self.appVersion, creationDate: self.creationDate, activityDate: self.activityDate, ip: self.ip, country: self.country, region: self.region, flags: flags)
}
func withUpdatedAcceptsIncomingCalls(_ accepts: Bool) -> RecentAccountSession {
var flags = self.flags
if accepts {
flags.insert(.acceptsIncomingCalls)
} else {
flags.remove(.acceptsIncomingCalls)
}
return RecentAccountSession(hash: self.hash, deviceModel: self.deviceModel, platform: self.platform, systemVersion: self.systemVersion, apiId: self.apiId, appName: self.appName, appVersion: self.appVersion, creationDate: self.creationDate, activityDate: self.activityDate, ip: self.ip, country: self.country, region: self.region, flags: flags)
}
}
extension RecentAccountSession {
init(apiAuthorization: Api.Authorization) {
switch apiAuthorization {
case let .authorization(flags, hash, deviceModel, platform, systemVersion, apiId, appName, appVersion, dateCreated, dateActive, ip, country, region):
var accountSessionFlags: AccountSessionFlags = []
if (flags & (1 << 1)) != 0 {
accountSessionFlags.insert(.isOfficial)
}
if (flags & (1 << 2)) != 0 {
accountSessionFlags.insert(.passwordPending)
}
if (flags & (1 << 3)) == 0 {
accountSessionFlags.insert(.acceptsSecretChats)
}
if (flags & (1 << 4)) == 0 {
accountSessionFlags.insert(.acceptsIncomingCalls)
}
self.init(hash: hash, deviceModel: deviceModel, platform: platform, systemVersion: systemVersion, apiId: apiId, appName: appName, appVersion: appVersion, creationDate: dateCreated, activityDate: dateActive, ip: ip, country: country, region: region, flags: accountSessionFlags)
}
}
}
@@ -0,0 +1,93 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
func requestRecentAccountSessions(account: Account) -> Signal<([RecentAccountSession], Int32), NoError> {
return account.network.request(Api.functions.account.getAuthorizations())
|> retryRequestIfNotFrozen
|> map { result -> ([RecentAccountSession], Int32) in
guard let result else {
return ([], 1)
}
var sessions: [RecentAccountSession] = []
var ttlDays: Int32 = 1
switch result {
case let .authorizations(authorizationTtlDays, authorizations):
for authorization in authorizations {
sessions.append(RecentAccountSession(apiAuthorization: authorization))
}
ttlDays = authorizationTtlDays
}
return (sessions, ttlDays)
}
}
public enum TerminateSessionError {
case generic
case freshReset
}
func terminateAccountSession(account: Account, hash: Int64) -> Signal<Void, TerminateSessionError> {
return account.network.request(Api.functions.account.resetAuthorization(hash: hash))
|> mapError { error -> TerminateSessionError in
if error.errorCode == 406 {
return .freshReset
}
return .generic
}
|> mapToSignal { _ -> Signal<Void, TerminateSessionError> in
return .single(Void())
}
}
func terminateOtherAccountSessions(account: Account) -> Signal<Void, TerminateSessionError> {
return account.network.request(Api.functions.auth.resetAuthorizations())
|> mapError { error -> TerminateSessionError in
if error.errorCode == 406 {
return .freshReset
}
return .generic
}
|> mapToSignal { _ -> Signal<Void, TerminateSessionError> in
return .single(Void())
}
}
public enum UpadteAuthorizationTTLError {
case generic
}
func setAuthorizationTTL(account: Account, ttl: Int32) -> Signal<Void, UpadteAuthorizationTTLError> {
return account.network.request(Api.functions.account.setAuthorizationTTL(authorizationTtlDays: ttl))
|> mapError { error -> UpadteAuthorizationTTLError in
return .generic
}
|> mapToSignal { _ -> Signal<Void, UpadteAuthorizationTTLError> in
return .single(Void())
}
}
public enum UpdateSessionError {
case generic
}
func updateAccountSessionAcceptsSecretChats(account: Account, hash: Int64, accepts: Bool) -> Signal<Void, UpdateSessionError> {
return account.network.request(Api.functions.account.changeAuthorizationSettings(flags: 1 << 0, hash: hash, encryptedRequestsDisabled: accepts ? .boolFalse : .boolTrue, callRequestsDisabled: nil))
|> mapError { error -> UpdateSessionError in
return .generic
}
|> mapToSignal { _ -> Signal<Void, UpdateSessionError> in
return .single(Void())
}
}
func updateAccountSessionAcceptsIncomingCalls(account: Account, hash: Int64, accepts: Bool) -> Signal<Void, UpdateSessionError> {
return account.network.request(Api.functions.account.changeAuthorizationSettings(flags: 1 << 1, hash: hash, encryptedRequestsDisabled: nil, callRequestsDisabled: accepts ? .boolFalse : .boolTrue))
|> mapError { error -> UpdateSessionError in
return .generic
}
|> mapToSignal { _ -> Signal<Void, UpdateSessionError> in
return .single(Void())
}
}
@@ -0,0 +1,71 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
public struct WebAuthorization : Equatable {
public let hash: Int64
public let botId: PeerId
public let domain: String
public let browser: String
public let platform: String
public let dateCreated: Int32
public let dateActive: Int32
public let ip: String
public let region: String
public static func ==(lhs: WebAuthorization, rhs: WebAuthorization) -> Bool {
return lhs.hash == rhs.hash && lhs.botId == rhs.botId && lhs.domain == rhs.domain && lhs.browser == rhs.browser && lhs.platform == rhs.platform && lhs.dateActive == rhs.dateActive && lhs.dateCreated == rhs.dateCreated && lhs.ip == rhs.ip && lhs.region == rhs.region
}
}
func webSessions(network: Network) -> Signal<([WebAuthorization], [PeerId: Peer]), NoError> {
return network.request(Api.functions.account.getWebAuthorizations())
|> retryRequestIfNotFrozen
|> map { result -> ([WebAuthorization], [PeerId : Peer]) in
guard let result else {
return ([], [:])
}
var sessions: [WebAuthorization] = []
var peers:[PeerId : Peer] = [:]
switch result {
case let .webAuthorizations(authorizations, users):
for authorization in authorizations {
switch authorization {
case let .webAuthorization(hash, botId, domain, browser, platform, dateCreated, dateActive, ip, region):
sessions.append(WebAuthorization(hash: hash, botId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), domain: domain, browser: browser, platform: platform, dateCreated: dateCreated, dateActive: dateActive, ip: ip, region: region))
}
}
for user in users {
let peer = TelegramUser(user: user)
peers[peer.id] = peer
}
}
return (sessions, peers)
}
}
func terminateWebSession(network: Network, hash: Int64) -> Signal<Bool, NoError> {
return network.request(Api.functions.account.resetWebAuthorization(hash: hash))
|> retryRequest
|> map { result in
switch result {
case .boolFalse:
return false
case .boolTrue:
return true
}
}
}
func terminateAllWebSessions(network: Network) -> Signal<Void, NoError> {
return network.request(Api.functions.account.resetWebAuthorizations())
|> retryRequest
|> map { _ in }
}
@@ -0,0 +1,92 @@
import SwiftSignalKit
import Postbox
public extension TelegramEngine {
final class Privacy {
private let account: Account
init(account: Account) {
self.account = account
}
public func requestUpdatePeerIsBlocked(peerId: PeerId, isBlocked: Bool) -> Signal<Void, NoError> {
return _internal_requestUpdatePeerIsBlocked(account: self.account, peerId: peerId, isBlocked: isBlocked)
}
public func requestUpdatePeerIsBlockedFromStories(peerId: PeerId, isBlocked: Bool) -> Signal<Void, NoError> {
return _internal_requestUpdatePeerIsBlockedFromStories(account: self.account, peerId: peerId, isBlocked: isBlocked)
}
public func activeSessions() -> ActiveSessionsContext {
return ActiveSessionsContext(account: self.account)
}
public func webSessions() -> WebSessionsContext {
return WebSessionsContext(account: self.account)
}
public func requestAccountPrivacySettings() -> Signal<AccountPrivacySettings, NoError> {
return _internal_requestAccountPrivacySettings(account: self.account)
}
public func updateGlobalPrivacySettings() -> Signal<Never, NoError> {
return _internal_updateGlobalPrivacySettings(account: self.account)
}
public func updateAccountAutoArchiveChats(value: Bool) -> Signal<Never, NoError> {
return _internal_updateAccountAutoArchiveChats(account: self.account, value: value)
}
public func updateNonContactChatsPrivacy(value: GlobalPrivacySettings.NonContactChatsPrivacy) -> Signal<Never, NoError> {
return _internal_updateNonContactChatsPrivacy(account: self.account, value: value)
}
public func updateAccountKeepArchivedFolders(value: Bool) -> Signal<Never, NoError> {
return _internal_updateAccountKeepArchivedFolders(account: self.account, value: value)
}
public func updateAccountKeepArchivedUnmuted(value: Bool) -> Signal<Never, NoError> {
return _internal_updateAccountKeepArchivedUnmuted(account: self.account, value: value)
}
public func updateGlobalPrivacySettings(settings: GlobalPrivacySettings) -> Signal<Never, NoError> {
return _internal_updateGlobalPrivacySettings(account: self.account, settings: settings)
}
public func updateAccountRemovalTimeout(timeout: Int32) -> Signal<Void, NoError> {
return _internal_updateAccountRemovalTimeout(account: self.account, timeout: timeout)
}
public func updateGlobalMessageRemovalTimeout(timeout: Int32?) -> Signal<Void, NoError> {
return _internal_updateMessageRemovalTimeout(account: self.account, timeout: timeout)
}
public func updatePhoneNumberDiscovery(value: Bool) -> Signal<Void, NoError> {
return _internal_updatePhoneNumberDiscovery(account: self.account, value: value)
}
public func updateSelectiveAccountPrivacySettings(type: UpdateSelectiveAccountPrivacySettingsType, settings: SelectivePrivacySettings) -> Signal<Void, NoError> {
return _internal_updateSelectiveAccountPrivacySettings(account: self.account, type: type, settings: settings)
}
public func updateCloseFriends(peerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
return _internal_updateCloseFriends(account: self.account, peerIds: peerIds)
}
public func cleanupSessionReviews() -> Signal<Never, NoError> {
return _internal_cleanupSessionReviews(account: self.account)
}
public func confirmNewSessionReview(id: Int64) -> Signal<Never, NoError> {
let _ = removeNewSessionReviews(postbox: self.account.postbox, ids: [id]).start()
return _internal_confirmNewSessionReview(account: self.account, id: id)
}
public func terminateAnotherSession(id: Int64) -> Signal<Never, TerminateSessionError> {
let _ = removeNewSessionReviews(postbox: self.account.postbox, ids: [id]).start()
return terminateAccountSession(account: self.account, hash: id)
|> ignoreValues
}
}
}
@@ -0,0 +1,592 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
func _internal_updateGlobalPrivacySettings(account: Account) -> Signal<Never, NoError> {
return account.network.request(Api.functions.account.getGlobalPrivacySettings())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.GlobalPrivacySettings?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> Void in
guard let result = result else {
return
}
let globalSettings: GlobalPrivacySettings
switch result {
case let .globalPrivacySettings(flags, nonContactPeersPaidStars, disallowedStarGifts):
let automaticallyArchiveAndMuteNonContacts = (flags & (1 << 0)) != 0
let keepArchivedUnmuted = (flags & (1 << 1)) != 0
let keepArchivedFolders = (flags & (1 << 2)) != 0
let hideReadTime = (flags & (1 << 3)) != 0
let nonContactChatsRequirePremium = (flags & (1 << 4)) != 0
let displayGiftButton = (flags & (1 << 7)) != 0
let nonContactChatsPrivacy: GlobalPrivacySettings.NonContactChatsPrivacy
if let nonContactPeersPaidStars, nonContactPeersPaidStars > 0 {
nonContactChatsPrivacy = .paidMessages(StarsAmount(value: nonContactPeersPaidStars, nanos: 0))
} else if nonContactChatsRequirePremium {
nonContactChatsPrivacy = .requirePremium
} else {
nonContactChatsPrivacy = .everybody
}
let disallowedGifts = TelegramDisallowedGifts(apiDisallowedGifts: disallowedStarGifts)
globalSettings = GlobalPrivacySettings(
automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts,
keepArchivedUnmuted: keepArchivedUnmuted,
keepArchivedFolders: keepArchivedFolders,
hideReadTime: hideReadTime,
nonContactChatsPrivacy: nonContactChatsPrivacy,
disallowedGifts: disallowedGifts,
displayGiftButton: displayGiftButton
)
}
updateGlobalPrivacySettings(transaction: transaction, { _ in
return globalSettings
})
}
|> ignoreValues
}
}
func _internal_requestAccountPrivacySettings(account: Account) -> Signal<AccountPrivacySettings, NoError> {
let lastSeenPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyStatusTimestamp))
let groupPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyChatInvite))
let voiceCallPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyPhoneCall))
let voiceCallP2P = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyPhoneP2P))
let profilePhotoPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyProfilePhoto))
let forwardPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyForwards))
let phoneNumberPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyPhoneNumber))
let phoneDiscoveryPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyAddedByPhone))
let voiceMessagesPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyVoiceMessages))
let bioPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyAbout))
let birthdayPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyBirthday))
let giftsAutoSavePrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyStarGiftsAutoSave))
let noPaidMessagesPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyNoPaidMessages))
let savedMusicPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeySavedMusic))
let autoremoveTimeout = account.network.request(Api.functions.account.getAccountTTL())
let globalPrivacySettings = account.network.request(Api.functions.account.getGlobalPrivacySettings())
let messageAutoremoveTimeout = account.network.request(Api.functions.messages.getDefaultHistoryTTL())
return combineLatest(lastSeenPrivacy, groupPrivacy, voiceCallPrivacy, voiceCallP2P, profilePhotoPrivacy, forwardPrivacy, phoneNumberPrivacy, phoneDiscoveryPrivacy, voiceMessagesPrivacy, bioPrivacy, birthdayPrivacy, giftsAutoSavePrivacy, noPaidMessagesPrivacy, savedMusicPrivacy, autoremoveTimeout, globalPrivacySettings, messageAutoremoveTimeout)
|> `catch` { _ in
return .complete()
}
|> mapToSignal { lastSeenPrivacy, groupPrivacy, voiceCallPrivacy, voiceCallP2P, profilePhotoPrivacy, forwardPrivacy, phoneNumberPrivacy, phoneDiscoveryPrivacy, voiceMessagesPrivacy, bioPrivacy, birthdayPrivacy, giftsAutoSavePrivacy, noPaidMessagesPrivacy, savedMusicPrivacy, autoremoveTimeout, globalPrivacySettings, messageAutoremoveTimeout -> Signal<AccountPrivacySettings, NoError> in
let accountTimeoutSeconds: Int32
switch autoremoveTimeout {
case let .accountDaysTTL(days):
accountTimeoutSeconds = days * 24 * 60 * 60
}
let messageAutoremoveSeconds: Int32?
switch messageAutoremoveTimeout {
case let .defaultHistoryTTL(period):
if period != 0 {
messageAutoremoveSeconds = period
} else {
messageAutoremoveSeconds = nil
}
}
let lastSeenRules: [Api.PrivacyRule]
let groupRules: [Api.PrivacyRule]
let voiceRules: [Api.PrivacyRule]
let voiceP2PRules: [Api.PrivacyRule]
let profilePhotoRules: [Api.PrivacyRule]
let forwardRules: [Api.PrivacyRule]
let phoneNumberRules: [Api.PrivacyRule]
let voiceMessagesRules: [Api.PrivacyRule]
let bioRules: [Api.PrivacyRule]
let birthdayRules: [Api.PrivacyRule]
let giftsAutoSaveRules: [Api.PrivacyRule]
let noPaidMessagesRules: [Api.PrivacyRule]
let savedMusicRules: [Api.PrivacyRule]
var apiUsers: [Api.User] = []
var apiChats: [Api.Chat] = []
switch lastSeenPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
lastSeenRules = rules
}
switch groupPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
groupRules = rules
}
switch voiceCallPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
voiceRules = rules
}
switch voiceCallP2P {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
voiceP2PRules = rules
}
switch profilePhotoPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
profilePhotoRules = rules
}
switch forwardPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
forwardRules = rules
}
switch phoneNumberPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
phoneNumberRules = rules
}
var phoneDiscoveryValue = false
switch phoneDiscoveryPrivacy {
case let .privacyRules(rules, _, _):
for rule in rules {
switch rule {
case .privacyValueAllowAll:
phoneDiscoveryValue = true
default:
break
}
}
}
switch voiceMessagesPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
voiceMessagesRules = rules
}
switch bioPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
bioRules = rules
}
switch birthdayPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
birthdayRules = rules
}
switch giftsAutoSavePrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
giftsAutoSaveRules = rules
}
switch noPaidMessagesPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
noPaidMessagesRules = rules
}
switch savedMusicPrivacy {
case let .privacyRules(rules, chats, users):
apiUsers.append(contentsOf: users)
apiChats.append(contentsOf: chats)
savedMusicRules = rules
}
var peers: [SelectivePrivacyPeer] = []
for user in apiUsers {
peers.append(SelectivePrivacyPeer(peer: TelegramUser(user: user), participantCount: nil))
}
for chat in apiChats {
if let peer = parseTelegramGroupOrChannel(chat: chat) {
var participantCount: Int32? = nil
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _, _, _, _, _, _, _, _, _, _):
participantCount = participantsCountValue
default:
break
}
peers.append(SelectivePrivacyPeer(peer: peer, participantCount: participantCount))
}
}
var peerMap: [PeerId: SelectivePrivacyPeer] = [:]
for peer in peers {
peerMap[peer.peer.id] = peer
}
let globalSettings: GlobalPrivacySettings
switch globalPrivacySettings {
case let .globalPrivacySettings(flags, nonContactPeersPaidStars, disallowedStarGifts):
let automaticallyArchiveAndMuteNonContacts = (flags & (1 << 0)) != 0
let keepArchivedUnmuted = (flags & (1 << 1)) != 0
let keepArchivedFolders = (flags & (1 << 2)) != 0
let hideReadTime = (flags & (1 << 3)) != 0
let nonContactChatsRequirePremium = (flags & (1 << 4)) != 0
let displayGiftButton = (flags & (1 << 7)) != 0
let nonContactChatsPrivacy: GlobalPrivacySettings.NonContactChatsPrivacy
if let nonContactPeersPaidStars, nonContactPeersPaidStars > 0 {
nonContactChatsPrivacy = .paidMessages(StarsAmount(value: nonContactPeersPaidStars, nanos: 0))
} else if nonContactChatsRequirePremium {
nonContactChatsPrivacy = .requirePremium
} else {
nonContactChatsPrivacy = .everybody
}
let disallowedGifts = TelegramDisallowedGifts(apiDisallowedGifts: disallowedStarGifts)
globalSettings = GlobalPrivacySettings(
automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts,
keepArchivedUnmuted: keepArchivedUnmuted,
keepArchivedFolders: keepArchivedFolders,
hideReadTime: hideReadTime,
nonContactChatsPrivacy: nonContactChatsPrivacy,
disallowedGifts: disallowedGifts,
displayGiftButton: displayGiftButton
)
}
return account.postbox.transaction { transaction -> AccountPrivacySettings in
updatePeersCustom(transaction: transaction, peers: peers.map { $0.peer }, update: { _, updated in
return updated
})
updateGlobalMessageAutoremoveTimeoutSettings(transaction: transaction, { settings in
var settings = settings
settings.messageAutoremoveTimeout = messageAutoremoveSeconds
return settings
})
updateGlobalPrivacySettings(transaction: transaction, { _ in
return globalSettings
})
return AccountPrivacySettings(
presence: SelectivePrivacySettings(apiRules: lastSeenRules, peers: peerMap),
groupInvitations: SelectivePrivacySettings(apiRules: groupRules, peers: peerMap),
voiceCalls: SelectivePrivacySettings(apiRules: voiceRules, peers: peerMap),
voiceCallsP2P: SelectivePrivacySettings(apiRules: voiceP2PRules, peers: peerMap),
profilePhoto: SelectivePrivacySettings(apiRules: profilePhotoRules, peers: peerMap),
forwards: SelectivePrivacySettings(apiRules: forwardRules, peers: peerMap),
phoneNumber: SelectivePrivacySettings(apiRules: phoneNumberRules, peers: peerMap),
phoneDiscoveryEnabled: phoneDiscoveryValue,
voiceMessages: SelectivePrivacySettings(apiRules: voiceMessagesRules, peers: peerMap),
bio: SelectivePrivacySettings(apiRules: bioRules, peers: peerMap),
birthday: SelectivePrivacySettings(apiRules: birthdayRules, peers: peerMap),
giftsAutoSave: SelectivePrivacySettings(apiRules: giftsAutoSaveRules, peers: peerMap),
noPaidMessages: SelectivePrivacySettings(apiRules: noPaidMessagesRules, peers: peerMap),
savedMusic: SelectivePrivacySettings(apiRules: savedMusicRules, peers: peerMap),
globalSettings: globalSettings,
accountRemovalTimeout: accountTimeoutSeconds,
messageAutoremoveTimeout: messageAutoremoveSeconds
)
}
}
}
func _internal_updateAccountAutoArchiveChats(account: Account, value: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.automaticallyArchiveAndMuteNonContacts = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateNonContactChatsPrivacy(account: Account, value: GlobalPrivacySettings.NonContactChatsPrivacy) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.nonContactChatsPrivacy = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateAccountKeepArchivedFolders(account: Account, value: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.keepArchivedFolders = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateAccountKeepArchivedUnmuted(account: Account, value: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> GlobalPrivacySettings in
return fetchGlobalPrivacySettings(transaction: transaction)
}
|> mapToSignal { settings -> Signal<Never, NoError> in
var settings = settings
settings.keepArchivedUnmuted = value
return _internal_updateGlobalPrivacySettings(account: account, settings: settings)
}
}
func _internal_updateGlobalPrivacySettings(account: Account, settings: GlobalPrivacySettings) -> Signal<Never, NoError> {
let _ = (account.postbox.transaction { transaction -> Void in
updateGlobalPrivacySettings(transaction: transaction, { _ in
return settings
})
}).start()
var flags: Int32 = 0
if settings.automaticallyArchiveAndMuteNonContacts {
flags |= 1 << 0
}
if settings.keepArchivedUnmuted {
flags |= 1 << 1
}
if settings.keepArchivedFolders {
flags |= 1 << 2
}
if settings.hideReadTime {
flags |= 1 << 3
}
if settings.displayGiftButton {
flags |= 1 << 7
}
var noncontactPeersPaidStars: Int64?
switch settings.nonContactChatsPrivacy {
case .everybody:
flags |= 1 << 5
noncontactPeersPaidStars = 0
case .requirePremium:
flags |= 1 << 4
case let .paidMessages(starsAmount):
flags |= 1 << 5
noncontactPeersPaidStars = starsAmount.value
}
var giftFlags: Int32 = 0
if !settings.disallowedGifts.isEmpty {
if settings.disallowedGifts.contains(.unlimited) {
giftFlags |= 1 << 0
}
if settings.disallowedGifts.contains(.limited) {
giftFlags |= 1 << 1
}
if settings.disallowedGifts.contains(.unique) {
giftFlags |= 1 << 2
}
if settings.disallowedGifts.contains(.premium) {
giftFlags |= 1 << 3
}
if settings.disallowedGifts.contains(.channel) {
giftFlags |= 1 << 4
}
}
flags |= 1 << 6
let disallowedStargifts: Api.DisallowedGiftsSettings = .disallowedGiftsSettings(flags: giftFlags)
return account.network.request(Api.functions.account.setGlobalPrivacySettings(
settings: .globalPrivacySettings(flags: flags, noncontactPeersPaidStars: noncontactPeersPaidStars, disallowedGifts: disallowedStargifts)
))
|> retryRequest
|> ignoreValues
}
func _internal_updateAccountRemovalTimeout(account: Account, timeout: Int32) -> Signal<Void, NoError> {
return account.network.request(Api.functions.account.setAccountTTL(ttl: .accountDaysTTL(days: timeout / (24 * 60 * 60))))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
func _internal_updateMessageRemovalTimeout(account: Account, timeout: Int32?) -> Signal<Void, NoError> {
let _ = account.postbox.transaction({ transaction -> Void in
updateGlobalMessageAutoremoveTimeoutSettings(transaction: transaction, { settings in
var settings = settings
settings.messageAutoremoveTimeout = timeout
return settings
})
}).start()
return account.network.request(Api.functions.messages.setDefaultHistoryTTL(period: timeout ?? 0))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
func _internal_updatePhoneNumberDiscovery(account: Account, value: Bool) -> Signal<Void, NoError> {
var rules: [Api.InputPrivacyRule] = []
if value {
rules.append(.inputPrivacyValueAllowAll)
} else {
rules.append(.inputPrivacyValueAllowContacts)
}
return account.network.request(Api.functions.account.setPrivacy(key: .inputPrivacyKeyAddedByPhone, rules: rules))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
public enum UpdateSelectiveAccountPrivacySettingsType {
case presence
case groupInvitations
case voiceCalls
case voiceCallsP2P
case profilePhoto
case forwards
case phoneNumber
case voiceMessages
case bio
case birthday
case giftsAutoSave
case noPaidMessages
case savedMusic
var apiKey: Api.InputPrivacyKey {
switch self {
case .presence:
return .inputPrivacyKeyStatusTimestamp
case .groupInvitations:
return .inputPrivacyKeyChatInvite
case .voiceCalls:
return .inputPrivacyKeyPhoneCall
case .voiceCallsP2P:
return .inputPrivacyKeyPhoneP2P
case .profilePhoto:
return .inputPrivacyKeyProfilePhoto
case .forwards:
return .inputPrivacyKeyForwards
case .phoneNumber:
return .inputPrivacyKeyPhoneNumber
case .voiceMessages:
return .inputPrivacyKeyVoiceMessages
case .bio:
return .inputPrivacyKeyAbout
case .birthday:
return .inputPrivacyKeyBirthday
case .giftsAutoSave:
return .inputPrivacyKeyStarGiftsAutoSave
case .noPaidMessages:
return .inputPrivacyKeyNoPaidMessages
case .savedMusic:
return .inputPrivacyKeySavedMusic
}
}
}
private func apiInputUsers(transaction: Transaction, peerIds: [PeerId]) -> [Api.InputUser] {
var result: [Api.InputUser] = []
for peerId in peerIds {
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
result.append(inputUser)
}
}
return result
}
private func apiUserAndGroupIds(peerIds: [PeerId: SelectivePrivacyPeer]) -> (users: [PeerId], groups: [PeerId]) {
var users: [PeerId] = []
var groups: [PeerId] = []
for (peerId, _) in peerIds {
if peerId.namespace == Namespaces.Peer.CloudUser {
users.append(peerId)
} else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
groups.append(peerId)
}
}
return (users, groups)
}
func _internal_updateSelectiveAccountPrivacySettings(account: Account, type: UpdateSelectiveAccountPrivacySettingsType, settings: SelectivePrivacySettings) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
var rules: [Api.InputPrivacyRule] = []
switch settings {
case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium, enableForBots):
let enablePeers = apiUserAndGroupIds(peerIds: enableFor)
if !enablePeers.users.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowUsers(users: apiInputUsers(transaction: transaction, peerIds: enablePeers.users)))
}
if !enablePeers.groups.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowChatParticipants(chats: enablePeers.groups.map({ $0.id._internalGetInt64Value() })))
}
rules.append(Api.InputPrivacyRule.inputPrivacyValueDisallowAll)
if enableForCloseFriends {
rules.append(.inputPrivacyValueAllowCloseFriends)
}
if enableForPremium {
rules.append(.inputPrivacyValueAllowPremium)
}
if enableForBots {
rules.append(.inputPrivacyValueAllowBots)
}
case let .enableContacts(enableFor, disableFor, enableForPremium, enableForBots):
let enablePeers = apiUserAndGroupIds(peerIds: enableFor)
let disablePeers = apiUserAndGroupIds(peerIds: disableFor)
if !enablePeers.users.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowUsers(users: apiInputUsers(transaction: transaction, peerIds: enablePeers.users)))
}
if !enablePeers.groups.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowChatParticipants(chats: enablePeers.groups.map({ $0.id._internalGetInt64Value() })))
}
if !disablePeers.users.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueDisallowUsers(users: apiInputUsers(transaction: transaction, peerIds: disablePeers.users)))
}
if !disablePeers.groups.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueDisallowChatParticipants(chats: disablePeers.groups.map({ $0.id._internalGetInt64Value() })))
}
rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowContacts)
if enableForPremium {
rules.append(.inputPrivacyValueAllowPremium)
}
if enableForBots {
rules.append(.inputPrivacyValueAllowBots)
}
case let .enableEveryone(disableFor):
let disablePeers = apiUserAndGroupIds(peerIds: disableFor)
if !disablePeers.users.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueDisallowUsers(users: apiInputUsers(transaction: transaction, peerIds: disablePeers.users)))
}
if !disablePeers.groups.isEmpty {
rules.append(Api.InputPrivacyRule.inputPrivacyValueDisallowChatParticipants(chats: disablePeers.groups.map({ $0.id._internalGetInt64Value() })))
}
rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowAll)
}
return account.network.request(Api.functions.account.setPrivacy(key: type.apiKey, rules: rules))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
|> switchToLatest
}