mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-01-07 05:25:12 +00:00
Merge branch 'master' into experimental-2
# Conflicts: # submodules/ChatListUI/Sources/ChatContextMenus.swift # submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
func _internal_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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,935 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
import SyncCore
|
||||
|
||||
func _internal_revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
let flags: Int32 = (1 << 2)
|
||||
if let _ = peer as? TelegramChannel {
|
||||
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: nil, usageLimit: nil))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
|
||||
return account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||
let invitation = ExportedInvitation(apiExportedInvite: result)
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedExportedInvitation(invitation)
|
||||
} else {
|
||||
return CachedChannelData().withUpdatedExportedInvitation(invitation)
|
||||
}
|
||||
})
|
||||
return invitation
|
||||
|
||||
}
|
||||
}
|
||||
} else if let _ = peer as? TelegramGroup {
|
||||
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: nil, usageLimit: nil))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
|
||||
return account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||
let invitation = ExportedInvitation(apiExportedInvite: result)
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedExportedInvitation(invitation)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
return invitation
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
|
||||
public enum CreatePeerExportedInvitationError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var flags: Int32 = 0
|
||||
if let _ = expireDate {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
if let _ = usageLimit {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: expireDate, usageLimit: usageLimit))
|
||||
|> mapError { _ in return CreatePeerExportedInvitationError.generic }
|
||||
|> map { result -> ExportedInvitation? in
|
||||
return ExportedInvitation(apiExportedInvite: result)
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> castError(CreatePeerExportedInvitationError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum EditPeerExportedInvitationError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_editPeerExportedInvitation(account: Account, peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var flags: Int32 = 0
|
||||
if let _ = expireDate {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
if let _ = usageLimit {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: expireDate, usageLimit: usageLimit))
|
||||
|> mapError { _ in return EditPeerExportedInvitationError.generic }
|
||||
|> mapToSignal { result -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> in
|
||||
return account.postbox.transaction { transaction in
|
||||
if case let .exportedChatInvite(invite, users) = result {
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
return ExportedInvitation(apiExportedInvite: invite)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} |> mapError { _ in .generic }
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> castError(EditPeerExportedInvitationError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum RevokePeerExportedInvitationError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public enum RevokeExportedInvitationResult {
|
||||
case update(ExportedInvitation)
|
||||
case replace(ExportedInvitation, ExportedInvitation)
|
||||
}
|
||||
|
||||
func _internal_revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
let flags: Int32 = (1 << 2)
|
||||
return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil))
|
||||
|> mapError { _ in return RevokePeerExportedInvitationError.generic }
|
||||
|> mapToSignal { result -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> in
|
||||
return account.postbox.transaction { transaction in
|
||||
if case let .exportedChatInvite(invite, users) = result {
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
return .update(ExportedInvitation(apiExportedInvite: invite))
|
||||
} else if case let .exportedChatInviteReplaced(invite, newInvite, users) = result {
|
||||
var peers: [Peer] = []
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
let previous = ExportedInvitation(apiExportedInvite: invite)
|
||||
let new = ExportedInvitation(apiExportedInvite: newInvite)
|
||||
|
||||
if previous.isPermanent && new.isPermanent {
|
||||
transaction.updatePeerCachedData(peerIds: [peerId]) { peerId, current -> CachedPeerData? in
|
||||
if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
var current = current as? CachedGroupData ?? CachedGroupData()
|
||||
current = current.withUpdatedExportedInvitation(new)
|
||||
return current
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
var current = current as? CachedChannelData ?? CachedChannelData()
|
||||
current = current.withUpdatedExportedInvitation(new)
|
||||
return current
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .replace(previous, new)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} |> mapError { _ in .generic }
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> castError(RevokePeerExportedInvitationError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public struct ExportedInvitations : Equatable {
|
||||
public let list: [ExportedInvitation]?
|
||||
public let totalCount: Int32
|
||||
}
|
||||
|
||||
func _internal_peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId ?? account.peerId), let adminId = apiInputUser(adminPeer) {
|
||||
var flags: Int32 = 0
|
||||
if let _ = offsetLink {
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
if revoked {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: adminId, offsetDate: offsetLink?.date, offsetLink: offsetLink?.link, limit: 50))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<ExportedInvitations?, NoError> in
|
||||
return account.postbox.transaction { transaction -> ExportedInvitations? in
|
||||
if let result = result, case let .exportedChatInvites(count, apiInvites, users) = result {
|
||||
var peers: [Peer] = []
|
||||
var peersMap: [PeerId: Peer] = [:]
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
peersMap[telegramUser.id] = telegramUser
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
let invites = apiInvites.map { ExportedInvitation(apiExportedInvite: $0) }
|
||||
return ExportedInvitations(list: invites, totalCount: count)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
|
||||
public enum DeletePeerExportedInvitationError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<Never, DeletePeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, DeletePeerExportedInvitationError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.deleteExportedChatInvite(peer: inputPeer, link: link))
|
||||
|> mapError { _ in return DeletePeerExportedInvitationError.generic }
|
||||
|> ignoreValues
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> castError(DeletePeerExportedInvitationError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func _internal_deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId, adminId: PeerId) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId), let inputAdminId = apiInputUser(adminPeer) {
|
||||
return account.network.request(Api.functions.messages.deleteRevokedExportedChatInvites(peer: inputPeer, adminId: inputAdminId))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
private let cachedPeerExportedInvitationsCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||
|
||||
public struct PeerExportedInvitationsState: Equatable {
|
||||
public var invitations: [ExportedInvitation]
|
||||
public var isLoadingMore: Bool
|
||||
public var hasLoadedOnce: Bool
|
||||
public var canLoadMore: Bool
|
||||
public var count: Int32
|
||||
|
||||
public init() {
|
||||
self.invitations = []
|
||||
self.isLoadingMore = false
|
||||
self.hasLoadedOnce = false
|
||||
self.canLoadMore = false
|
||||
self.count = 0
|
||||
}
|
||||
|
||||
public init(invitations: [ExportedInvitation], isLoadingMore: Bool, hasLoadedOnce: Bool, canLoadMore: Bool, count: Int32) {
|
||||
self.invitations = invitations
|
||||
self.isLoadingMore = isLoadingMore
|
||||
self.hasLoadedOnce = hasLoadedOnce
|
||||
self.canLoadMore = canLoadMore
|
||||
self.count = count
|
||||
}
|
||||
}
|
||||
|
||||
final class CachedPeerExportedInvitations: PostboxCoding {
|
||||
let invitations: [ExportedInvitation]
|
||||
let canLoadMore: Bool
|
||||
let count: Int32
|
||||
|
||||
static func key(peerId: PeerId, revoked: Bool) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 8 + 4)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setInt32(8, value: revoked ? 1 : 0)
|
||||
return key
|
||||
}
|
||||
|
||||
init(invitations: [ExportedInvitation], canLoadMore: Bool, count: Int32) {
|
||||
self.invitations = invitations
|
||||
self.canLoadMore = canLoadMore
|
||||
self.count = count
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.invitations = decoder.decodeObjectArrayForKey("invitations")
|
||||
self.canLoadMore = decoder.decodeBoolForKey("canLoadMore", orElse: false)
|
||||
self.count = decoder.decodeInt32ForKey("count", orElse: 0)
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObjectArray(self.invitations, forKey: "invitations")
|
||||
encoder.encodeBool(self.canLoadMore, forKey: "canLoadMore")
|
||||
encoder.encodeInt32(self.count, forKey: "count")
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerExportedInvitationsContextImpl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
private let adminId: PeerId
|
||||
private let revoked: Bool
|
||||
private var forceUpdate: Bool
|
||||
private let disposable = MetaDisposable()
|
||||
private let updateDisposable = MetaDisposable()
|
||||
private var isLoadingMore: Bool = false
|
||||
private var hasLoadedOnce: Bool = false
|
||||
private var canLoadMore: Bool = true
|
||||
private var loadedFromCache: Bool = false
|
||||
private var results: [ExportedInvitation] = []
|
||||
private var count: Int32
|
||||
private var populateCache: Bool = true
|
||||
private var isMainList: Bool
|
||||
|
||||
let state = Promise<PeerExportedInvitationsState>()
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
self.adminId = adminId ?? account.peerId
|
||||
self.revoked = revoked
|
||||
self.forceUpdate = forceUpdate
|
||||
self.isMainList = adminId == nil
|
||||
|
||||
self.count = 0
|
||||
|
||||
if adminId == nil {
|
||||
self.isLoadingMore = true
|
||||
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
|
||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) as? CachedPeerExportedInvitations
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] cachedResult in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
if let cachedResult = cachedResult {
|
||||
strongSelf.results = cachedResult.invitations
|
||||
strongSelf.count = cachedResult.count
|
||||
strongSelf.hasLoadedOnce = true
|
||||
strongSelf.canLoadMore = cachedResult.canLoadMore
|
||||
strongSelf.loadedFromCache = true
|
||||
}
|
||||
strongSelf.loadMore()
|
||||
}))
|
||||
}
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.updateDisposable.dispose()
|
||||
}
|
||||
|
||||
func reload() {
|
||||
self.forceUpdate = true
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
func loadMore() {
|
||||
if self.isLoadingMore {
|
||||
return
|
||||
}
|
||||
self.isLoadingMore = true
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
let adminId = self.adminId
|
||||
let revoked = self.revoked
|
||||
var lastResult = self.results.last
|
||||
|
||||
if self.forceUpdate {
|
||||
self.populateCache = self.isMainList
|
||||
self.forceUpdate = false
|
||||
lastResult = nil
|
||||
} else if self.loadedFromCache {
|
||||
self.populateCache = false
|
||||
self.loadedFromCache = false
|
||||
}
|
||||
let populateCache = self.populateCache
|
||||
|
||||
self.disposable.set((self.account.postbox.transaction { transaction -> (peerId: Api.InputPeer?, adminId: Api.InputUser?) in
|
||||
return (transaction.getPeer(peerId).flatMap(apiInputPeer), transaction.getPeer(adminId).flatMap(apiInputUser))
|
||||
}
|
||||
|> mapToSignal { inputPeer, adminId -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||
if let inputPeer = inputPeer, let adminId = adminId {
|
||||
let offsetLink = lastResult?.link
|
||||
let offsetDate = lastResult?.date
|
||||
var flags: Int32 = 0
|
||||
if let _ = offsetLink {
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
if revoked {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: adminId, offsetDate: offsetDate, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<([ExportedInvitation], Int32), NoError> in
|
||||
return account.postbox.transaction { transaction -> ([ExportedInvitation], Int32) in
|
||||
guard let result = result else {
|
||||
return ([], 0)
|
||||
}
|
||||
switch result {
|
||||
case let .exportedChatInvites(count, invites, users):
|
||||
var peers: [Peer] = []
|
||||
for apiUser in users {
|
||||
peers.append(TelegramUser(user: apiUser))
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
let invitations: [ExportedInvitation] = invites.compactMap { ExportedInvitation(apiExportedInvite: $0) }
|
||||
if populateCache {
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked)), entry: CachedPeerExportedInvitations(invitations: invitations, canLoadMore: count >= 50, count: count), collectionSpec: cachedPeerExportedInvitationsCollectionSpec)
|
||||
}
|
||||
return (invitations, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
return signal
|
||||
} else {
|
||||
return .single(([], 0))
|
||||
}
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] invitations, updatedCount in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.populateCache {
|
||||
strongSelf.populateCache = false
|
||||
strongSelf.results.removeAll()
|
||||
}
|
||||
var existingLinks = Set(strongSelf.results.map { $0.link })
|
||||
for invitation in invitations {
|
||||
if !existingLinks.contains(invitation.link) {
|
||||
strongSelf.results.append(invitation)
|
||||
existingLinks.insert(invitation.link)
|
||||
}
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
strongSelf.hasLoadedOnce = true
|
||||
strongSelf.canLoadMore = !invitations.isEmpty
|
||||
if strongSelf.canLoadMore {
|
||||
strongSelf.count = max(updatedCount, Int32(strongSelf.results.count))
|
||||
} else {
|
||||
strongSelf.count = Int32(strongSelf.results.count)
|
||||
}
|
||||
strongSelf.updateState()
|
||||
|
||||
if strongSelf.forceUpdate {
|
||||
strongSelf.loadMore()
|
||||
}
|
||||
}))
|
||||
self.updateState()
|
||||
}
|
||||
|
||||
func add(_ invite: ExportedInvitation) {
|
||||
var results = self.results
|
||||
results.removeAll(where: { $0.link == invite.link})
|
||||
results.insert(invite, at: 0)
|
||||
self.results = results
|
||||
self.updateState()
|
||||
self.updateCache()
|
||||
}
|
||||
|
||||
func update(_ invite: ExportedInvitation) {
|
||||
var results = self.results
|
||||
if let index = self.results.firstIndex(where: { $0.link == invite.link }) {
|
||||
results[index] = invite
|
||||
}
|
||||
self.results = results
|
||||
self.updateState()
|
||||
self.updateCache()
|
||||
}
|
||||
|
||||
func remove(_ invite: ExportedInvitation) {
|
||||
var results = self.results
|
||||
results.removeAll(where: { $0.link == invite.link})
|
||||
self.results = results
|
||||
self.updateState()
|
||||
self.updateCache()
|
||||
}
|
||||
|
||||
func clear() {
|
||||
self.results = []
|
||||
self.count = 0
|
||||
self.updateState()
|
||||
self.updateCache()
|
||||
}
|
||||
|
||||
private func updateCache() {
|
||||
guard self.isMainList && self.hasLoadedOnce && !self.isLoadingMore else {
|
||||
return
|
||||
}
|
||||
|
||||
let peerId = self.peerId
|
||||
let revoked = self.revoked
|
||||
let invitations = Array(self.results.prefix(50))
|
||||
let canLoadMore = self.canLoadMore
|
||||
let count = self.count
|
||||
self.updateDisposable.set(self.account.postbox.transaction({ transaction in
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked)), entry: CachedPeerExportedInvitations(invitations: invitations, canLoadMore: canLoadMore, count: count), collectionSpec: cachedPeerExportedInvitationsCollectionSpec)
|
||||
}).start())
|
||||
}
|
||||
|
||||
private func updateState() {
|
||||
self.state.set(.single(PeerExportedInvitationsState(invitations: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count)))
|
||||
}
|
||||
}
|
||||
|
||||
public final class PeerExportedInvitationsContext {
|
||||
private let queue: Queue = Queue()
|
||||
private let impl: QueueLocalObject<PeerExportedInvitationsContextImpl>
|
||||
|
||||
public var state: Signal<PeerExportedInvitationsState, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.state.get().start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
init(account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, adminId: adminId, revoked: revoked, forceUpdate: forceUpdate)
|
||||
})
|
||||
}
|
||||
|
||||
public func reload() {
|
||||
self.impl.with { impl in
|
||||
impl.reload()
|
||||
}
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
self.impl.with { impl in
|
||||
impl.loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
public func add(_ invite: ExportedInvitation) {
|
||||
self.impl.with { impl in
|
||||
impl.add(invite)
|
||||
}
|
||||
}
|
||||
|
||||
public func update(_ invite: ExportedInvitation) {
|
||||
self.impl.with { impl in
|
||||
impl.update(invite)
|
||||
}
|
||||
}
|
||||
|
||||
public func remove(_ invite: ExportedInvitation) {
|
||||
self.impl.with { impl in
|
||||
impl.remove(invite)
|
||||
}
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
self.impl.with { impl in
|
||||
impl.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private let cachedPeerInvitationImportersCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||
|
||||
public struct PeerInvitationImportersState: Equatable {
|
||||
public struct Importer: Equatable {
|
||||
public var peer: RenderedPeer
|
||||
public var date: Int32
|
||||
}
|
||||
public var importers: [Importer]
|
||||
public var isLoadingMore: Bool
|
||||
public var hasLoadedOnce: Bool
|
||||
public var canLoadMore: Bool
|
||||
public var count: Int32
|
||||
}
|
||||
|
||||
final class CachedPeerInvitationImporters: PostboxCoding {
|
||||
let peerIds: [PeerId]
|
||||
let dates: [PeerId: Int32]
|
||||
let count: Int32
|
||||
|
||||
static func key(peerId: PeerId, link: String) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 8 + 4)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setInt32(8, value: Int32(HashFunctions.murMurHash32(link)))
|
||||
return key
|
||||
}
|
||||
|
||||
init(importers: [PeerInvitationImportersState.Importer], count: Int32) {
|
||||
self.peerIds = importers.map { $0.peer.peerId }
|
||||
self.dates = importers.reduce(into: [PeerId: Int32]()) {
|
||||
$0[$1.peer.peerId] = $1.date
|
||||
}
|
||||
self.count = count
|
||||
}
|
||||
|
||||
init(peerIds: [PeerId], dates: [PeerId: Int32], count: Int32) {
|
||||
self.peerIds = peerIds
|
||||
self.dates = dates
|
||||
self.count = count
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.peerIds = decoder.decodeInt64ArrayForKey("peerIds").map(PeerId.init)
|
||||
|
||||
var dates: [PeerId: Int32] = [:]
|
||||
let datesArray = decoder.decodeInt64ArrayForKey("dates")
|
||||
for index in stride(from: 0, to: datesArray.endIndex, by: 2) {
|
||||
let userId = datesArray[index]
|
||||
let date = datesArray[index + 1]
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
dates[peerId] = Int32(clamping: date)
|
||||
}
|
||||
self.dates = dates
|
||||
|
||||
self.count = decoder.decodeInt32ForKey("count", orElse: 0)
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64Array(self.peerIds.map { $0.toInt64() }, forKey: "peerIds")
|
||||
|
||||
var dates: [Int64] = []
|
||||
for (peerId, date) in self.dates {
|
||||
dates.append(peerId.id._internalGetInt64Value())
|
||||
dates.append(Int64(date))
|
||||
}
|
||||
encoder.encodeInt64Array(dates, forKey: "dates")
|
||||
|
||||
encoder.encodeInt32(self.count, forKey: "count")
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerInvitationImportersContextImpl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
private let link: String
|
||||
private let disposable = MetaDisposable()
|
||||
private var isLoadingMore: Bool = false
|
||||
private var hasLoadedOnce: Bool = false
|
||||
private var canLoadMore: Bool = true
|
||||
private var loadedFromCache = false
|
||||
private var results: [PeerInvitationImportersState.Importer] = []
|
||||
private var count: Int32
|
||||
private var populateCache: Bool = true
|
||||
|
||||
let state = Promise<PeerInvitationImportersState>()
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId, invite: ExportedInvitation) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
self.link = invite.link
|
||||
|
||||
let count = invite.count ?? 0
|
||||
self.count = count
|
||||
|
||||
self.isLoadingMore = true
|
||||
self.disposable.set((account.postbox.transaction { transaction -> (peers: [PeerInvitationImportersState.Importer], canLoadMore: Bool)? in
|
||||
let cachedResult = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerInvitationImporters.key(peerId: peerId, link: invite.link))) as? CachedPeerInvitationImporters
|
||||
if let cachedResult = cachedResult, Int(cachedResult.count) == count {
|
||||
var result: [PeerInvitationImportersState.Importer] = []
|
||||
for peerId in cachedResult.peerIds {
|
||||
if let peer = transaction.getPeer(peerId), let date = cachedResult.dates[peerId] {
|
||||
result.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return (result, Int(cachedResult.count) > result.count)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] cachedPeersAndCanLoadMore in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
if let (cachedPeers, canLoadMore) = cachedPeersAndCanLoadMore {
|
||||
strongSelf.results = cachedPeers
|
||||
strongSelf.hasLoadedOnce = true
|
||||
strongSelf.canLoadMore = canLoadMore
|
||||
strongSelf.loadedFromCache = true
|
||||
}
|
||||
strongSelf.loadMore()
|
||||
}))
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func loadMore() {
|
||||
if self.isLoadingMore {
|
||||
return
|
||||
}
|
||||
self.isLoadingMore = true
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
let link = self.link
|
||||
let populateCache = self.populateCache
|
||||
|
||||
var lastResult = self.results.last
|
||||
if self.loadedFromCache {
|
||||
self.loadedFromCache = false
|
||||
lastResult = nil
|
||||
}
|
||||
|
||||
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<([PeerInvitationImportersState.Importer], Int32), NoError> in
|
||||
if let inputPeer = inputPeer {
|
||||
let offsetUser = lastResult?.peer.peer.flatMap { apiInputUser($0) } ?? .inputUserEmpty
|
||||
let offsetDate = lastResult?.date ?? 0
|
||||
let signal = account.network.request(Api.functions.messages.getChatInviteImporters(peer: inputPeer, link: link, offsetDate: offsetDate, offsetUser: offsetUser, limit: lastResult == nil ? 10 : 50))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ChatInviteImporters?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<([PeerInvitationImportersState.Importer], Int32), NoError> in
|
||||
return account.postbox.transaction { transaction -> ([PeerInvitationImportersState.Importer], Int32) in
|
||||
guard let result = result else {
|
||||
return ([], 0)
|
||||
}
|
||||
switch result {
|
||||
case let .chatInviteImporters(count, importers, users):
|
||||
var peers: [Peer] = []
|
||||
for apiUser in users {
|
||||
peers.append(TelegramUser(user: apiUser))
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
var resultImporters: [PeerInvitationImportersState.Importer] = []
|
||||
for importer in importers {
|
||||
let peerId: PeerId
|
||||
let date: Int32
|
||||
switch importer {
|
||||
case let .chatInviteImporter(userId, dateValue):
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
date = dateValue
|
||||
}
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
resultImporters.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date))
|
||||
}
|
||||
}
|
||||
if populateCache {
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerInvitationImporters.key(peerId: peerId, link: link)), entry: CachedPeerInvitationImporters(importers: resultImporters, count: count), collectionSpec: cachedPeerInvitationImportersCollectionSpec)
|
||||
}
|
||||
return (resultImporters, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
return signal
|
||||
} else {
|
||||
return .single(([], 0))
|
||||
}
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] importers, updatedCount in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.populateCache {
|
||||
strongSelf.populateCache = false
|
||||
strongSelf.results.removeAll()
|
||||
}
|
||||
var existingIds = Set(strongSelf.results.map { $0.peer.peerId })
|
||||
for importer in importers {
|
||||
if !existingIds.contains(importer.peer.peerId) {
|
||||
strongSelf.results.append(importer)
|
||||
existingIds.insert(importer.peer.peerId)
|
||||
}
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
strongSelf.hasLoadedOnce = true
|
||||
strongSelf.canLoadMore = !importers.isEmpty
|
||||
if strongSelf.canLoadMore {
|
||||
strongSelf.count = max(updatedCount, Int32(strongSelf.results.count))
|
||||
} else {
|
||||
strongSelf.count = Int32(strongSelf.results.count)
|
||||
}
|
||||
strongSelf.updateState()
|
||||
}))
|
||||
self.updateState()
|
||||
}
|
||||
|
||||
private func updateState() {
|
||||
self.state.set(.single(PeerInvitationImportersState(importers: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count)))
|
||||
}
|
||||
}
|
||||
|
||||
public final class PeerInvitationImportersContext {
|
||||
private let queue: Queue = Queue()
|
||||
private let impl: QueueLocalObject<PeerInvitationImportersContextImpl>
|
||||
|
||||
public var state: Signal<PeerInvitationImportersState, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.state.get().start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
init(account: Account, peerId: PeerId, invite: ExportedInvitation) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return PeerInvitationImportersContextImpl(queue: queue, account: account, peerId: peerId, invite: invite)
|
||||
})
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
self.impl.with { impl in
|
||||
impl.loadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ExportedInvitationCreator : Equatable {
|
||||
public let peer: RenderedPeer
|
||||
public let count: Int32
|
||||
public let revokedCount: Int32
|
||||
}
|
||||
|
||||
func _internal_peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<[ExportedInvitationCreator], NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var isCreator = false
|
||||
if let peer = peer as? TelegramGroup, case .creator = peer.role {
|
||||
isCreator = true
|
||||
} else if let peer = peer as? TelegramChannel, peer.flags.contains(.isCreator) {
|
||||
isCreator = true
|
||||
}
|
||||
if !isCreator {
|
||||
return .single([])
|
||||
} else {
|
||||
return account.network.request(Api.functions.messages.getAdminsWithInvites(peer: inputPeer))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.ChatAdminsWithInvites?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[ExportedInvitationCreator], NoError> in
|
||||
return account.postbox.transaction { transaction -> [ExportedInvitationCreator] in
|
||||
if let result = result, case let .chatAdminsWithInvites(admins, users) = result {
|
||||
var creators: [ExportedInvitationCreator] = []
|
||||
var peers: [Peer] = []
|
||||
var peersMap: [PeerId: Peer] = [:]
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
peersMap[telegramUser.id] = telegramUser
|
||||
}
|
||||
|
||||
for admin in admins {
|
||||
switch admin {
|
||||
case let .chatAdminWithInvites(adminId, invitesCount, revokedInvitesCount):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId))
|
||||
if let peer = peersMap[peerId], peerId != account.peerId {
|
||||
creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount, revokedCount: revokedInvitesCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
return creators
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum JoinLinkError {
|
||||
case generic
|
||||
case tooMuchJoined
|
||||
case tooMuchUsers
|
||||
}
|
||||
|
||||
func apiUpdatesGroups(_ updates: Api.Updates) -> [Api.Chat] {
|
||||
switch updates {
|
||||
case let .updates( _, _, chats, _, _):
|
||||
return chats
|
||||
case let .updatesCombined(_, _, chats, _, _, _):
|
||||
return chats
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public enum ExternalJoiningChatState {
|
||||
case invite(title: String, photoRepresentation: TelegramMediaImageRepresentation?, participantsCount: Int32, participants: [Peer]?)
|
||||
case alreadyJoined(PeerId)
|
||||
case invalidHash
|
||||
case peek(PeerId, Int32)
|
||||
}
|
||||
|
||||
func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal <PeerId?, JoinLinkError> {
|
||||
return account.network.request(Api.functions.messages.importChatInvite(hash: hash))
|
||||
|> mapError { error -> JoinLinkError in
|
||||
switch error.errorDescription {
|
||||
case "CHANNELS_TOO_MUCH":
|
||||
return .tooMuchJoined
|
||||
case "USERS_TOO_MUCH":
|
||||
return .tooMuchUsers
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<PeerId?, JoinLinkError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
if let peerId = apiUpdatesGroups(updates).first?.peerId {
|
||||
return account.postbox.multiplePeersView([peerId])
|
||||
|> castError(JoinLinkError.self)
|
||||
|> filter { view in
|
||||
return view.peers[peerId] != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ in
|
||||
return peerId
|
||||
}
|
||||
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .single(nil) |> castError(JoinLinkError.self))
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<ExternalJoiningChatState, NoError> {
|
||||
return account.network.request(Api.functions.messages.checkChatInvite(hash: hash))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.ChatInvite?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { (result) -> Signal<ExternalJoiningChatState, NoError> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .chatInvite(invite):
|
||||
let photo = telegramMediaImageFromApiPhoto(invite.photo).flatMap({ smallestImageRepresentation($0.representations) })
|
||||
return .single(.invite(title: invite.title, photoRepresentation: photo, participantsCount: invite.participantsCount, participants: invite.participants?.map({TelegramUser(user: $0)})))
|
||||
case let .chatInviteAlready(chat):
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
return account.postbox.transaction({ (transaction) -> ExternalJoiningChatState in
|
||||
updatePeers(transaction: transaction, peers: [peer], update: { (previous, updated) -> Peer? in
|
||||
return updated
|
||||
})
|
||||
|
||||
return .alreadyJoined(peer.id)
|
||||
})
|
||||
}
|
||||
return .single(.invalidHash)
|
||||
case let .chatInvitePeek(chat, expires):
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
return account.postbox.transaction({ (transaction) -> ExternalJoiningChatState in
|
||||
updatePeers(transaction: transaction, peers: [peer], update: { (previous, updated) -> Peer? in
|
||||
return updated
|
||||
})
|
||||
|
||||
return .peek(peer.id, expires)
|
||||
})
|
||||
}
|
||||
return .single(.invalidHash)
|
||||
}
|
||||
} else {
|
||||
return .single(.invalidHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
public final class NotificationExceptionsList: Equatable {
|
||||
public let peers: [PeerId: Peer]
|
||||
public let settings: [PeerId: TelegramPeerNotificationSettings]
|
||||
|
||||
public init(peers: [PeerId: Peer], settings: [PeerId: TelegramPeerNotificationSettings]) {
|
||||
self.peers = peers
|
||||
self.settings = settings
|
||||
}
|
||||
|
||||
public static func ==(lhs: NotificationExceptionsList, rhs: NotificationExceptionsList) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_notificationExceptionsList(postbox: Postbox, network: Network) -> Signal<NotificationExceptionsList, NoError> {
|
||||
return network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 1, peer: nil))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<NotificationExceptionsList, NoError> in
|
||||
return postbox.transaction { transaction -> NotificationExceptionsList in
|
||||
switch result {
|
||||
case let .updates(updates, users, chats, _, _):
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var settings: [PeerId: TelegramPeerNotificationSettings] = [:]
|
||||
|
||||
for user in users {
|
||||
let peer = TelegramUser(user: user)
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
for chat in chats {
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: Array(peers.values), update: { _, updated in updated })
|
||||
|
||||
for update in updates {
|
||||
switch update {
|
||||
case let .updateNotifySettings(apiPeer, notifySettings):
|
||||
switch apiPeer {
|
||||
case let .notifyPeer(notifyPeer):
|
||||
let peerId: PeerId
|
||||
switch notifyPeer {
|
||||
case let .peerUser(userId):
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
case let .peerChat(chatId):
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
|
||||
case let .peerChannel(channelId):
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
|
||||
}
|
||||
settings[peerId] = TelegramPeerNotificationSettings(apiSettings: notifySettings)
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return NotificationExceptionsList(peers: peers, settings: settings)
|
||||
default:
|
||||
return NotificationExceptionsList(peers: [:], settings: [:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import MtProtoKit
|
||||
import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum UpdatePeerPhotoStatus {
|
||||
case progress(Float)
|
||||
case complete([TelegramMediaImageRepresentation])
|
||||
}
|
||||
|
||||
public enum UploadPeerPhotoError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
|
||||
}
|
||||
|
||||
public struct UploadedPeerPhotoData {
|
||||
fileprivate let resource: MediaResource
|
||||
fileprivate let content: UploadedPeerPhotoDataContent
|
||||
|
||||
public var isCompleted: Bool {
|
||||
if case let .result(result) = content, case .inputFile = result {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum UploadedPeerPhotoDataContent {
|
||||
case result(MultipartUploadResult)
|
||||
case error
|
||||
}
|
||||
|
||||
func _internal_uploadedPeerPhoto(postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
||||
|> map { result -> UploadedPeerPhotoData in
|
||||
return UploadedPeerPhotoData(resource: resource, content: .result(result))
|
||||
}
|
||||
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
|
||||
return .single(UploadedPeerPhotoData(resource: resource, content: .error))
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_uploadedPeerVideo(postbox: Postbox, network: Network, messageMediaPreuploadManager: MessageMediaPreuploadManager?, resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
if let messageMediaPreuploadManager = messageMediaPreuploadManager {
|
||||
return messageMediaPreuploadManager.upload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .video), hintFileSize: nil, hintFileIsLarge: false)
|
||||
|> map { result -> UploadedPeerPhotoData in
|
||||
return UploadedPeerPhotoData(resource: resource, content: .result(result))
|
||||
}
|
||||
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
|
||||
return .single(UploadedPeerPhotoData(resource: resource, content: .error))
|
||||
}
|
||||
} else {
|
||||
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .video), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|
||||
|> map { result -> UploadedPeerPhotoData in
|
||||
return UploadedPeerPhotoData(resource: resource, content: .result(result))
|
||||
}
|
||||
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
|
||||
return .single(UploadedPeerPhotoData(resource: resource, content: .error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_updatePeerPhoto(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
return _internal_updatePeerPhotoInternal(postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId, peer: postbox.loadedPeerWithId(peerId), photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
|
||||
}
|
||||
|
||||
func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal<Peer, NoError>, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
return peer
|
||||
|> mapError { _ in return .generic }
|
||||
|> mapToSignal { peer -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|
||||
if let photo = photo {
|
||||
let mappedPhoto = photo
|
||||
|> take(until: { value in
|
||||
if case let .result(resultData) = value.content, case .inputFile = resultData {
|
||||
return SignalTakeAction(passthrough: true, complete: true)
|
||||
} else {
|
||||
return SignalTakeAction(passthrough: true, complete: false)
|
||||
}
|
||||
})
|
||||
|
||||
let mappedVideo: Signal<UploadedPeerPhotoData?, NoError>
|
||||
if let video = video {
|
||||
mappedVideo = video
|
||||
|> take(until: { value in
|
||||
if case let .result(resultData)? = value?.content, case .inputFile = resultData {
|
||||
return SignalTakeAction(passthrough: true, complete: true)
|
||||
} else {
|
||||
return SignalTakeAction(passthrough: true, complete: false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
mappedVideo = .single(nil)
|
||||
}
|
||||
|
||||
return combineLatest(mappedPhoto, mappedVideo)
|
||||
|> mapError { _ -> UploadPeerPhotoError in return .generic }
|
||||
|> mapToSignal { photoResult, videoResult -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in
|
||||
switch photoResult.content {
|
||||
case .error:
|
||||
return .fail(.generic)
|
||||
case let .result(resultData):
|
||||
switch resultData {
|
||||
case let .progress(progress):
|
||||
var mappedProgress = progress
|
||||
if let _ = videoResult {
|
||||
mappedProgress *= 0.2
|
||||
}
|
||||
return .single((.progress(mappedProgress), photoResult.resource, videoResult?.resource))
|
||||
case let .inputFile(file):
|
||||
var videoFile: Api.InputFile?
|
||||
if let videoResult = videoResult {
|
||||
switch videoResult.content {
|
||||
case .error:
|
||||
return .fail(.generic)
|
||||
case let .result(resultData):
|
||||
switch resultData {
|
||||
case let .progress(progress):
|
||||
let mappedProgress = 0.2 + progress * 0.8
|
||||
return .single((.progress(mappedProgress), photoResult.resource, videoResult.resource))
|
||||
case let .inputFile(file):
|
||||
videoFile = file
|
||||
break
|
||||
default:
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
if peer is TelegramUser {
|
||||
var flags: Int32 = (1 << 0)
|
||||
if let _ = videoFile {
|
||||
flags |= (1 << 1)
|
||||
if let _ = videoStartTimestamp {
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
}
|
||||
|
||||
return network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp))
|
||||
|> mapError { _ in return UploadPeerPhotoError.generic }
|
||||
|> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = []
|
||||
switch photo {
|
||||
case let .photo(photo: apiPhoto, users: _):
|
||||
switch apiPhoto {
|
||||
case .photoEmpty:
|
||||
break
|
||||
case let .photo(_, id, accessHash, fileReference, _, sizes, videoSizes, dcId):
|
||||
var sizes = sizes
|
||||
if sizes.count == 3 {
|
||||
sizes.remove(at: 1)
|
||||
}
|
||||
for size in sizes {
|
||||
switch size {
|
||||
case let .photoSize(_, w, h, _):
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: id, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: nil, localId: nil), progressiveSizes: [], immediateThumbnailData: nil))
|
||||
case let .photoSizeProgressive(_, w, h, sizes):
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: id, sizeSpec: w <= 200 ? .small : .fullSize, volumeId: nil, localId: nil), progressiveSizes: sizes, immediateThumbnailData: nil))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let videoSizes = videoSizes {
|
||||
for size in videoSizes {
|
||||
switch size {
|
||||
case let .videoSize(_, type, w, h, size, videoStartTs):
|
||||
let resource: TelegramMediaResource
|
||||
resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, size: Int(size), fileReference: fileReference.makeData())
|
||||
|
||||
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, startTimestamp: videoStartTs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for representation in representations {
|
||||
postbox.mediaBox.copyResourceData(from: photoResult.resource.id, to: representation.resource.id)
|
||||
}
|
||||
|
||||
if let resource = videoResult?.resource {
|
||||
for representation in videoRepresentations {
|
||||
postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return postbox.transaction { transaction -> (UpdatePeerPhotoStatus, MediaResource?, MediaResource?) in
|
||||
if let peer = transaction.getPeer(peer.id) {
|
||||
updatePeers(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in
|
||||
if let peer = peer as? TelegramUser {
|
||||
return peer.withUpdatedPhoto(representations)
|
||||
} else {
|
||||
return peer
|
||||
}
|
||||
})
|
||||
}
|
||||
return (.complete(representations), photoResult.resource, videoResult?.resource)
|
||||
} |> mapError {_ in return UploadPeerPhotoError.generic}
|
||||
}
|
||||
} else {
|
||||
var flags: Int32 = (1 << 0)
|
||||
if let _ = videoFile {
|
||||
flags |= (1 << 1)
|
||||
if let _ = videoStartTimestamp {
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
}
|
||||
|
||||
let request: Signal<Api.Updates, MTRpcError>
|
||||
if let peer = peer as? TelegramGroup {
|
||||
request = network.request(Api.functions.messages.editChatPhoto(chatId: peer.id.id._internalGetInt64Value(), photo: .inputChatUploadedPhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp)))
|
||||
} else if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
|
||||
request = network.request(Api.functions.channels.editPhoto(channel: inputChannel, photo: .inputChatUploadedPhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp)))
|
||||
} else {
|
||||
assertionFailure()
|
||||
request = .complete()
|
||||
}
|
||||
|
||||
return request
|
||||
|> mapError {_ in return UploadPeerPhotoError.generic}
|
||||
|> mapToSignal { updates -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in
|
||||
guard let chat = updates.chats.first, chat.peerId == peer.id, let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) else {
|
||||
stateManager?.addUpdates(updates)
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return mapResourceToAvatarSizes(photoResult.resource, groupOrChannel.profileImageRepresentations)
|
||||
|> castError(UploadPeerPhotoError.self)
|
||||
|> mapToSignal { generatedData -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in
|
||||
stateManager?.addUpdates(updates)
|
||||
|
||||
for (index, data) in generatedData {
|
||||
if index >= 0 && index < groupOrChannel.profileImageRepresentations.count {
|
||||
postbox.mediaBox.storeResourceData(groupOrChannel.profileImageRepresentations[index].resource.id, data: data)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> (UpdatePeerPhotoStatus, MediaResource?, MediaResource?) in
|
||||
updatePeers(transaction: transaction, peers: [groupOrChannel], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
return (.complete(groupOrChannel.profileImageRepresentations), photoResult.resource, videoResult?.resource)
|
||||
}
|
||||
|> mapError { _ in return .generic }
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result, resource, videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|
||||
if case .complete = result {
|
||||
return _internal_fetchAndUpdateCachedPeerData(accountPeerId: accountPeerId, peerId: peer.id, network: network, postbox: postbox)
|
||||
|> castError(UploadPeerPhotoError.self)
|
||||
|> mapToSignal { status -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|
||||
return postbox.transaction { transaction in
|
||||
if let videoResource = videoResource {
|
||||
let cachedData = transaction.getPeerCachedData(peerId: peer.id)
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
if let photo = cachedData.photo {
|
||||
for representation in photo.videoRepresentations {
|
||||
postbox.mediaBox.copyResourceData(from: videoResource.id, to: representation.resource.id, synchronous: true)
|
||||
}
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
if let photo = cachedData.photo {
|
||||
for representation in photo.videoRepresentations {
|
||||
postbox.mediaBox.copyResourceData(from: videoResource.id, to: representation.resource.id, synchronous: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|> castError(UploadPeerPhotoError.self)
|
||||
}
|
||||
} else {
|
||||
return .single(result)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let _ = peer as? TelegramUser {
|
||||
let signal: Signal<Api.photos.Photo, UploadPeerPhotoError> = network.request(Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty))
|
||||
|> mapError { _ -> UploadPeerPhotoError in
|
||||
return .generic
|
||||
}
|
||||
|
||||
return signal
|
||||
|> mapToSignal { _ -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|
||||
return .single(.complete([]))
|
||||
}
|
||||
} else {
|
||||
let request: Signal<Api.Updates, MTRpcError>
|
||||
if let peer = peer as? TelegramGroup {
|
||||
request = network.request(Api.functions.messages.editChatPhoto(chatId: peer.id.id._internalGetInt64Value(), photo: .inputChatPhotoEmpty))
|
||||
} else if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
|
||||
request = network.request(Api.functions.channels.editPhoto(channel: inputChannel, photo: .inputChatPhotoEmpty))
|
||||
} else {
|
||||
assertionFailure()
|
||||
request = .complete()
|
||||
}
|
||||
|
||||
return request
|
||||
|> mapError {_ in return UploadPeerPhotoError.generic}
|
||||
|> mapToSignal { updates -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|
||||
stateManager?.addUpdates(updates)
|
||||
for chat in updates.chats {
|
||||
if chat.peerId == peer.id {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
return postbox.transaction { transaction -> UpdatePeerPhotoStatus in
|
||||
updatePeers(transaction: transaction, peers: [groupOrChannel], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
return .complete(groupOrChannel.profileImageRepresentations)
|
||||
}
|
||||
|> mapError { _ in return .generic }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
|
||||
switch reference {
|
||||
case let .cloud(imageId, accessHash, fileReference):
|
||||
return network.request(Api.functions.photos.updateProfilePhoto(id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))))
|
||||
|> `catch` { _ -> Signal<Api.photos.Photo, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> mapToSignal { photo -> Signal<TelegramMediaImage?, NoError> in
|
||||
if case let .photo(photo, _) = photo {
|
||||
return .single(telegramMediaImageFromApiPhoto(photo))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
|
||||
if let reference = reference {
|
||||
switch reference {
|
||||
case let .cloud(imageId, accessHash, fileReference):
|
||||
if let fileReference = fileReference {
|
||||
return network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))]))
|
||||
|> `catch` { _ -> Signal<[Int64], NoError> in
|
||||
return .single([])
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let api = Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty)
|
||||
return network.request(api) |> map { _ in } |> retryRequest
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func _internal_removePeerChat(account: Account, transaction: Transaction, mediaB
|
||||
}
|
||||
})
|
||||
}
|
||||
updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
_internal_updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
var filters = filters
|
||||
for i in 0 ..< filters.count {
|
||||
if filters[i].data.includePeers.peers.contains(peerId) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import SyncCore
|
||||
@@ -327,5 +328,136 @@ public extension TelegramEngine {
|
||||
public func removeRecentlyUsedInlineBot(peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return _internal_removeRecentlyUsedInlineBot(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func uploadedPeerPhoto(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource)
|
||||
}
|
||||
|
||||
public func uploadedPeerVideo(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource)
|
||||
}
|
||||
|
||||
public func updatePeerPhoto(peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
|
||||
}
|
||||
|
||||
public func requestUpdateChatListFilter(id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
return _internal_requestUpdateChatListFilter(postbox: self.account.postbox, network: self.account.network, id: id, filter: filter)
|
||||
}
|
||||
|
||||
public func requestUpdateChatListFilterOrder(ids: [Int32]) -> Signal<Never, RequestUpdateChatListFilterOrderError> {
|
||||
return _internal_requestUpdateChatListFilterOrder(account: self.account, ids: ids)
|
||||
}
|
||||
|
||||
public func generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 {
|
||||
return _internal_generateNewChatListFilterId(filters: filters)
|
||||
}
|
||||
|
||||
public func updateChatListFiltersInteractively(_ f: @escaping ([ChatListFilter]) -> [ChatListFilter]) -> Signal<[ChatListFilter], NoError> {
|
||||
return _internal_updateChatListFiltersInteractively(postbox: self.account.postbox, f)
|
||||
}
|
||||
|
||||
public func updatedChatListFilters() -> Signal<[ChatListFilter], NoError> {
|
||||
return _internal_updatedChatListFilters(postbox: self.account.postbox)
|
||||
}
|
||||
|
||||
public func updatedChatListFiltersInfo() -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> {
|
||||
return _internal_updatedChatListFiltersInfo(postbox: self.account.postbox)
|
||||
}
|
||||
|
||||
public func currentChatListFilters() -> Signal<[ChatListFilter], NoError> {
|
||||
return _internal_currentChatListFilters(postbox: self.account.postbox)
|
||||
}
|
||||
|
||||
public func markChatListFeaturedFiltersAsSeen() -> Signal<Never, NoError> {
|
||||
return _internal_markChatListFeaturedFiltersAsSeen(postbox: self.account.postbox)
|
||||
}
|
||||
|
||||
public func updateChatListFeaturedFilters() -> Signal<Never, NoError> {
|
||||
return _internal_updateChatListFeaturedFilters(postbox: self.account.postbox, network: self.account.network)
|
||||
}
|
||||
|
||||
public func unmarkChatListFeaturedFiltersAsSeen() -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction in
|
||||
_internal_unmarkChatListFeaturedFiltersAsSeen(transaction: transaction)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func checkPeerChatServiceActions(peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return _internal_checkPeerChatServiceActions(postbox: self.account.postbox, peerId: peerId)
|
||||
}
|
||||
|
||||
public func createPeerExportedInvitation(peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
|
||||
return _internal_createPeerExportedInvitation(account: self.account, peerId: peerId, expireDate: expireDate, usageLimit: usageLimit)
|
||||
}
|
||||
|
||||
public func editPeerExportedInvitation(peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> {
|
||||
return _internal_editPeerExportedInvitation(account: self.account, peerId: peerId, link: link, expireDate: expireDate, usageLimit: usageLimit)
|
||||
}
|
||||
|
||||
public func revokePeerExportedInvitation(peerId: PeerId, link: String) -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> {
|
||||
return _internal_revokePeerExportedInvitation(account: self.account, peerId: peerId, link: link)
|
||||
}
|
||||
|
||||
public func deletePeerExportedInvitation(peerId: PeerId, link: String) -> Signal<Never, DeletePeerExportedInvitationError> {
|
||||
return _internal_deletePeerExportedInvitation(account: self.account, peerId: peerId, link: link)
|
||||
}
|
||||
|
||||
public func deleteAllRevokedPeerExportedInvitations(peerId: PeerId, adminId: PeerId) -> Signal<Never, NoError> {
|
||||
return _internal_deleteAllRevokedPeerExportedInvitations(account: self.account, peerId: peerId, adminId: adminId)
|
||||
}
|
||||
|
||||
public func peerExportedInvitationsCreators(peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
|
||||
return _internal_peerExportedInvitationsCreators(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func peerExportedInvitations(peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) -> PeerExportedInvitationsContext {
|
||||
return PeerExportedInvitationsContext(account: self.account, peerId: peerId, adminId: adminId, revoked: revoked, forceUpdate: forceUpdate)
|
||||
}
|
||||
|
||||
public func peerInvitationImporters(peerId: PeerId, invite: ExportedInvitation) -> PeerInvitationImportersContext {
|
||||
return PeerInvitationImportersContext(account: self.account, peerId: peerId, invite: invite)
|
||||
}
|
||||
|
||||
public func notificationExceptionsList() -> Signal<NotificationExceptionsList, NoError> {
|
||||
return _internal_notificationExceptionsList(postbox: self.account.postbox, network: self.account.network)
|
||||
}
|
||||
|
||||
public func fetchAndUpdateCachedPeerData(peerId: PeerId) -> Signal<Bool, NoError> {
|
||||
return _internal_fetchAndUpdateCachedPeerData(accountPeerId: self.account.peerId, peerId: peerId, network: self.account.network, postbox: self.account.postbox)
|
||||
}
|
||||
|
||||
public func toggleItemPinned(location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
|
||||
return _internal_toggleItemPinned(postbox: self.account.postbox, location: location, itemId: itemId)
|
||||
}
|
||||
|
||||
public func getPinnedItemIds(location: TogglePeerChatPinnedLocation) -> Signal<[PinnedItemId], NoError> {
|
||||
return self.account.postbox.transaction { transaction -> [PinnedItemId] in
|
||||
return _internal_getPinnedItemIds(transaction: transaction, location: location)
|
||||
}
|
||||
}
|
||||
|
||||
public func reorderPinnedItemIds(location: TogglePeerChatPinnedLocation, itemIds: [PinnedItemId]) -> Signal<Bool, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Bool in
|
||||
return _internal_reorderPinnedItemIds(transaction: transaction, location: location, itemIds: itemIds)
|
||||
}
|
||||
}
|
||||
|
||||
public func joinChatInteractively(with hash: String) -> Signal <PeerId?, JoinLinkError> {
|
||||
return _internal_joinChatInteractively(with: hash, account: self.account)
|
||||
}
|
||||
|
||||
public func joinLinkInformation(_ hash: String) -> Signal<ExternalJoiningChatState, NoError> {
|
||||
return _internal_joinLinkInformation(hash, account: self.account)
|
||||
}
|
||||
|
||||
public func updatePeerTitle(peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
|
||||
return _internal_updatePeerTitle(account: self.account, peerId: peerId, title: title)
|
||||
}
|
||||
|
||||
public func updatePeerDescription(peerId: PeerId, description: String?) -> Signal<Void, UpdatePeerDescriptionError> {
|
||||
return _internal_updatePeerDescription(account: self.account, peerId: peerId, description: description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum TogglePeerChatPinnedLocation {
|
||||
case group(PeerGroupId)
|
||||
case filter(Int32)
|
||||
}
|
||||
|
||||
public enum TogglePeerChatPinnedResult {
|
||||
case done
|
||||
case limitExceeded(Int)
|
||||
}
|
||||
|
||||
func _internal_toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
|
||||
return postbox.transaction { transaction -> TogglePeerChatPinnedResult in
|
||||
switch location {
|
||||
case let .group(groupId):
|
||||
var itemIds = transaction.getPinnedItemIds(groupId: groupId)
|
||||
let sameKind = itemIds.filter { item in
|
||||
switch itemId {
|
||||
case let .peer(lhsPeerId):
|
||||
if case let .peer(rhsPeerId) = item {
|
||||
return (lhsPeerId.namespace == Namespaces.Peer.SecretChat) == (rhsPeerId.namespace == Namespaces.Peer.SecretChat) && lhsPeerId != rhsPeerId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let additionalCount: Int
|
||||
if let _ = itemIds.firstIndex(of: itemId) {
|
||||
additionalCount = -1
|
||||
} else {
|
||||
additionalCount = 1
|
||||
}
|
||||
|
||||
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
|
||||
let limitCount: Int
|
||||
if case .root = groupId {
|
||||
limitCount = Int(limitsConfiguration.maxPinnedChatCount)
|
||||
} else {
|
||||
limitCount = Int(limitsConfiguration.maxArchivedPinnedChatCount)
|
||||
}
|
||||
|
||||
if sameKind.count + additionalCount > limitCount {
|
||||
return .limitExceeded(limitCount)
|
||||
} else {
|
||||
if let index = itemIds.firstIndex(of: itemId) {
|
||||
itemIds.remove(at: index)
|
||||
} else {
|
||||
itemIds.insert(itemId, at: 0)
|
||||
}
|
||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||
transaction.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
||||
return .done
|
||||
}
|
||||
case let .filter(filterId):
|
||||
var result: TogglePeerChatPinnedResult = .done
|
||||
_internal_updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == filterId }) {
|
||||
switch itemId {
|
||||
case let .peer(peerId):
|
||||
if filters[index].data.includePeers.pinnedPeers.contains(peerId) {
|
||||
filters[index].data.includePeers.removePinnedPeer(peerId)
|
||||
} else {
|
||||
if !filters[index].data.includePeers.addPinnedPeer(peerId) {
|
||||
result = .limitExceeded(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_getPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation) -> [PinnedItemId] {
|
||||
switch location {
|
||||
case let .group(groupId):
|
||||
return transaction.getPinnedItemIds(groupId: groupId)
|
||||
case let .filter(filterId):
|
||||
var itemIds: [PinnedItemId] = []
|
||||
let _ = _internal_updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
if let index = filters.firstIndex(where: { $0.id == filterId }) {
|
||||
itemIds = filters[index].data.includePeers.pinnedPeers.map { peerId in
|
||||
return .peer(peerId)
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
return itemIds
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_reorderPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation, itemIds: [PinnedItemId]) -> Bool {
|
||||
switch location {
|
||||
case let .group(groupId):
|
||||
if transaction.getPinnedItemIds(groupId: groupId) != itemIds {
|
||||
transaction.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .filter(filterId):
|
||||
var result: Bool = false
|
||||
_internal_updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == filterId }) {
|
||||
let peerIds: [PeerId] = itemIds.map { itemId -> PeerId in
|
||||
switch itemId {
|
||||
case let .peer(peerId):
|
||||
return peerId
|
||||
}
|
||||
}
|
||||
|
||||
if filters[index].data.includePeers.pinnedPeers != peerIds {
|
||||
filters[index].data.includePeers.reorderPinnedPeers(peerIds)
|
||||
result = true
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
import SwiftSignalKit
|
||||
import SyncCore
|
||||
|
||||
func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal<Bool, NoError> {
|
||||
return postbox.combinedView(keys: [.basicPeer(rawPeerId)])
|
||||
|> mapToSignal { views -> Signal<Peer, NoError> in
|
||||
guard let view = views.views[.basicPeer(rawPeerId)] as? BasicPeerView else {
|
||||
return .complete()
|
||||
}
|
||||
guard let peer = view.peer else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(peer)
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
||||
return postbox.transaction { transaction -> Signal<Bool, NoError> in
|
||||
guard let rawPeer = transaction.getPeer(rawPeerId) else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
let peer: Peer
|
||||
if let secretChat = rawPeer as? TelegramSecretChat {
|
||||
guard let user = transaction.getPeer(secretChat.regularPeerId) else {
|
||||
return .single(false)
|
||||
}
|
||||
peer = user
|
||||
} else {
|
||||
peer = rawPeer
|
||||
}
|
||||
|
||||
let cachedData = transaction.getPeerCachedData(peerId: peer.id)
|
||||
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
if cachedData.peerStatusSettings != nil {
|
||||
return .single(true)
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
if cachedData.peerStatusSettings != nil {
|
||||
return .single(true)
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedChannelData {
|
||||
if cachedData.peerStatusSettings != nil {
|
||||
return .single(true)
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedSecretChatData {
|
||||
if cachedData.peerStatusSettings != nil {
|
||||
return .single(true)
|
||||
}
|
||||
}
|
||||
|
||||
if peer.id.namespace == Namespaces.Peer.SecretChat {
|
||||
return postbox.transaction { transaction -> Bool in
|
||||
var peerStatusSettings: PeerStatusSettings
|
||||
if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) {
|
||||
if let peer = peer as? TelegramSecretChat, case .creator = peer.role {
|
||||
peerStatusSettings = PeerStatusSettings(flags: [])
|
||||
} else {
|
||||
peerStatusSettings = PeerStatusSettings(flags: [.canReport])
|
||||
}
|
||||
} else {
|
||||
peerStatusSettings = PeerStatusSettings(flags: [])
|
||||
}
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in
|
||||
if let current = current as? CachedSecretChatData {
|
||||
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
} else {
|
||||
return CachedSecretChatData(peerStatusSettings: peerStatusSettings)
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
} else if let inputPeer = apiInputPeer(peer) {
|
||||
return network.request(Api.functions.messages.getPeerSettings(peer: inputPeer))
|
||||
|> retryRequest
|
||||
|> mapToSignal { peerSettings -> Signal<Bool, NoError> in
|
||||
let peerStatusSettings = PeerStatusSettings(apiSettings: peerSettings)
|
||||
|
||||
return postbox.transaction { transaction -> Bool in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
|
||||
switch peer.id.namespace {
|
||||
case Namespaces.Peer.CloudUser:
|
||||
let previous: CachedUserData
|
||||
if let current = current as? CachedUserData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedUserData()
|
||||
}
|
||||
return previous.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
case Namespaces.Peer.CloudGroup:
|
||||
let previous: CachedGroupData
|
||||
if let current = current as? CachedGroupData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedGroupData()
|
||||
}
|
||||
return previous.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
case Namespaces.Peer.CloudChannel:
|
||||
let previous: CachedChannelData
|
||||
if let current = current as? CachedChannelData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedChannelData()
|
||||
}
|
||||
return previous.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return current
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal<Bool, NoError> {
|
||||
return postbox.combinedView(keys: [.basicPeer(rawPeerId)])
|
||||
|> mapToSignal { views -> Signal<Bool, NoError> in
|
||||
if accountPeerId == rawPeerId {
|
||||
return .single(true)
|
||||
}
|
||||
guard let view = views.views[.basicPeer(rawPeerId)] as? BasicPeerView else {
|
||||
return .complete()
|
||||
}
|
||||
guard let _ = view.peer else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(true)
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
||||
return postbox.transaction { transaction -> (Api.InputUser?, Peer?, PeerId) in
|
||||
guard let rawPeer = transaction.getPeer(rawPeerId) else {
|
||||
if rawPeerId == accountPeerId {
|
||||
return (.inputUserSelf, transaction.getPeer(rawPeerId), rawPeerId)
|
||||
} else {
|
||||
return (nil, nil, rawPeerId)
|
||||
}
|
||||
}
|
||||
|
||||
let peer: Peer
|
||||
if let secretChat = rawPeer as? TelegramSecretChat {
|
||||
guard let user = transaction.getPeer(secretChat.regularPeerId) else {
|
||||
return (nil, nil, rawPeerId)
|
||||
}
|
||||
peer = user
|
||||
} else {
|
||||
peer = rawPeer
|
||||
}
|
||||
|
||||
if rawPeerId == accountPeerId {
|
||||
return (.inputUserSelf, transaction.getPeer(rawPeerId), rawPeerId)
|
||||
} else {
|
||||
return (apiInputUser(peer), peer, peer.id)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { inputUser, maybePeer, peerId -> Signal<Bool, NoError> in
|
||||
if let inputUser = inputUser {
|
||||
return network.request(Api.functions.users.getFullUser(id: inputUser))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<Bool, NoError> in
|
||||
return postbox.transaction { transaction -> Bool in
|
||||
switch result {
|
||||
case let .userFull(userFull):
|
||||
if let telegramUser = TelegramUser.merge(transaction.getPeer(userFull.user.peerId) as? TelegramUser, rhs: userFull.user) {
|
||||
updatePeers(transaction: transaction, peers: [telegramUser], update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFull.notifySettings)])
|
||||
if let presence = TelegramUserPresence(apiUser: userFull.user) {
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: [userFull.user.peerId: presence])
|
||||
}
|
||||
}
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, current in
|
||||
let previous: CachedUserData
|
||||
if let current = current as? CachedUserData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedUserData()
|
||||
}
|
||||
switch result {
|
||||
case let .userFull(userFull):
|
||||
let botInfo = userFull.botInfo.flatMap(BotInfo.init(apiBotInfo:))
|
||||
let isBlocked = (userFull.flags & (1 << 0)) != 0
|
||||
let voiceCallsAvailable = (userFull.flags & (1 << 4)) != 0
|
||||
var videoCallsAvailable = (userFull.flags & (1 << 13)) != 0
|
||||
#if DEBUG
|
||||
videoCallsAvailable = true
|
||||
#endif
|
||||
let callsPrivate = (userFull.flags & (1 << 5)) != 0
|
||||
let canPinMessages = (userFull.flags & (1 << 7)) != 0
|
||||
let pinnedMessageId = userFull.pinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })
|
||||
|
||||
let peerStatusSettings = PeerStatusSettings(apiSettings: userFull.settings)
|
||||
|
||||
let hasScheduledMessages = (userFull.flags & 1 << 12) != 0
|
||||
|
||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(userFull.ttlPeriod))
|
||||
|
||||
return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
return network.request(Api.functions.messages.getFullChat(chatId: peerId.id._internalGetInt64Value()))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<Bool, NoError> in
|
||||
return postbox.transaction { transaction -> Bool in
|
||||
switch result {
|
||||
case let .chatFull(fullChat, chats, users):
|
||||
switch fullChat {
|
||||
case let .chatFull(chatFull):
|
||||
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: chatFull.notifySettings)])
|
||||
case .channelFull:
|
||||
break
|
||||
}
|
||||
|
||||
switch fullChat {
|
||||
case let .chatFull(chatFull):
|
||||
var botInfos: [CachedPeerBotInfo] = []
|
||||
for botInfo in chatFull.botInfo ?? [] {
|
||||
switch botInfo {
|
||||
case let .botInfo(userId, _, _):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
let parsedBotInfo = BotInfo(apiBotInfo: botInfo)
|
||||
botInfos.append(CachedPeerBotInfo(peerId: peerId, botInfo: parsedBotInfo))
|
||||
}
|
||||
}
|
||||
let participants = CachedGroupParticipants(apiParticipants: chatFull.participants)
|
||||
|
||||
var invitedBy: PeerId?
|
||||
if let participants = participants {
|
||||
for participant in participants.participants {
|
||||
if participant.peerId == accountPeerId {
|
||||
if participant.invitedBy != accountPeerId {
|
||||
invitedBy = participant.invitedBy
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let photo: TelegramMediaImage? = chatFull.chatPhoto.flatMap(telegramMediaImageFromApiPhoto)
|
||||
|
||||
let exportedInvitation = chatFull.exportedInvite.flatMap { ExportedInvitation(apiExportedInvite: $0) }
|
||||
let pinnedMessageId = chatFull.pinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })
|
||||
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
for user in users {
|
||||
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
|
||||
var flags = CachedGroupFlags()
|
||||
if (chatFull.flags & 1 << 7) != 0 {
|
||||
flags.insert(.canChangeUsername)
|
||||
}
|
||||
|
||||
var hasScheduledMessages = false
|
||||
if (chatFull.flags & 1 << 8) != 0 {
|
||||
hasScheduledMessages = true
|
||||
}
|
||||
|
||||
let groupCallDefaultJoinAs = chatFull.groupcallDefaultJoinAs
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
||||
let previous: CachedGroupData
|
||||
if let current = current as? CachedGroupData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedGroupData()
|
||||
}
|
||||
|
||||
var updatedActiveCall: CachedChannelData.ActiveCall?
|
||||
if let inputCall = chatFull.call {
|
||||
switch inputCall {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false)
|
||||
}
|
||||
}
|
||||
|
||||
return previous.withUpdatedParticipants(participants)
|
||||
.withUpdatedExportedInvitation(exportedInvitation)
|
||||
.withUpdatedBotInfos(botInfos)
|
||||
.withUpdatedPinnedMessageId(pinnedMessageId)
|
||||
.withUpdatedAbout(chatFull.about)
|
||||
.withUpdatedFlags(flags)
|
||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||
.withUpdatedInvitedBy(invitedBy)
|
||||
.withUpdatedPhoto(photo)
|
||||
.withUpdatedActiveCall(updatedActiveCall)
|
||||
.withUpdatedCallJoinPeerId(groupCallDefaultJoinAs?.peerId)
|
||||
})
|
||||
case .channelFull:
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if let inputChannel = maybePeer.flatMap(apiInputChannel) {
|
||||
let fullChannelSignal = network.request(Api.functions.channels.getFullChannel(channel: inputChannel))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.messages.ChatFull?, NoError> in
|
||||
if error.errorDescription == "CHANNEL_PRIVATE" {
|
||||
return .single(nil)
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
let participantSignal = network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: .inputPeerSelf))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.channels.ChannelParticipant?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
return combineLatest(fullChannelSignal, participantSignal)
|
||||
|> mapToSignal { result, participantResult -> Signal<Bool, NoError> in
|
||||
return postbox.transaction { transaction -> Bool in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .chatFull(fullChat, chats, users):
|
||||
switch fullChat {
|
||||
case let .channelFull(channelFull):
|
||||
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: channelFull.notifySettings)])
|
||||
case .chatFull:
|
||||
break
|
||||
}
|
||||
|
||||
switch fullChat {
|
||||
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs):
|
||||
var channelFlags = CachedChannelFlags()
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
channelFlags.insert(.canDisplayParticipants)
|
||||
}
|
||||
if (flags & (1 << 6)) != 0 {
|
||||
channelFlags.insert(.canChangeUsername)
|
||||
}
|
||||
if (flags & (1 << 10)) == 0 {
|
||||
channelFlags.insert(.preHistoryEnabled)
|
||||
}
|
||||
if (flags & (1 << 20)) != 0 {
|
||||
channelFlags.insert(.canViewStats)
|
||||
}
|
||||
if (flags & (1 << 7)) != 0 {
|
||||
channelFlags.insert(.canSetStickerSet)
|
||||
}
|
||||
if (flags & (1 << 16)) != 0 {
|
||||
channelFlags.insert(.canChangePeerGeoLocation)
|
||||
}
|
||||
|
||||
let linkedDiscussionPeerId: PeerId?
|
||||
if let linkedChatId = linkedChatId, linkedChatId != 0 {
|
||||
linkedDiscussionPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(Int64(linkedChatId)))
|
||||
} else {
|
||||
linkedDiscussionPeerId = nil
|
||||
}
|
||||
|
||||
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(ttl))
|
||||
|
||||
let peerGeoLocation: PeerGeoLocation?
|
||||
if let location = location {
|
||||
peerGeoLocation = PeerGeoLocation(apiLocation: location)
|
||||
} else {
|
||||
peerGeoLocation = nil
|
||||
}
|
||||
|
||||
var botInfos: [CachedPeerBotInfo] = []
|
||||
for botInfo in apiBotInfos {
|
||||
switch botInfo {
|
||||
case let .botInfo(userId, _, _):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
let parsedBotInfo = BotInfo(apiBotInfo: botInfo)
|
||||
botInfos.append(CachedPeerBotInfo(peerId: peerId, botInfo: parsedBotInfo))
|
||||
}
|
||||
}
|
||||
|
||||
var pinnedMessageId: MessageId?
|
||||
if let pinnedMsgId = pinnedMsgId {
|
||||
pinnedMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: pinnedMsgId)
|
||||
}
|
||||
|
||||
var minAvailableMessageId: MessageId?
|
||||
if let minAvailableMsgId = minAvailableMsgId {
|
||||
minAvailableMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minAvailableMsgId)
|
||||
|
||||
if let pinnedMsgId = pinnedMsgId, pinnedMsgId < minAvailableMsgId {
|
||||
pinnedMessageId = nil
|
||||
}
|
||||
}
|
||||
|
||||
var migrationReference: ChannelMigrationReference?
|
||||
if let migratedFromChatId = migratedFromChatId, let migratedFromMaxId = migratedFromMaxId {
|
||||
migrationReference = ChannelMigrationReference(maxMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(migratedFromChatId)), namespace: Namespaces.Message.Cloud, id: migratedFromMaxId))
|
||||
}
|
||||
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
for user in users {
|
||||
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let participantResult = participantResult {
|
||||
switch participantResult {
|
||||
case let .channelParticipant(_, chats, users):
|
||||
for user in users {
|
||||
if let telegramUser = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
}
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
|
||||
let stickerPack: StickerPackCollectionInfo? = stickerSet.flatMap { apiSet -> StickerPackCollectionInfo in
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
switch apiSet {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
namespace = Namespaces.ItemCollection.CloudMaskPacks
|
||||
} else {
|
||||
namespace = Namespaces.ItemCollection.CloudStickerPacks
|
||||
}
|
||||
}
|
||||
|
||||
return StickerPackCollectionInfo(apiSet: apiSet, namespace: namespace)
|
||||
}
|
||||
|
||||
var hasScheduledMessages = false
|
||||
if (flags & (1 << 19)) != 0 {
|
||||
hasScheduledMessages = true
|
||||
}
|
||||
|
||||
var invitedBy: PeerId?
|
||||
if let participantResult = participantResult {
|
||||
switch participantResult {
|
||||
case let.channelParticipant(participant, _, _):
|
||||
switch participant {
|
||||
case let .channelParticipantSelf(_, inviterId, _):
|
||||
invitedBy = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let photo = telegramMediaImageFromApiPhoto(chatPhoto)
|
||||
|
||||
var minAvailableMessageIdUpdated = false
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
||||
var previous: CachedChannelData
|
||||
if let current = current as? CachedChannelData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedChannelData()
|
||||
}
|
||||
|
||||
previous = previous.withUpdatedIsNotAccessible(false)
|
||||
|
||||
minAvailableMessageIdUpdated = previous.minAvailableMessageId != minAvailableMessageId
|
||||
|
||||
var updatedActiveCall: CachedChannelData.ActiveCall?
|
||||
if let inputCall = inputCall {
|
||||
switch inputCall {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false)
|
||||
}
|
||||
}
|
||||
|
||||
return previous.withUpdatedFlags(channelFlags)
|
||||
.withUpdatedAbout(about)
|
||||
.withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: participantsCount, adminCount: adminsCount, bannedCount: bannedCount, kickedCount: kickedCount))
|
||||
.withUpdatedExportedInvitation(apiExportedInvite.flatMap { ExportedInvitation(apiExportedInvite: $0) })
|
||||
.withUpdatedBotInfos(botInfos)
|
||||
.withUpdatedPinnedMessageId(pinnedMessageId)
|
||||
.withUpdatedStickerPack(stickerPack)
|
||||
.withUpdatedMinAvailableMessageId(minAvailableMessageId)
|
||||
.withUpdatedMigrationReference(migrationReference)
|
||||
.withUpdatedLinkedDiscussionPeerId(.known(linkedDiscussionPeerId))
|
||||
.withUpdatedPeerGeoLocation(peerGeoLocation)
|
||||
.withUpdatedSlowModeTimeout(slowmodeSeconds)
|
||||
.withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate)
|
||||
.withUpdatedHasScheduledMessages(hasScheduledMessages)
|
||||
.withUpdatedStatsDatacenterId(statsDc ?? 0)
|
||||
.withUpdatedInvitedBy(invitedBy)
|
||||
.withUpdatedPhoto(photo)
|
||||
.withUpdatedActiveCall(updatedActiveCall)
|
||||
.withUpdatedCallJoinPeerId(groupcallDefaultJoinAs?.peerId)
|
||||
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
|
||||
.withUpdatedPendingSuggestions(pendingSuggestions ?? [])
|
||||
})
|
||||
|
||||
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {
|
||||
var resourceIds: [WrappedMediaResourceId] = []
|
||||
transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id, forEachMedia: { media in
|
||||
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
|
||||
})
|
||||
if !resourceIds.isEmpty {
|
||||
let _ = postbox.mediaBox.removeCachedResources(Set(resourceIds)).start()
|
||||
}
|
||||
}
|
||||
case .chatFull:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, _ in
|
||||
var updated = CachedChannelData()
|
||||
updated = updated.withUpdatedIsNotAccessible(true)
|
||||
return updated
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CachedPeerAutoremoveTimeout.Value {
|
||||
init?(_ apiValue: Int32?) {
|
||||
if let value = apiValue {
|
||||
self.init(peerValue: value)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum UpdatePeerTitleError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerTitleError> in
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.editTitle(channel: inputChannel, title: title))
|
||||
|> mapError { _ -> UpdatePeerTitleError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, UpdatePeerTitleError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let apiChat = apiUpdatesGroups(result).first, let updatedPeer = parseTelegramGroupOrChannel(chat: apiChat) {
|
||||
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
} |> mapError { _ -> UpdatePeerTitleError in }
|
||||
}
|
||||
} else if let peer = peer as? TelegramGroup {
|
||||
return account.network.request(Api.functions.messages.editChatTitle(chatId: peer.id.id._internalGetInt64Value(), title: title))
|
||||
|> mapError { _ -> UpdatePeerTitleError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, UpdatePeerTitleError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let apiChat = apiUpdatesGroups(result).first, let updatedPeer = parseTelegramGroupOrChannel(chat: apiChat) {
|
||||
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
} |> mapError { _ -> UpdatePeerTitleError in return .generic }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} |> mapError { _ -> UpdatePeerTitleError in return .generic } |> switchToLatest
|
||||
}
|
||||
|
||||
public enum UpdatePeerDescriptionError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_updatePeerDescription(account: Account, peerId: PeerId, description: String?) -> Signal<Void, UpdatePeerDescriptionError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerDescriptionError> in
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
if (peer is TelegramChannel || peer is TelegramGroup), let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.editChatAbout(peer: inputPeer, about: description ?? ""))
|
||||
|> mapError { _ -> UpdatePeerDescriptionError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, UpdatePeerDescriptionError> in
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if case .boolTrue = result {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedAbout(description)
|
||||
} else if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedAbout(description)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> UpdatePeerDescriptionError in return .generic }
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} |> mapError { _ -> UpdatePeerDescriptionError in return .generic } |> switchToLatest
|
||||
}
|
||||
Reference in New Issue
Block a user