Merge branch 'master' into experimental-2

# Conflicts:
#	submodules/ChatListUI/Sources/ChatContextMenus.swift
#	submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift
This commit is contained in:
Ali
2021-07-18 01:10:29 +02:00
139 changed files with 3459 additions and 1015 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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