Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,95 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
public class AdPeer: Equatable {
public let opaqueId: Data
public let peer: EnginePeer
public let subscribers: Int32?
public let sponsorInfo: String?
public let additionalInfo: String?
public init(opaqueId: Data, peer: EnginePeer, subscribers: Int32?, sponsorInfo: String?, additionalInfo: String?) {
self.opaqueId = opaqueId
self.peer = peer
self.subscribers = subscribers
self.sponsorInfo = sponsorInfo
self.additionalInfo = additionalInfo
}
public static func ==(lhs: AdPeer, rhs: AdPeer) -> Bool {
if lhs.opaqueId != rhs.opaqueId {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.subscribers != rhs.subscribers {
return false
}
if lhs.sponsorInfo != rhs.sponsorInfo {
return false
}
if lhs.additionalInfo != rhs.additionalInfo {
return false
}
return true
}
}
func _internal_searchAdPeers(account: Account, query: String) -> Signal<[AdPeer], NoError> {
return account.network.request(Api.functions.contacts.getSponsoredPeers(q: query))
|> map(Optional.init)
|> `catch` { _ in
return .single(nil)
}
|> mapToSignal { result in
guard let result else {
return .single([])
}
return account.postbox.transaction { transaction -> [AdPeer] in
switch result {
case let .sponsoredPeers(peers, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var subscribers: [PeerId: Int32] = [:]
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _):
if let participantsCount = participantsCount {
subscribers[groupOrChannel.id] = participantsCount
}
default:
break
}
}
}
var result: [AdPeer] = []
for peer in peers {
switch peer {
case let .sponsoredPeer(_, randomId, apiPeer, sponsorInfo, additionalInfo):
guard let peer = parsedPeers.get(apiPeer.peerId) else {
continue
}
result.append(
AdPeer(
opaqueId: randomId.makeData(),
peer: EnginePeer(peer),
subscribers: subscribers[peer.id],
sponsorInfo: sponsorInfo,
additionalInfo: additionalInfo
)
)
}
}
return result
default:
return []
}
}
}
}
@@ -0,0 +1,371 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum AddGroupMemberError {
case generic
case groupFull
case privacy(TelegramInvitePeersResult?)
case notMutualContact
case tooManyChannels
}
public final class TelegramForbiddenInvitePeer: Equatable {
public let peer: EnginePeer
public let canInviteWithPremium: Bool
public let premiumRequiredToContact: Bool
public init(peer: EnginePeer, canInviteWithPremium: Bool, premiumRequiredToContact: Bool) {
self.peer = peer
self.canInviteWithPremium = canInviteWithPremium
self.premiumRequiredToContact = premiumRequiredToContact
}
public static func ==(lhs: TelegramForbiddenInvitePeer, rhs: TelegramForbiddenInvitePeer) -> Bool {
if lhs === rhs {
return true
}
if lhs.peer != rhs.peer {
return false
}
if lhs.canInviteWithPremium != rhs.canInviteWithPremium {
return false
}
if lhs.premiumRequiredToContact != rhs.premiumRequiredToContact {
return false
}
return true
}
}
public final class TelegramInvitePeersResult {
public let forbiddenPeers: [TelegramForbiddenInvitePeer]
public init(forbiddenPeers: [TelegramForbiddenInvitePeer]) {
self.forbiddenPeers = forbiddenPeers
}
}
func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<Void, AddGroupMemberError> {
return account.postbox.transaction { transaction -> Signal<Void, AddGroupMemberError> in
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
if let group = peer as? TelegramGroup {
return account.network.request(Api.functions.messages.addChatUser(chatId: group.id.id._internalGetInt64Value(), userId: inputUser, fwdLimit: 100))
|> `catch` { error -> Signal<Api.messages.InvitedUsers, AddGroupMemberError> in
switch error.errorDescription {
case "USERS_TOO_MUCH":
return .fail(.groupFull)
case "USER_PRIVACY_RESTRICTED":
return .fail(.privacy(nil))
case "USER_CHANNELS_TOO_MUCH":
return .fail(.tooManyChannels)
case "USER_NOT_MUTUAL_CONTACT":
return .fail(.privacy(nil))
default:
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result {
case let .invitedUsers(updates, missingInvitees):
updatesValue = updates
missingInviteesValue = missingInvitees
}
account.stateManager.addUpdates(updatesValue)
return account.postbox.transaction { transaction -> TelegramInvitePeersResult in
if let message = updatesValue.messages.first, let timestamp = message.timestamp {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants {
var updatedParticipants = participants.participants
var found = false
for participant in participants.participants {
if participant.peerId == memberId {
found = true
break
}
}
if !found {
updatedParticipants.append(.member(id: memberId, invitedBy: account.peerId, invitedAt: timestamp))
}
return cachedData.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version))
} else {
return cachedData
}
})
}
let result = TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee {
case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
return nil
}
return TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)
}
})
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: result.forbiddenPeers.map { $0.peer.id }).startStandalone()
return result
}
|> mapError { _ -> AddGroupMemberError in }
|> mapToSignal { result -> Signal<Void, AddGroupMemberError> in
if result.forbiddenPeers.isEmpty {
return .single(Void())
} else {
return .fail(.privacy(result))
}
}
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
} |> mapError { _ -> AddGroupMemberError in } |> switchToLatest
}
public enum AddChannelMemberError {
case generic
case restricted(TelegramForbiddenInvitePeer?)
case notMutualContact
case limitExceeded
case tooMuchJoined
case bot(PeerId)
case botDoesntSupportGroups
case tooMuchBots
case kicked
}
func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> {
return _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId)
|> mapError { error -> AddChannelMemberError in
}
|> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in
return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
let updatedParticipant: ChannelParticipant
if let currentParticipant = currentParticipant, case let .member(_, invitedAt, adminInfo, _, rank, subscriptionUntilDate) = currentParticipant {
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: nil, rank: rank, subscriptionUntilDate: subscriptionUntilDate)
} else {
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil)
}
return account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: [inputUser]))
|> `catch` { error -> Signal<Api.messages.InvitedUsers, AddChannelMemberError> in
switch error.errorDescription {
case "USER_CHANNELS_TOO_MUCH":
return .fail(.tooMuchJoined)
case "USERS_TOO_MUCH":
return .fail(.limitExceeded)
case "USER_PRIVACY_RESTRICTED":
return .fail(.restricted(nil))
case "USER_NOT_MUTUAL_CONTACT":
return .fail(.notMutualContact)
case "USER_BOT":
return .fail(.bot(memberId))
case "BOT_GROUPS_BLOCKED":
return .fail(.botDoesntSupportGroups)
case "BOTS_TOO_MUCH":
return .fail(.tooMuchBots)
case "USER_KICKED":
return .fail(.kicked)
default:
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), AddChannelMemberError> in
let updatesValue: Api.Updates
switch result {
case let .invitedUsers(updates, missingInvitees):
if case let .missingInvitee(flags, _) = missingInvitees.first {
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: [memberPeer.id]).startStandalone()
return .fail(.restricted(TelegramForbiddenInvitePeer(
peer: EnginePeer(memberPeer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)))
}
updatesValue = updates
}
account.stateManager.addUpdates(updatesValue)
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount, let kickedCount = cachedData.participantsSummary.kickedCount {
var updatedMemberCount = memberCount
var updatedKickedCount = kickedCount
var wasMember = false
var wasBanned = false
if let currentParticipant = currentParticipant {
switch currentParticipant {
case .creator:
break
case let .member(_, _, _, banInfo, _, _):
if let banInfo = banInfo {
wasBanned = true
wasMember = !banInfo.rights.flags.contains(.banReadMessages)
} else {
wasMember = true
}
}
}
if !wasMember {
updatedMemberCount = updatedMemberCount + 1
}
if wasBanned {
updatedKickedCount = max(0, updatedKickedCount - 1)
}
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedMemberCount(updatedMemberCount).withUpdatedKickedCount(updatedKickedCount))
} else {
return cachedData
}
})
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
peers[memberPeer.id] = memberPeer
if let presence = transaction.getPeerPresence(peerId: memberPeer.id) {
presences[memberPeer.id] = presence
}
if case let .member(_, _, maybeAdminInfo, _, _, _) = updatedParticipant {
if let adminInfo = maybeAdminInfo {
if let peer = transaction.getPeer(adminInfo.promotedBy) {
peers[peer.id] = peer
}
}
}
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences))
}
|> mapError { _ -> AddChannelMemberError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> AddChannelMemberError in }
|> switchToLatest
}
}
func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal<TelegramInvitePeersResult, AddChannelMemberError> {
let signal = account.postbox.transaction { transaction -> Signal<TelegramInvitePeersResult, AddChannelMemberError> in
var memberPeerIds: [PeerId:Peer] = [:]
var inputUsers: [Api.InputUser] = []
for memberId in memberIds {
if let peer = transaction.getPeer(memberId) {
memberPeerIds[peerId] = peer
if let inputUser = apiInputUser(peer) {
inputUsers.append(inputUser)
}
}
}
if let peer = transaction.getPeer(peerId), let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
let signal: Signal<TelegramInvitePeersResult, AddChannelMemberError> = account.network.request(Api.functions.channels.inviteToChannel(channel: inputChannel, users: inputUsers))
|> mapError { error -> AddChannelMemberError in
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":
return .tooMuchJoined
case "USER_PRIVACY_RESTRICTED":
return .restricted(nil)
case "USER_NOT_MUTUAL_CONTACT":
return .notMutualContact
case "USERS_TOO_MUCH":
return .limitExceeded
case "USER_KICKED":
return .kicked
default:
return .generic
}
}
|> mapToQueue { result -> Signal<TelegramInvitePeersResult, AddChannelMemberError> in
let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result {
case let .invitedUsers(updates, missingInvitees):
updatesValue = updates
missingInviteesValue = missingInvitees
}
account.stateManager.addUpdates(updatesValue)
account.viewTracker.forceUpdateCachedPeerData(peerId: peerId)
return account.postbox.transaction { transaction -> TelegramInvitePeersResult in
let result = TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee {
case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
return nil
}
return TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)
}
})
let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: result.forbiddenPeers.map { $0.peer.id }).startStandalone()
return result
}
|> castError(AddChannelMemberError.self)
}
return signal
} else {
return .fail(.generic)
}
}
|> castError(AddChannelMemberError.self)
return signal
|> switchToLatest
}
public enum SendBotRequestedPeerError {
case generic
}
func _internal_sendBotRequestedPeer(account: Account, peerId: PeerId, messageId: MessageId, buttonId: Int32, requestedPeerIds: [PeerId]) -> Signal<Void, SendBotRequestedPeerError> {
return account.postbox.transaction { transaction -> Signal<Void, SendBotRequestedPeerError> in
if let peer = transaction.getPeer(peerId) {
var inputRequestedPeers: [Api.InputPeer] = []
for requestedPeerId in requestedPeerIds {
if let requestedPeer = transaction.getPeer(requestedPeerId), let inputRequestedPeer = apiInputPeer(requestedPeer) {
inputRequestedPeers.append(inputRequestedPeer)
}
}
if let inputPeer = apiInputPeer(peer), !inputRequestedPeers.isEmpty {
let signal = account.network.request(Api.functions.messages.sendBotRequestedPeer(peer: inputPeer, msgId: messageId.id, buttonId: buttonId, requestedPeers: inputRequestedPeers))
|> mapError { error -> SendBotRequestedPeerError in
return .generic
}
|> map { result in
account.stateManager.addUpdates(result)
}
return signal
}
}
return .single(Void())
}
|> castError(SendBotRequestedPeerError.self)
|> switchToLatest
}
@@ -0,0 +1,772 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum AddressNameFormatError {
case startsWithUnderscore
case endsWithUnderscore
case startsWithDigit
case tooShort
case invalidCharacters
}
public enum AddressNameAvailability: Equatable {
case available
case invalid
case taken
case purchaseAvailable
}
public enum AddressNameDomain {
case account
case peer(PeerId)
case bot(PeerId)
case theme(TelegramTheme)
}
func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -> AddressNameFormatError? {
var index = 0
let length = value.count
for char in value {
if char == "_" {
if index == 0 {
return .startsWithUnderscore
} else if index == length - 1 {
return length < 4 ? .tooShort : .endsWithUnderscore
}
}
if index == 0 && char >= "0" && char <= "9" {
return .startsWithDigit
}
if (!((char >= "a" && char <= "z") || (char >= "A" && char <= "Z") || (char >= "0" && char <= "9") || char == "_")) {
return .invalidCharacters
}
index += 1
}
if length < 4 && (!canEmpty || length != 0) {
return .tooShort
}
return nil
}
func _internal_addressNameAvailability(account: Account, domain: AddressNameDomain, name: String) -> Signal<AddressNameAvailability, NoError> {
return account.postbox.transaction { transaction -> Signal<AddressNameAvailability, NoError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.checkUsername(username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid)
}
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid)
}
}
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name))
|> map { result -> AddressNameAvailability in
switch result {
case .boolTrue:
return .available
case .boolFalse:
return .taken
}
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid)
}
}
} else {
return .single(.invalid)
}
case .bot:
return .single(.invalid)
case .theme:
return account.network.request(Api.functions.account.createTheme(flags: 0, slug: name, title: "", document: .inputDocumentEmpty, settings: nil))
|> map { _ -> AddressNameAvailability in
return .available
}
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "THEME_SLUG_OCCUPIED" {
return .single(.taken)
} else if error.errorDescription == "THEME_SLUG_INVALID" {
return .single(.invalid)
} else {
return .single(.available)
}
}
}
} |> switchToLatest
}
public enum UpdateAddressNameError {
case generic
}
func _internal_updateAddressName(account: Account, domain: AddressNameDomain, name: String?) -> Signal<Void, UpdateAddressNameError> {
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> Signal<Void, UpdateAddressNameError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.updateUsername(username: name ?? ""), automaticFloodWait: false)
|> mapError { _ -> UpdateAddressNameError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateAddressNameError> in
return account.postbox.transaction { transaction -> Void in
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: [result]))
} |> mapError { _ -> UpdateAddressNameError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.updateUsername(channel: inputChannel, username: name ?? ""), automaticFloodWait: false)
|> mapError { _ -> UpdateAddressNameError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateAddressNameError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result {
if let peer = transaction.getPeer(peerId) as? TelegramChannel {
var updatedPeer = peer.withUpdatedAddressName(name)
if name != nil, let defaultBannedRights = updatedPeer.defaultBannedRights {
updatedPeer = updatedPeer.withUpdatedDefaultBannedRights(TelegramChatBannedRights(flags: defaultBannedRights.flags.union([.banPinMessages, .banChangeInfo]), untilDate: Int32.max))
}
updatePeersCustom(transaction: transaction, peers: [updatedPeer], update: { _, updated in
updated
})
}
}
} |> mapError { _ -> UpdateAddressNameError in }
}
} else {
return .fail(.generic)
}
case .bot:
return .fail(.generic)
case let .theme(theme):
let flags: Int32 = 1 << 0
return account.network.request(Api.functions.account.updateTheme(flags: flags, format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: nil, title: nil, document: nil, settings: nil))
|> mapError { _ -> UpdateAddressNameError in
return .generic
}
|> map { _ in
return Void()
}
}
} |> mapError { _ -> UpdateAddressNameError in } |> switchToLatest
}
public enum DeactivateAllAddressNamesError {
case generic
}
func _internal_deactivateAllAddressNames(account: Account, peerId: EnginePeer.Id) -> Signal<Never, DeactivateAllAddressNamesError> {
return account.postbox.transaction { transaction -> Signal<Never, DeactivateAllAddressNamesError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.deactivateAllUsernames(channel: inputChannel), automaticFloodWait: false)
|> mapError { _ -> DeactivateAllAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Never, DeactivateAllAddressNamesError> in
return account.postbox.transaction { transaction -> Signal<Void, DeactivateAllAddressNamesError> in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
var updatedNames: [TelegramPeerUsername] = []
for username in peer.usernames {
var updatedFlags = username.flags
updatedFlags.remove(.isActive)
updatedNames.append(TelegramPeerUsername(flags: updatedFlags, username: username.username))
}
let updatedUser = peer.withUpdatedAddressNames(updatedNames)
updatePeersCustom(transaction: transaction, peers: [updatedUser], update: { _, updated in
return updated
})
}
return .complete()
}
|> castError(DeactivateAllAddressNamesError.self)
|> switchToLatest
|> ignoreValues
}
} else {
return .never()
}
}
|> mapError { _ -> DeactivateAllAddressNamesError in
}
|> switchToLatest
}
public enum ToggleAddressNameActiveError {
case generic
case activeLimitReached
}
func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDomain, name: String, active: Bool) -> Signal<Void, ToggleAddressNameActiveError> {
return account.postbox.transaction { transaction -> Signal<Void, ToggleAddressNameActiveError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.toggleUsername(username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramUser {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedUser = peer.withUpdatedUsernames(updatedNames)
updatePeersCustom(transaction: transaction, peers: [updatedUser], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleUsername(channel: inputChannel, username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedPeer = peer.withUpdatedAddressNames(updatedNames)
updatePeersCustom(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
} else {
return .fail(.generic)
}
case let .bot(peerId):
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.bots.toggleUsername(bot: inputUser, username: name, active: active ? .boolTrue : .boolFalse), automaticFloodWait: false)
|> mapError { error -> ToggleAddressNameActiveError in
if error.errorDescription == "USERNAMES_ACTIVE_TOO_MUCH" {
return .activeLimitReached
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, ToggleAddressNameActiveError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramUser {
var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive)
} else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive)
}
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index)
if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
}
let updatedPeer = peer.withUpdatedUsernames(updatedNames)
updatePeersCustom(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ToggleAddressNameActiveError in }
}
} else {
return .fail(.generic)
}
case .theme:
return .fail(.generic)
}
} |> mapError { _ -> ToggleAddressNameActiveError in } |> switchToLatest
}
public enum ReorderAddressNamesError {
case generic
}
func _internal_reorderAddressNames(account: Account, domain: AddressNameDomain, names: [TelegramPeerUsername]) -> Signal<Void, ReorderAddressNamesError> {
return account.postbox.transaction { transaction -> Signal<Void, ReorderAddressNamesError> in
switch domain {
case .account:
return account.network.request(Api.functions.account.reorderUsernames(order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramUser {
let updatedUser = peer.withUpdatedUsernames(names)
updatePeersCustom(transaction: transaction, peers: [updatedUser], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.reorderUsernames(channel: inputChannel, order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
let updatedPeer = peer.withUpdatedAddressNames(names)
updatePeersCustom(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
} else {
return .fail(.generic)
}
case let .bot(peerId):
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.bots.reorderUsernames(bot: inputUser, order: names.filter { $0.isActive }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ReorderAddressNamesError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel {
let updatedPeer = peer.withUpdatedAddressNames(names)
updatePeersCustom(transaction: transaction, peers: [updatedPeer], update: { _, updated in
return updated
})
}
} |> mapError { _ -> ReorderAddressNamesError in }
}
} else {
return .fail(.generic)
}
case .theme:
return .fail(.generic)
}
} |> mapError { _ -> ReorderAddressNamesError in } |> switchToLatest
}
func _internal_checkPublicChannelCreationAvailability(account: Account, location: Bool = false) -> Signal<Bool, NoError> {
var flags: Int32 = (1 << 1)
if location {
flags |= (1 << 0)
}
return account.network.request(Api.functions.channels.getAdminedPublicChannels(flags: flags))
|> map { _ -> Bool in
return true
}
|> `catch` { error -> Signal<Bool, NoError> in
return .single(false)
}
}
public enum AdminedPublicChannelsScope {
case all
case forLocation
case forVoiceChat
case forPersonalProfile
}
public final class TelegramAdminedPublicChannel: Equatable {
public let peer: EnginePeer
public let subscriberCount: Int?
public init(peer: EnginePeer, subscriberCount: Int?) {
self.peer = peer
self.subscriberCount = subscriberCount
}
public static func ==(lhs: TelegramAdminedPublicChannel, rhs: TelegramAdminedPublicChannel) -> Bool {
if lhs === rhs {
return true
}
if lhs.peer != rhs.peer {
return false
}
if lhs.subscriberCount != rhs.subscriberCount {
return false
}
return true
}
}
func _internal_adminedPublicChannels(account: Account, scope: AdminedPublicChannelsScope = .all) -> Signal<[TelegramAdminedPublicChannel], NoError> {
var flags: Int32 = 0
switch scope {
case .all:
break
case .forLocation:
flags |= (1 << 0)
case .forVoiceChat:
flags |= (1 << 2)
case .forPersonalProfile:
flags |= (1 << 2)
}
let accountPeerId = account.peerId
return account.network.request(Api.functions.channels.getAdminedPublicChannels(flags: flags))
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<[TelegramAdminedPublicChannel], NoError> in
guard let result else {
return .single([])
}
return account.postbox.transaction { transaction -> [TelegramAdminedPublicChannel] in
let chats: [Api.Chat]
var subscriberCounts: [PeerId: Int] = [:]
let parsedPeers: AccumulatedPeers
switch result {
case let .chats(apiChats):
chats = apiChats
for chat in apiChats {
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat {
subscriberCounts[chat.peerId] = participantsCount.flatMap(Int.init)
}
}
case let .chatsSlice(_, apiChats):
chats = apiChats
}
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var peers: [TelegramAdminedPublicChannel] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(TelegramAdminedPublicChannel(
peer: EnginePeer(peer),
subscriberCount: subscriberCounts[peer.id]
))
}
}
return peers
}
}
}
final class CachedStorySendAsPeers: Codable {
public let peerIds: [PeerId]
public let timestamp: Double
public init(peerIds: [PeerId], timestamp: Double) {
self.peerIds = peerIds
self.timestamp = timestamp
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.peerIds = try container.decode([Int64].self, forKey: "l").map(PeerId.init)
self.timestamp = try container.decodeIfPresent(Double.self, forKey: "ts") ?? 0.0
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: "l")
try container.encode(self.timestamp, forKey: "ts")
}
}
func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> {
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> [Peer]? in
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)))?.get(CachedStorySendAsPeers.self) {
return entry.peerIds.compactMap(transaction.getPeer)
} else {
return nil
}
}
|> mapToSignal { cachedPeers in
let remote: Signal<[Peer], NoError> = account.network.request(Api.functions.stories.getChatsToSend())
|> retryRequest
|> mapToSignal { result -> Signal<[Peer], NoError> in
return account.postbox.transaction { transaction -> [Peer] in
let chats: [Api.Chat]
let parsedPeers: AccumulatedPeers
switch result {
case let .chats(apiChats):
chats = apiChats
case let .chatsSlice(_, apiChats):
chats = apiChats
}
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var peers: [Peer] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(peer)
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat, let participantsCount = participantsCount {
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
var current = current as? CachedChannelData ?? CachedChannelData()
var participantsSummary = current.participantsSummary
participantsSummary.memberCount = participantsCount
current = current.withUpdatedParticipantsSummary(participantsSummary)
return current
})
}
}
}
if let entry = CodableEntry(CachedStorySendAsPeers(peerIds: peers.map(\.id), timestamp: CFAbsoluteTimeGetCurrent())) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)), entry: entry)
}
return peers
}
}
if let cachedPeers = cachedPeers {
return .single(cachedPeers) |> then(remote)
} else {
return remote
}
}
}
func _internal_channelsForPublicReaction(account: Account, useLocalCache: Bool) -> Signal<[Peer], NoError> {
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> ([Peer], Double)? in
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.channelsForPublicReaction, key: ValueBoxKey(length: 0)))?.get(CachedStorySendAsPeers.self) {
return (entry.peerIds.compactMap(transaction.getPeer), entry.timestamp)
} else {
return nil
}
}
|> mapToSignal { cachedPeers in
let remote: Signal<[Peer], NoError> = account.network.request(Api.functions.channels.getAdminedPublicChannels(flags: 0))
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<[Peer], NoError> in
guard let result else {
return .single([])
}
return account.postbox.transaction { transaction -> [Peer] in
let chats: [Api.Chat]
let parsedPeers: AccumulatedPeers
switch result {
case let .chats(apiChats):
chats = apiChats
case let .chatsSlice(_, apiChats):
chats = apiChats
}
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var peers: [Peer] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(peer)
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat, let participantsCount = participantsCount {
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
var current = current as? CachedChannelData ?? CachedChannelData()
var participantsSummary = current.participantsSummary
participantsSummary.memberCount = participantsCount
current = current.withUpdatedParticipantsSummary(participantsSummary)
return current
})
}
}
}
if let entry = CodableEntry(CachedStorySendAsPeers(peerIds: peers.map(\.id), timestamp: CFAbsoluteTimeGetCurrent())) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.channelsForPublicReaction, key: ValueBoxKey(length: 0)), entry: entry)
}
return peers
}
}
if useLocalCache {
if let cachedPeers {
return .single(cachedPeers.0)
} else {
return .single([])
}
}
if let cachedPeers {
if CFAbsoluteTimeGetCurrent() < cachedPeers.1 + 5 * 60 {
return .single(cachedPeers.0)
} else {
return .single(cachedPeers.0) |> then(remote)
}
} else {
return remote
}
}
}
public enum ChannelAddressNameAssignmentAvailability {
case available
case unknown
case addressNameLimitReached
}
func _internal_channelAddressNameAssignmentAvailability(account: Account, peerId: PeerId?) -> Signal<ChannelAddressNameAssignmentAvailability, NoError> {
return account.postbox.transaction { transaction -> Signal<ChannelAddressNameAssignmentAvailability, NoError> in
var inputChannel: Api.InputChannel?
if let peerId = peerId {
if let peer = transaction.getPeer(peerId), let channel = apiInputChannel(peer) {
inputChannel = channel
}
} else {
inputChannel = .inputChannelEmpty
}
if let inputChannel = inputChannel {
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: "username"))
|> map { _ -> ChannelAddressNameAssignmentAvailability in
return .available
}
|> `catch` { error -> Signal<ChannelAddressNameAssignmentAvailability, NoError> in
return .single(.addressNameLimitReached)
}
} else {
return .single(.unknown)
}
} |> switchToLatest
}
@@ -0,0 +1,38 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_toggleAntiSpamProtection(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleAntiSpam(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse)) |> `catch` { _ in .complete() } |> map { updates -> Void in
account.stateManager.addUpdates(updates)
}
} else {
return .complete()
}
} |> switchToLatest
}
func _internal_reportAntiSpamFalsePositive(account: Account, peerId: PeerId, messageId: MessageId) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Signal<Bool, NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.reportAntiSpamFalsePositive(channel: inputChannel, msgId: messageId.id))
|> map { result -> Bool in
switch result {
case .boolTrue:
return true
case .boolFalse:
return false
}
}
|> `catch` { _ -> Signal<Bool, NoError> in
return .single(false)
}
} else {
return .complete()
}
} |> switchToLatest
}
@@ -0,0 +1,19 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_toggleAutoTranslation(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleAutotranslation(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse)) |> `catch` { _ in .complete() } |> map { updates -> Void in
account.stateManager.addUpdates(updates)
}
} else {
return .complete()
}
}
|> switchToLatest
|> ignoreValues
}
@@ -0,0 +1,141 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public final class TelegramBirthday: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case day
case month
case year
}
public let day: Int32
public let month: Int32
public let year: Int32?
public init(day: Int32, month: Int32, year: Int32?) {
self.day = day
self.month = month
self.year = year
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.day = try container.decode(Int32.self, forKey: .day)
self.month = try container.decode(Int32.self, forKey: .month)
self.year = try container.decodeIfPresent(Int32.self, forKey: .year)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.day, forKey: .day)
try container.encode(self.month, forKey: .month)
try container.encodeIfPresent(self.year, forKey: .year)
}
public static func ==(lhs: TelegramBirthday, rhs: TelegramBirthday) -> Bool {
if lhs === rhs {
return true
}
if lhs.day != rhs.day {
return false
}
if lhs.month != rhs.month {
return false
}
if lhs.year != rhs.year {
return false
}
return true
}
}
extension TelegramBirthday {
convenience init(apiBirthday: Api.Birthday) {
switch apiBirthday {
case let .birthday(_, day, month, year):
self.init(
day: day,
month: month,
year: year
)
}
}
var apiBirthday: Api.Birthday {
var flags: Int32 = 0
if let _ = self.year {
flags |= (1 << 0)
}
return .birthday(flags: flags, day: self.day, month: self.month, year: self.year)
}
}
public enum UpdateBirthdayError {
case generic
case flood
}
func _internal_updateBirthday(account: Account, birthday: TelegramBirthday?) -> Signal<Never, UpdateBirthdayError> {
var flags: Int32 = 0
if let _ = birthday {
flags |= (1 << 0)
}
return account.network.request(Api.functions.account.updateBirthday(flags: flags, birthday: birthday?.apiBirthday), automaticFloodWait: false)
|> mapError { error -> UpdateBirthdayError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Never, UpdateBirthdayError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result {
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
let current = current as? CachedUserData ?? CachedUserData()
return current.withUpdatedBirthday(birthday)
})
}
}
|> mapError { _ -> UpdateBirthdayError in }
|> ignoreValues
}
}
func managedContactBirthdays(stateManager: AccountStateManager) -> Signal<Never, NoError> {
let poll = stateManager.network.request(Api.functions.contacts.getBirthdays())
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result else {
return .complete()
}
return stateManager.postbox.transaction { transaction -> Void in
if case let .contactBirthdays(contactBirthdays, users) = result {
updatePeers(transaction: transaction, accountPeerId: stateManager.accountPeerId, peers: AccumulatedPeers(users: users))
var birthdays: [EnginePeer.Id: TelegramBirthday] = [:]
for contactBirthday in contactBirthdays {
if case let .contactBirthday(contactId, birthday) = contactBirthday {
let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(contactId))
if peerId == stateManager.accountPeerId {
continue
}
birthdays[peerId] = TelegramBirthday(apiBirthday: birthday)
}
}
stateManager.modifyContactBirthdays({ _ in
return birthdays
})
}
}
|> ignoreValues
}
return (poll |> then(.complete() |> suspendAwareDelay(8.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
@@ -0,0 +1,130 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
final class CachedRecommendedBots: Codable {
public let peerIds: [EnginePeer.Id]
public let count: Int32
public let timestamp: Int32?
public init(peerIds: [EnginePeer.Id], count: Int32, timestamp: Int32?) {
self.peerIds = peerIds
self.count = count
self.timestamp = timestamp
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.peerIds = try container.decode([Int64].self, forKey: "l").map(EnginePeer.Id.init)
self.count = try container.decodeIfPresent(Int32.self, forKey: "c") ?? 0
self.timestamp = try container.decodeIfPresent(Int32.self, forKey: "ts")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: "l")
try container.encode(self.count, forKey: "c")
try container.encodeIfPresent(self.timestamp, forKey: "ts")
}
}
private func entryId(peerId: EnginePeer.Id?) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
if let peerId {
cacheKey.setInt64(0, value: peerId.toInt64())
} else {
cacheKey.setInt64(0, value: 0)
}
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.recommendedBots, key: cacheKey)
}
func _internal_requestRecommendedBots(account: Account, peerId: EnginePeer.Id, forceUpdate: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> (Peer?, Bool) in
guard let user = transaction.getPeer(peerId) as? TelegramUser, let _ = user.botInfo else {
return (nil, false)
}
if let entry = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedRecommendedBots.self), !entry.peerIds.isEmpty && !forceUpdate {
return (nil, false)
} else {
return (user, true)
}
}
|> mapToSignal { user, shouldUpdate in
guard shouldUpdate, let user, let inputUser = apiInputUser(user) else {
return .complete()
}
return account.network.request(Api.functions.bots.getBotRecommendations(bot: inputUser))
|> retryRequest
|> mapToSignal { result -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> [EnginePeer] in
let users: [Api.User]
let parsedPeers: AccumulatedPeers
var count: Int32
switch result {
case let .users(apiUsers):
users = apiUsers
count = Int32(apiUsers.count)
case let .usersSlice(apiCount, apiUsers):
users = apiUsers
count = apiCount
}
parsedPeers = AccumulatedPeers(users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
let peers = users.map { EnginePeer(TelegramUser(user: $0)) }
if let entry = CodableEntry(CachedRecommendedBots(peerIds: peers.map(\.id), count: count, timestamp: Int32(Date().timeIntervalSince1970))) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
return peers
}
|> ignoreValues
}
}
}
public struct RecommendedBots: Equatable {
public var bots: [EnginePeer]
public var count: Int32
public init(bots: [EnginePeer], count: Int32) {
self.bots = bots
self.count = count
}
}
func _internal_recommendedBotPeerIds(account: Account, peerId: EnginePeer.Id) -> Signal<[EnginePeer.Id]?, NoError> {
let key = PostboxViewKey.cachedItem(entryId(peerId: peerId))
return account.postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<[EnginePeer.Id]?, NoError> in
guard let cachedBots = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedBots.self), !cachedBots.peerIds.isEmpty else {
return .single(nil)
}
return .single(cachedBots.peerIds)
}
}
func _internal_recommendedBots(account: Account, peerId: EnginePeer.Id) -> Signal<RecommendedBots?, NoError> {
let key = PostboxViewKey.cachedItem(entryId(peerId: peerId))
return account.postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<RecommendedBots?, NoError> in
guard let cachedBots = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedBots.self) else {
return .single(nil)
}
if cachedBots.peerIds.isEmpty {
return .single(nil)
}
return account.postbox.transaction { transaction -> RecommendedBots? in
var bots: [EnginePeer] = []
for peerId in cachedBots.peerIds {
if let peer = transaction.getPeer(peerId) {
bots.append(EnginePeer(peer))
}
}
return RecommendedBots(bots: bots, count: cachedBots.count)
}
}
}
@@ -0,0 +1,364 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64?) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
guard let peer = transaction.getPeer(peerId) else {
return
}
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
if let threadId = threadId {
if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
var updatedSettings: TelegramPeerNotificationSettings
switch data.notificationSettings.muteState {
case .default:
updatedSettings = data.notificationSettings.withUpdatedMuteState(.muted(until: Int32.max))
case .unmuted:
updatedSettings = data.notificationSettings.withUpdatedMuteState(.muted(until: Int32.max))
case .muted:
updatedSettings = data.notificationSettings.withUpdatedMuteState(.unmuted)
}
data.notificationSettings = updatedSettings
if let entry = StoredMessageHistoryThreadInfo(data) {
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry)
//TODO:loc
let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start()
}
}
} else {
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
let updatedSettings: TelegramPeerNotificationSettings
switch previousSettings.muteState {
case .default:
let globalNotificationSettings = transaction.getGlobalNotificationSettings()
if resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: previousSettings) {
updatedSettings = previousSettings.withUpdatedMuteState(.unmuted)
} else {
updatedSettings = previousSettings.withUpdatedMuteState(.muted(until: Int32.max))
}
case .unmuted:
updatedSettings = previousSettings.withUpdatedMuteState(.muted(until: Int32.max))
case .muted:
updatedSettings = previousSettings.withUpdatedMuteState(.unmuted)
}
transaction.updatePendingPeerNotificationSettings(peerId: notificationPeerId, settings: updatedSettings)
}
}
}
public func resolvedAreStoriesMuted(globalSettings: GlobalNotificationSettingsSet, peer: Peer, peerSettings: TelegramPeerNotificationSettings?, topSearchPeers: [PeerId]) -> Bool {
let defaultIsMuted: Bool
switch globalSettings.privateChats.storySettings.mute {
case .muted:
defaultIsMuted = true
case .unmuted:
defaultIsMuted = false
case .default:
defaultIsMuted = !topSearchPeers.prefix(5).contains(peer.id)
}
switch peerSettings?.storySettings.mute {
case .none:
return defaultIsMuted
case .muted:
return true
case .unmuted:
return false
default:
return defaultIsMuted
}
}
func _internal_togglePeerStoriesMuted(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
guard let peer = transaction.getPeer(peerId) else {
return
}
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
var topSearchPeers: [PeerId] = []
if let value = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId())?.get(CachedRecentPeers.self), value.enabled {
topSearchPeers = value.ids
}
let updatedSettings: TelegramPeerNotificationSettings
var storySettings = previousSettings.storySettings
switch previousSettings.storySettings.mute {
case .default:
let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings
if resolvedAreStoriesMuted(globalSettings: globalNotificationSettings.effective, peer: peer, peerSettings: previousSettings, topSearchPeers: topSearchPeers) {
storySettings.mute = .unmuted
} else {
storySettings.mute = .muted
}
case .unmuted:
storySettings.mute = .muted
case .muted:
storySettings.mute = .unmuted
}
updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: notificationPeerId, settings: updatedSettings)
}
}
func _internal_updatePeerMuteSetting(account: Account, peerId: PeerId, threadId: Int64?, muteInterval: Int32?) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
_internal_updatePeerMuteSetting(account: account, transaction: transaction, peerId: peerId, threadId: threadId, muteInterval: muteInterval)
}
}
func _internal_updatePeerMuteSetting(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, muteInterval: Int32?) {
if let peer = transaction.getPeer(peerId) {
if let threadId = threadId {
let peerSettings: TelegramPeerNotificationSettings = (transaction.getPeerNotificationSettings(id: peerId) as? TelegramPeerNotificationSettings) ?? .defaultSettings
if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings
var muteState: PeerMuteState
if let muteInterval = muteInterval {
if muteInterval == 0 {
muteState = .unmuted
} else {
let absoluteUntil: Int32
if muteInterval == Int32.max {
absoluteUntil = Int32.max
} else {
absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval
}
muteState = .muted(until: absoluteUntil)
}
} else {
muteState = .unmuted
}
if peerSettings.muteState == muteState {
muteState = .default
}
data.notificationSettings = previousSettings.withUpdatedMuteState(muteState)
if let entry = StoredMessageHistoryThreadInfo(data) {
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry)
}
//TODO:loc
let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start()
}
} else {
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
let muteState: PeerMuteState
if let muteInterval = muteInterval {
if muteInterval == 0 {
muteState = .unmuted
} else {
let absoluteUntil: Int32
if muteInterval == Int32.max {
absoluteUntil = Int32.max
} else {
absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval
}
muteState = .muted(until: absoluteUntil)
}
} else {
muteState = .default
}
let updatedSettings = previousSettings.withUpdatedMuteState(muteState)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
}
func _internal_updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId, threadId: Int64?, displayPreviews: PeerNotificationDisplayPreviews) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
_internal_updatePeerDisplayPreviewsSetting(account: account, transaction: transaction, peerId: peerId, threadId: threadId, displayPreviews: displayPreviews)
}
}
func _internal_updatePeerDisplayPreviewsSetting(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, displayPreviews: PeerNotificationDisplayPreviews) {
if let peer = transaction.getPeer(peerId) {
if let threadId = threadId {
if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings
data.notificationSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews)
if let entry = StoredMessageHistoryThreadInfo(data) {
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry)
}
//TODO:loc
let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start()
}
} else {
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
let updatedSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
}
func _internal_updatePeerStoriesMutedSetting(account: Account, peerId: PeerId, mute: PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
_internal_updatePeerStoriesMutedSetting(account: account, transaction: transaction, peerId: peerId, mute: mute)
}
}
func _internal_updatePeerStoriesMutedSetting(account: Account, transaction: Transaction, peerId: PeerId, mute: PeerStoryNotificationSettings.Mute) {
if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
var storySettings = previousSettings.storySettings
storySettings.mute = mute
let updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
func _internal_updatePeerStoriesHideSenderSetting(account: Account, transaction: Transaction, peerId: PeerId, hideSender: PeerStoryNotificationSettings.HideSender) {
if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
var storySettings = previousSettings.storySettings
storySettings.hideSender = hideSender
let updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
func _internal_updatePeerNotificationSoundInteractive(account: Account, peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
_internal_updatePeerNotificationSoundInteractive(account: account, transaction: transaction, peerId: peerId, threadId: threadId, sound: sound)
}
}
func _internal_updatePeerNotificationSoundInteractive(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) {
if let peer = transaction.getPeer(peerId) {
if let threadId = threadId {
if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings
data.notificationSettings = previousSettings.withUpdatedMessageSound(sound)
if let entry = StoredMessageHistoryThreadInfo(data) {
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry)
}
//TODO:loc
let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start()
}
} else {
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
let updatedSettings = previousSettings.withUpdatedMessageSound(sound)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
}
func _internal_updatePeerStoryNotificationSoundInteractive(account: Account, transaction: Transaction, peerId: PeerId, sound: PeerMessageSound) {
if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
var storySettings = previousSettings.storySettings
storySettings.sound = sound
let updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
@@ -0,0 +1,232 @@
import Postbox
import SwiftSignalKit
public struct ChannelAdminEventLogEntry: Comparable {
public let stableId: UInt32
public let headerStableId: UInt32
public let event: AdminLogEvent
public let peers: [PeerId: Peer]
public static func ==(lhs: ChannelAdminEventLogEntry, rhs: ChannelAdminEventLogEntry) -> Bool {
return lhs.event == rhs.event
}
public static func <(lhs: ChannelAdminEventLogEntry, rhs: ChannelAdminEventLogEntry) -> Bool {
return lhs.event < rhs.event
}
}
public enum ChannelAdminEventLogUpdateType {
case initial
case generic
case load
}
public struct ChannelAdminEventLogFilter: Equatable {
public let query: String?
public let events: AdminLogEventsFlags
public let adminPeerIds: [PeerId]?
public init(query: String? = nil, events: AdminLogEventsFlags = .all, adminPeerIds: [PeerId]? = nil) {
self.query = query
self.events = events
self.adminPeerIds = adminPeerIds
}
public static func ==(lhs: ChannelAdminEventLogFilter, rhs: ChannelAdminEventLogFilter) -> Bool {
if lhs.query != rhs.query {
return false
}
if lhs.events != rhs.events {
return false
}
if let lhsAdminPeerIds = lhs.adminPeerIds, let rhsAdminPeerIds = rhs.adminPeerIds {
if lhsAdminPeerIds != rhsAdminPeerIds {
return false
}
} else if (lhs.adminPeerIds != nil) != (rhs.adminPeerIds != nil) {
return false
}
return true
}
public var isEmpty: Bool {
if self.query != nil {
return false
}
if self.events != .all {
return false
}
if self.adminPeerIds != nil {
return false
}
return true
}
public func withQuery(_ query: String?) -> ChannelAdminEventLogFilter {
return ChannelAdminEventLogFilter(query: query, events: self.events, adminPeerIds: self.adminPeerIds)
}
public func withEvents(_ events: AdminLogEventsFlags) -> ChannelAdminEventLogFilter {
return ChannelAdminEventLogFilter(query: self.query, events: events, adminPeerIds: self.adminPeerIds)
}
public func withAdminPeerIds(_ adminPeerIds: [PeerId]?) -> ChannelAdminEventLogFilter {
return ChannelAdminEventLogFilter(query: self.query, events: self.events, adminPeerIds: adminPeerIds)
}
}
public final class ChannelAdminEventLogContext {
private let queue: Queue = Queue.mainQueue()
private let postbox: Postbox
private let network: Network
private let accountPeerId: PeerId
private let peerId: PeerId
private var filter: ChannelAdminEventLogFilter = ChannelAdminEventLogFilter()
private var nextStableId: UInt32 = 1
private var headerStableIds: [AdminLogEventId: UInt32] = [:]
private var stableIds: [AdminLogEventId: UInt32] = [:]
private var entries: ([ChannelAdminEventLogEntry], ChannelAdminEventLogFilter) = ([], ChannelAdminEventLogFilter())
private var hasEntries: Bool = false
private var hasEarlier: Bool = true
private var loadingMoreEarlier: Bool = false
private var subscribers = Bag<([ChannelAdminEventLogEntry], Bool, ChannelAdminEventLogUpdateType, Bool) -> Void>()
private let loadMoreDisposable = MetaDisposable()
init(postbox: Postbox, network: Network, peerId: PeerId, accountPeerId: PeerId) {
self.postbox = postbox
self.network = network
self.peerId = peerId
self.accountPeerId = accountPeerId
}
deinit {
self.loadMoreDisposable.dispose()
}
public func get() -> Signal<([ChannelAdminEventLogEntry], Bool, ChannelAdminEventLogUpdateType, Bool), NoError> {
let queue = self.queue
return Signal { [weak self] subscriber in
if let strongSelf = self {
subscriber.putNext((strongSelf.entries.0, strongSelf.hasEarlier, .initial, strongSelf.hasEntries))
let index = strongSelf.subscribers.add({ entries, hasEarlier, type, hasEntries in
subscriber.putNext((entries, hasEarlier, type, hasEntries))
})
return ActionDisposable {
queue.async {
if let strongSelf = self {
strongSelf.subscribers.remove(index)
}
}
}
} else {
return EmptyDisposable
}
} |> runOn(queue)
}
public func setFilter(_ filter: ChannelAdminEventLogFilter) {
if self.filter != filter {
self.filter = filter
self.loadingMoreEarlier = false
self.hasEarlier = false
self.hasEntries = false
for subscriber in self.subscribers.copyItems() {
subscriber(self.entries.0, self.hasEarlier, .load, self.hasEntries)
}
self.loadMoreEntries()
}
}
public func reload() {
self.entries = ([], self.filter)
self.loadMoreEntries()
}
public func loadMoreEntries() {
assert(self.queue.isCurrent())
if self.loadingMoreEarlier {
return
}
let maxId: AdminLogEventId
if self.entries.1 == self.filter, let first = self.entries.0.first {
maxId = first.event.id
} else {
maxId = AdminLogEventId.max
}
self.loadingMoreEarlier = true
self.loadMoreDisposable.set((channelAdminLogEvents(accountPeerId: self.accountPeerId, postbox: self.postbox, network: self.network, peerId: self.peerId, maxId: maxId, minId: AdminLogEventId.min, limit: 100, query: self.filter.query, filter: self.filter.events, admins: self.filter.adminPeerIds)
|> deliverOn(self.queue)).start(next: { [weak self] result in
if let strongSelf = self {
var events = result.events.sorted()
if strongSelf.entries.1 == strongSelf.filter {
if let first = strongSelf.entries.0.first {
var clipIndex = events.count
for i in (0 ..< events.count).reversed() {
if events[i] >= first.event {
clipIndex = i - 1
}
}
if clipIndex < events.count {
events.removeSubrange(clipIndex ..< events.count)
}
}
var entries: [ChannelAdminEventLogEntry] = events.map { event in
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), headerStableId: strongSelf.headerStableIdForEventId(event.id), event: event, peers: result.peers)
}
entries.append(contentsOf: strongSelf.entries.0)
strongSelf.entries = (entries, strongSelf.filter)
} else {
let entries: [ChannelAdminEventLogEntry] = events.map { event in
return ChannelAdminEventLogEntry(stableId: strongSelf.stableIdForEventId(event.id), headerStableId: strongSelf.headerStableIdForEventId(event.id), event: event, peers: result.peers)
}
strongSelf.entries = (entries, strongSelf.filter)
}
strongSelf.hasEarlier = !events.isEmpty
strongSelf.loadingMoreEarlier = false
strongSelf.hasEntries = true
for subscriber in strongSelf.subscribers.copyItems() {
subscriber(strongSelf.entries.0, strongSelf.hasEarlier, .load, strongSelf.hasEntries)
}
}
}))
}
private func stableIdForEventId(_ id: AdminLogEventId) -> UInt32 {
if let value = self.stableIds[id] {
return value
} else {
let value = self.nextStableId
self.nextStableId += 1
self.stableIds[id] = value
return value
}
}
private func headerStableIdForEventId(_ id: AdminLogEventId) -> UInt32 {
if let value = self.headerStableIds[id] {
return value
} else {
let value = self.nextStableId
self.nextStableId += 1
self.headerStableIds[id] = value
return value
}
}
}
@@ -0,0 +1,465 @@
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public typealias AdminLogEventId = Int64
public struct AdminLogEvent: Comparable {
public let id: AdminLogEventId
public let peerId: PeerId
public let date: Int32
public let action: AdminLogEventAction
public static func ==(lhs: AdminLogEvent, rhs: AdminLogEvent) -> Bool {
return lhs.id == rhs.id
}
public static func <(lhs: AdminLogEvent, rhs: AdminLogEvent) -> Bool {
if lhs.date != rhs.date {
return lhs.date < rhs.date
} else {
return lhs.id < rhs.id
}
}
}
public struct AdminLogEventsResult {
public let peerId: PeerId
public let peers: [PeerId: Peer]
public let events: [AdminLogEvent]
}
public enum AdminLogEventAction {
public struct ForumTopicInfo {
public var info: EngineMessageHistoryThread.Info
public var isClosed: Bool
public var isHidden: Bool
public init(info: EngineMessageHistoryThread.Info, isClosed: Bool, isHidden: Bool) {
self.info = info
self.isClosed = isClosed
self.isHidden = isHidden
}
}
case changeTitle(prev: String, new: String)
case changeAbout(prev: String, new: String)
case changeUsername(prev: String, new: String)
case changePhoto(prev: ([TelegramMediaImageRepresentation], [TelegramMediaImage.VideoRepresentation]), new: ([TelegramMediaImageRepresentation], [TelegramMediaImage.VideoRepresentation]))
case toggleInvites(Bool)
case toggleSignatures(Bool)
case toggleSignatureProfiles(Bool)
case updatePinned(Message?)
case editMessage(prev: Message, new: Message)
case deleteMessage(Message)
case participantJoin
case participantLeave
case participantInvite(RenderedChannelParticipant)
case participantToggleBan(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
case participantToggleAdmin(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
case changeStickerPack(prev: StickerPackReference?, new: StickerPackReference?)
case togglePreHistoryHidden(Bool)
case updateDefaultBannedRights(prev: TelegramChatBannedRights, new: TelegramChatBannedRights)
case pollStopped(Message)
case linkedPeerUpdated(previous: Peer?, updated: Peer?)
case changeGeoLocation(previous: PeerGeoLocation?, updated: PeerGeoLocation?)
case updateSlowmode(previous: Int32?, updated: Int32?)
case startGroupCall
case endGroupCall
case groupCallUpdateParticipantMuteStatus(peerId: PeerId, isMuted: Bool)
case updateGroupCallSettings(joinMuted: Bool)
case groupCallUpdateParticipantVolume(peerId: PeerId, volume: Int32)
case deleteExportedInvitation(ExportedInvitation)
case revokeExportedInvitation(ExportedInvitation)
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
case participantJoinedViaInvite(invitation: ExportedInvitation, joinedViaFolderLink: Bool)
case changeHistoryTTL(previousValue: Int32?, updatedValue: Int32?)
case changeTheme(previous: String?, updated: String?)
case participantJoinByRequest(invitation: ExportedInvitation, approvedBy: PeerId)
case toggleCopyProtection(Bool)
case sendMessage(Message)
case changeAvailableReactions(previousValue: PeerAllowedReactions, updatedValue: PeerAllowedReactions)
case changeUsernames(prev: [String], new: [String])
case createTopic(info: EngineMessageHistoryThread.Info)
case deleteTopic(info: EngineMessageHistoryThread.Info)
case editTopic(prevInfo: ForumTopicInfo, newInfo: ForumTopicInfo)
case pinTopic(prevInfo: EngineMessageHistoryThread.Info?, newInfo: EngineMessageHistoryThread.Info?)
case toggleForum(isForum: Bool)
case toggleAntiSpam(isEnabled: Bool)
case changeNameColor(prevColor: PeerNameColor, prevIcon: Int64?, newColor: PeerNameColor, newIcon: Int64?)
case changeProfileColor(prevColor: PeerNameColor?, prevIcon: Int64?, newColor: PeerNameColor?, newIcon: Int64?)
case changeWallpaper(prev: TelegramWallpaper?, new: TelegramWallpaper?)
case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?)
case changeEmojiPack(prev: StickerPackReference?, new: StickerPackReference?)
case participantSubscriptionExtended(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
case toggleAutoTranslation(Bool)
}
public enum ChannelAdminLogEventError {
case generic
}
public struct AdminLogEventsFlags: OptionSet {
public var rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let join = AdminLogEventsFlags(rawValue: 1 << 0)
public static let leave = AdminLogEventsFlags(rawValue: 1 << 1)
public static let invite = AdminLogEventsFlags(rawValue: 1 << 2)
public static let ban = AdminLogEventsFlags(rawValue: 1 << 3)
public static let unban = AdminLogEventsFlags(rawValue: 1 << 4)
public static let kick = AdminLogEventsFlags(rawValue: 1 << 5)
public static let unkick = AdminLogEventsFlags(rawValue: 1 << 6)
public static let promote = AdminLogEventsFlags(rawValue: 1 << 7)
public static let demote = AdminLogEventsFlags(rawValue: 1 << 8)
public static let info = AdminLogEventsFlags(rawValue: 1 << 9)
public static let settings = AdminLogEventsFlags(rawValue: 1 << 10)
public static let pinnedMessages = AdminLogEventsFlags(rawValue: 1 << 11)
public static let editMessages = AdminLogEventsFlags(rawValue: 1 << 12)
public static let deleteMessages = AdminLogEventsFlags(rawValue: 1 << 13)
public static let calls = AdminLogEventsFlags(rawValue: 1 << 14)
public static let invites = AdminLogEventsFlags(rawValue: 1 << 15)
public static let sendMessages = AdminLogEventsFlags(rawValue: 1 << 16)
public static let forums = AdminLogEventsFlags(rawValue: 1 << 17)
public static var all: AdminLogEventsFlags {
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .sendMessages, .pinnedMessages, .editMessages, .deleteMessages, .calls, .invites, .forums]
}
public static var flags: AdminLogEventsFlags {
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .sendMessages, .pinnedMessages, .editMessages, .deleteMessages, .calls, .invites, .forums]
}
}
private func boolFromApiValue(_ value: Api.Bool) -> Bool {
switch value {
case .boolFalse:
return false
case .boolTrue:
return true
}
}
func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, maxId: AdminLogEventId, minId: AdminLogEventId, limit: Int32 = 100, query: String? = nil, filter: AdminLogEventsFlags? = nil, admins: [PeerId]? = nil) -> Signal<AdminLogEventsResult, ChannelAdminLogEventError> {
return postbox.transaction { transaction -> (Peer?, [Peer]?) in
return (transaction.getPeer(peerId), admins?.compactMap { transaction.getPeer($0) })
}
|> castError(ChannelAdminLogEventError.self)
|> mapToSignal { (peer, admins) -> Signal<AdminLogEventsResult, ChannelAdminLogEventError> in
if let peer = peer, let inputChannel = apiInputChannel(peer) {
let inputAdmins = admins?.compactMap { apiInputUser($0) }
var flags: Int32 = 0
var eventsFilter: Api.ChannelAdminLogEventsFilter? = nil
if let filter = filter {
flags += Int32(1 << 0)
eventsFilter = Api.ChannelAdminLogEventsFilter.channelAdminLogEventsFilter(flags: Int32(filter.rawValue))
}
if let _ = inputAdmins {
flags += Int32(1 << 1)
}
return network.request(Api.functions.channels.getAdminLog(flags: flags, channel: inputChannel, q: query ?? "", eventsFilter: eventsFilter, admins: inputAdmins, maxId: maxId, minId: minId, limit: limit))
|> mapToSignal { result in
switch result {
case let .adminLogResults(apiEvents, apiChats, apiUsers):
return postbox.transaction { transaction -> AdminLogEventsResult in
var peers: [PeerId: Peer] = [:]
for apiChat in apiChats {
if let peer = parseTelegramGroupOrChannel(chat: apiChat) {
peers[peer.id] = peer
}
}
for apiUser in apiUsers {
let peer = TelegramUser(user: apiUser)
peers[peer.id] = peer
}
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: apiChats, users: apiUsers)
var events: [AdminLogEvent] = []
func renderedMessage(message: StoreMessage) -> Message? {
var associatedThreadInfo: Message.AssociatedThreadInfo?
if let threadId = message.threadId, let threadInfo = transaction.getMessageHistoryThreadInfo(peerId: message.id.peerId, threadId: threadId) {
associatedThreadInfo = postbox.seedConfiguration.decodeMessageThreadInfo(threadInfo.data)
}
var associatedMessages: SimpleDictionary<MessageId, Message> = SimpleDictionary()
if let replyAttribute = message.attributes.first(where: { $0 is ReplyMessageAttribute }) as? ReplyMessageAttribute {
var foundDeletedReplyMessage = false
for event in apiEvents {
switch event {
case let .channelAdminLogEvent(_, _, _, apiAction):
switch apiAction {
case let .channelAdminLogEventActionDeleteMessage(apiMessage):
if let messageId = apiMessage.id(namespace: Namespaces.Message.Cloud), messageId == replyAttribute.messageId, let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let replyMessage = locallyRenderedMessage(message: message, peers: peers, associatedThreadInfo: associatedThreadInfo) {
associatedMessages[replyMessage.id] = replyMessage
foundDeletedReplyMessage = true
}
default:
break
}
}
}
if !foundDeletedReplyMessage, let replyMessage = transaction.getMessage(replyAttribute.messageId) {
associatedMessages[replyMessage.id] = replyMessage
}
}
return locallyRenderedMessage(message: message, peers: peers, associatedThreadInfo: associatedThreadInfo, associatedMessages: associatedMessages)
}
for event in apiEvents {
switch event {
case let .channelAdminLogEvent(id, date, userId, apiAction):
var action: AdminLogEventAction?
switch apiAction {
case let .channelAdminLogEventActionChangeTitle(prev, new):
action = .changeTitle(prev: prev, new: new)
case let .channelAdminLogEventActionChangeAbout(prev, new):
action = .changeAbout(prev: prev, new: new)
case let .channelAdminLogEventActionChangeUsername(prev, new):
action = .changeUsername(prev: prev, new: new)
case let .channelAdminLogEventActionChangePhoto(prev, new):
let previousImage = telegramMediaImageFromApiPhoto(prev)
let newImage = telegramMediaImageFromApiPhoto(new)
action = .changePhoto(prev: (previousImage?.representations ?? [], previousImage?.videoRepresentations ?? []) , new: (newImage?.representations ?? [], newImage?.videoRepresentations ?? []))
case let .channelAdminLogEventActionToggleInvites(new):
action = .toggleInvites(boolFromApiValue(new))
case let .channelAdminLogEventActionToggleSignatures(new):
action = .toggleSignatures(boolFromApiValue(new))
case let .channelAdminLogEventActionUpdatePinned(new):
switch new {
case .messageEmpty:
action = .updatePinned(nil)
default:
if let message = StoreMessage(apiMessage: new, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let rendered = renderedMessage(message: message) {
action = .updatePinned(rendered)
}
}
case let .channelAdminLogEventActionEditMessage(prev, new):
if let prev = StoreMessage(apiMessage: prev, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let prevRendered = renderedMessage(message: prev), let new = StoreMessage(apiMessage: new, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let newRendered = renderedMessage(message: new) {
action = .editMessage(prev: prevRendered, new: newRendered)
}
case let .channelAdminLogEventActionDeleteMessage(message):
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let rendered = renderedMessage(message: message) {
action = .deleteMessage(rendered)
}
case .channelAdminLogEventActionParticipantJoin:
action = .participantJoin
case .channelAdminLogEventActionParticipantLeave:
action = .participantLeave
case let .channelAdminLogEventActionParticipantInvite(participant):
let participant = ChannelParticipant(apiParticipant: participant)
if let peer = peers[participant.peerId] {
action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: peer))
}
case let .channelAdminLogEventActionParticipantToggleBan(prev, new):
let prevParticipant = ChannelParticipant(apiParticipant: prev)
let newParticipant = ChannelParticipant(apiParticipant: new)
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
}
case let .channelAdminLogEventActionParticipantToggleAdmin(prev, new):
let prevParticipant = ChannelParticipant(apiParticipant: prev)
let newParticipant = ChannelParticipant(apiParticipant: new)
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
}
case let .channelAdminLogEventActionChangeStickerSet(prevStickerset, newStickerset):
action = .changeStickerPack(prev: StickerPackReference(apiInputSet: prevStickerset), new: StickerPackReference(apiInputSet: newStickerset))
case let .channelAdminLogEventActionTogglePreHistoryHidden(value):
action = .togglePreHistoryHidden(value == .boolTrue)
case let .channelAdminLogEventActionDefaultBannedRights(prevBannedRights, newBannedRights):
action = .updateDefaultBannedRights(prev: TelegramChatBannedRights(apiBannedRights: prevBannedRights), new: TelegramChatBannedRights(apiBannedRights: newBannedRights))
case let .channelAdminLogEventActionStopPoll(message):
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let rendered = renderedMessage(message: message) {
action = .pollStopped(rendered)
}
case let .channelAdminLogEventActionChangeLinkedChat(prevValue, newValue):
action = .linkedPeerUpdated(previous: prevValue == 0 ? nil : peers[PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(prevValue))], updated: newValue == 0 ? nil : peers[PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(newValue))])
case let .channelAdminLogEventActionChangeLocation(prevValue, newValue):
action = .changeGeoLocation(previous: PeerGeoLocation(apiLocation: prevValue), updated: PeerGeoLocation(apiLocation: newValue))
case let .channelAdminLogEventActionToggleSlowMode(prevValue, newValue):
action = .updateSlowmode(previous: prevValue == 0 ? nil : prevValue, updated: newValue == 0 ? nil : newValue)
case .channelAdminLogEventActionStartGroupCall:
action = .startGroupCall
case .channelAdminLogEventActionDiscardGroupCall:
action = .endGroupCall
case let .channelAdminLogEventActionParticipantMute(participant):
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: true)
case let .channelAdminLogEventActionParticipantUnmute(participant):
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: false)
case let .channelAdminLogEventActionToggleGroupCallSetting(joinMuted):
action = .updateGroupCallSettings(joinMuted: joinMuted == .boolTrue)
case let .channelAdminLogEventActionExportedInviteDelete(invite):
action = .deleteExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
case let .channelAdminLogEventActionExportedInviteRevoke(invite):
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
case let .channelAdminLogEventActionParticipantJoinByInvite(flags, invite):
action = .participantJoinedViaInvite(invitation: ExportedInvitation(apiExportedInvite: invite), joinedViaFolderLink: (flags & (1 << 0)) != 0)
case let .channelAdminLogEventActionParticipantVolume(participant):
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
case let .channelAdminLogEventActionChangeHistoryTTL(prevValue, newValue):
action = .changeHistoryTTL(previousValue: prevValue, updatedValue: newValue)
case let .channelAdminLogEventActionParticipantJoinByRequest(invite, approvedBy):
action = .participantJoinByRequest(invitation: ExportedInvitation(apiExportedInvite: invite), approvedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(approvedBy)))
case let .channelAdminLogEventActionToggleNoForwards(new):
action = .toggleCopyProtection(boolFromApiValue(new))
case let .channelAdminLogEventActionSendMessage(message):
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let rendered = renderedMessage(message: message) {
action = .sendMessage(rendered)
}
case let .channelAdminLogEventActionChangeAvailableReactions(prevValue, newValue):
action = .changeAvailableReactions(previousValue: PeerAllowedReactions(apiReactions: prevValue), updatedValue: PeerAllowedReactions(apiReactions: newValue))
case let .channelAdminLogEventActionChangeUsernames(prevValue, newValue):
action = .changeUsernames(prev: prevValue, new: newValue)
case let .channelAdminLogEventActionCreateTopic(topic):
switch topic {
case let .forumTopic(_, _, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
action = .createTopic(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor))
case .forumTopicDeleted:
action = .createTopic(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0))
}
case let .channelAdminLogEventActionDeleteTopic(topic):
switch topic {
case let .forumTopic(_, _, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
action = .deleteTopic(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor))
case .forumTopicDeleted:
action = .deleteTopic(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0))
}
case let .channelAdminLogEventActionEditTopic(prevTopic, newTopic):
let prevInfo: AdminLogEventAction.ForumTopicInfo
switch prevTopic {
case let .forumTopic(flags, _, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
prevInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor), isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0)
case .forumTopicDeleted:
prevInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), isClosed: false, isHidden: false)
}
let newInfo: AdminLogEventAction.ForumTopicInfo
switch newTopic {
case let .forumTopic(flags, _, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
newInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor), isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0)
case .forumTopicDeleted:
newInfo = AdminLogEventAction.ForumTopicInfo(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), isClosed: false, isHidden: false)
}
action = .editTopic(prevInfo: prevInfo, newInfo: newInfo)
case let .channelAdminLogEventActionPinTopic(_, prevTopic, newTopic):
let prevInfo: EngineMessageHistoryThread.Info?
switch prevTopic {
case let .forumTopic(_, _, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
prevInfo = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor)
case .forumTopicDeleted:
prevInfo = EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0)
case .none:
prevInfo = nil
}
let newInfo: EngineMessageHistoryThread.Info?
switch newTopic {
case let .forumTopic(_, _, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
newInfo = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor)
case .forumTopicDeleted:
newInfo = EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0)
case .none:
newInfo = nil
}
action = .pinTopic(prevInfo: prevInfo, newInfo: newInfo)
case let .channelAdminLogEventActionToggleForum(newValue):
action = .toggleForum(isForum: newValue == .boolTrue)
case let .channelAdminLogEventActionToggleAntiSpam(newValue):
action = .toggleAntiSpam(isEnabled: newValue == .boolTrue)
case let .channelAdminLogEventActionChangePeerColor(prevValue, newValue):
guard case let .peerColor(_, prevColor, prevBackgroundEmojiIdValue) = prevValue, case let .peerColor(_, newColor, newBackgroundEmojiIdValue) = newValue else {
continue
}
let prevColorIndex = prevColor ?? 0
let prevEmojiId = prevBackgroundEmojiIdValue
let newColorIndex = newColor ?? 0
let newEmojiId = newBackgroundEmojiIdValue
action = .changeNameColor(prevColor: PeerNameColor(rawValue: prevColorIndex), prevIcon: prevEmojiId, newColor: PeerNameColor(rawValue: newColorIndex), newIcon: newEmojiId)
case let .channelAdminLogEventActionChangeProfilePeerColor(prevValue, newValue):
guard case let .peerColor(_, prevColorIndex, prevEmojiId) = prevValue, case let .peerColor(_, newColorIndex, newEmojiId) = newValue else {
continue
}
action = .changeProfileColor(prevColor: prevColorIndex.flatMap(PeerNameColor.init(rawValue:)), prevIcon: prevEmojiId, newColor: newColorIndex.flatMap(PeerNameColor.init(rawValue:)), newIcon: newEmojiId)
case let .channelAdminLogEventActionChangeWallpaper(prevValue, newValue):
let prev: TelegramWallpaper?
if case let .wallPaperNoFile(_, _, settings) = prevValue {
if settings == nil {
prev = nil
} else if case let .wallPaperSettings(flags, _, _, _, _, _, _, _) = settings, flags == 0 {
prev = nil
} else {
prev = TelegramWallpaper(apiWallpaper: prevValue)
}
} else {
prev = TelegramWallpaper(apiWallpaper: prevValue)
}
let new: TelegramWallpaper?
if case let .wallPaperNoFile(_, _, settings) = newValue {
if settings == nil {
new = nil
} else if case let .wallPaperSettings(flags, _, _, _, _, _, _, _) = settings, flags == 0 {
new = nil
} else {
new = TelegramWallpaper(apiWallpaper: newValue)
}
} else {
new = TelegramWallpaper(apiWallpaper: newValue)
}
action = .changeWallpaper(prev: prev, new: new)
case let .channelAdminLogEventActionChangeEmojiStatus(prevValue, newValue):
action = .changeStatus(prev: PeerEmojiStatus(apiStatus: prevValue), new: PeerEmojiStatus(apiStatus: newValue))
case let .channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset, newStickerset):
action = .changeEmojiPack(prev: StickerPackReference(apiInputSet: prevStickerset), new: StickerPackReference(apiInputSet: newStickerset))
case let .channelAdminLogEventActionToggleSignatureProfiles(newValue):
action = .toggleSignatureProfiles(boolFromApiValue(newValue))
case let .channelAdminLogEventActionParticipantSubExtend(prev, new):
let prevParticipant = ChannelParticipant(apiParticipant: prev)
let newParticipant = ChannelParticipant(apiParticipant: new)
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
}
case let .channelAdminLogEventActionToggleAutotranslation(newValue):
action = .toggleAutoTranslation(boolFromApiValue(newValue))
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
if let action = action {
events.append(AdminLogEvent(id: id, peerId: peerId, date: date, action: action))
}
}
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
if peers[peerId] == nil, let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
}
return AdminLogEventsResult(peerId: peerId, peers: peers, events: events)
}
|> castError(MTRpcError.self)
}
}
|> mapError {_ in return .generic}
}
return .complete()
}
}
@@ -0,0 +1,80 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum ChannelRestrictAdMessagesError {
case generic
}
func _internal_updateChannelRestrictAdMessages(account: Account, peerId: PeerId, restricted: Bool) -> Signal<Never, ChannelRestrictAdMessagesError> {
return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> castError(ChannelRestrictAdMessagesError.self)
|> mapToSignal { peer in
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
return .fail(.generic)
}
return account.network.request(Api.functions.channels.restrictSponsoredMessages(channel: inputChannel, restricted: restricted ? .boolTrue : .boolFalse))
|> `catch` { error -> Signal<Api.Updates, ChannelRestrictAdMessagesError> in
return .fail(.generic)
}
|> mapToSignal { updates -> Signal<Never, ChannelRestrictAdMessagesError> in
account.stateManager.addUpdates(updates)
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, currentData in
if let currentData = currentData as? CachedChannelData {
var flags = currentData.flags
if restricted {
flags.insert(.adsRestricted)
} else {
flags.remove(.adsRestricted)
}
return currentData.withUpdatedFlags(flags)
} else {
return currentData
}
})
}
|> castError(ChannelRestrictAdMessagesError.self)
|> ignoreValues
}
}
}
public enum AdMessagesEnableError {
case generic
}
func _internal_updateAdMessagesEnabled(account: Account, enabled: Bool) -> Signal<Never, AdMessagesEnableError> {
return account.network.request(Api.functions.account.toggleSponsoredMessages(enabled: enabled ? .boolTrue : .boolFalse))
|> `catch` { error -> Signal<Api.Bool, AdMessagesEnableError> in
return .fail(.generic)
}
|> mapToSignal { result -> Signal<Never, AdMessagesEnableError> in
guard case .boolTrue = result else {
return .fail(.generic)
}
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: [account.peerId], update: { peerId, currentData in
if let currentData = currentData as? CachedUserData {
var flags = currentData.flags
if enabled {
flags.insert(.adsEnabled)
} else {
flags.remove(.adsEnabled)
}
return currentData.withUpdatedFlags(flags)
} else {
return currentData
}
})
}
|> castError(AdMessagesEnableError.self)
|> ignoreValues
}
}
@@ -0,0 +1,213 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_updateChannelMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, rights: TelegramChatBannedRights?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> {
return _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId)
|> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in
return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer), let _ = transaction.getPeer(account.peerId), let memberPeer = transaction.getPeer(memberId), let inputPeer = apiInputPeer(memberPeer) {
let updatedParticipant: ChannelParticipant
if let currentParticipant = currentParticipant, case let .member(_, invitedAt, _, currentBanInfo, _, subscriptionUntilDate) = currentParticipant {
let banInfo: ChannelParticipantBannedInfo?
if let rights = rights, !rights.flags.isEmpty {
banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: currentBanInfo?.restrictedBy ?? account.peerId, timestamp: currentBanInfo?.timestamp ?? Int32(Date().timeIntervalSince1970), isMember: currentBanInfo?.isMember ?? true)
} else {
banInfo = nil
}
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: invitedAt, adminInfo: nil, banInfo: banInfo, rank: nil, subscriptionUntilDate: subscriptionUntilDate)
} else {
let banInfo: ChannelParticipantBannedInfo?
if let rights = rights, !rights.flags.isEmpty {
banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: account.peerId, timestamp: Int32(Date().timeIntervalSince1970), isMember: false)
} else {
banInfo = nil
}
updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: nil, banInfo: banInfo, rank: nil, subscriptionUntilDate: nil)
}
let apiRights: Api.ChatBannedRights
if let rights = rights, !rights.flags.isEmpty {
apiRights = rights.apiBannedRights
} else {
apiRights = .chatBannedRights(flags: 0, untilDate: 0)
}
return account.network.request(Api.functions.channels.editBanned(channel: inputChannel, participant: inputPeer, bannedRights: apiRights))
|> retryRequest
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant?, Bool), NoError> in
account.stateManager.addUpdates(result)
var wasKicked = false
var wasBanned = false
var wasMember = false
var wasAdmin = false
if let currentParticipant = currentParticipant {
switch currentParticipant {
case .creator:
break
case let .member(_, _, adminInfo, banInfo, _, _):
if let _ = adminInfo {
wasAdmin = true
}
if let banInfo = banInfo, !banInfo.rights.flags.isEmpty {
if banInfo.rights.flags.contains(.banReadMessages) {
wasKicked = true
} else {
wasBanned = true
wasMember = true
}
} else {
wasMember = true
}
}
}
var isKicked = false
var isBanned = false
if let rights = rights, !rights.flags.isEmpty {
if rights.flags.contains(.banReadMessages) {
isKicked = true
} else {
isBanned = true
}
}
let isMember = !wasKicked && !isKicked
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant?, Bool) in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData {
var updatedData = cachedData
if isKicked != wasKicked {
if let kickedCount = updatedData.participantsSummary.kickedCount {
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedKickedCount(max(0, kickedCount + (isKicked ? 1 : -1))))
}
}
if isBanned != wasBanned {
if let bannedCount = updatedData.participantsSummary.bannedCount {
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedBannedCount(max(0, bannedCount + (isBanned ? 1 : -1))))
}
}
if wasAdmin {
if let adminCount = updatedData.participantsSummary.adminCount {
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedAdminCount(max(0, adminCount - 1)))
}
}
if isMember != wasMember {
if let memberCount = updatedData.participantsSummary.memberCount {
updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedMemberCount(max(0, memberCount + (isMember ? 1 : -1))))
}
}
if let memberPeer = memberPeer as? TelegramUser, let _ = memberPeer.botInfo {
if isMember != wasMember {
if isMember {
// var updatedBotInfos = updatedData.botInfos
// if updatedBotInfos.firstIndex(where: { $0.peerId == memberPeer.id }) == nil {
// updatedBotInfos.append(CachedPeerBotInfo(peerId: memberPeer.id, botInfo: ))
// }
// updatedData = updatedData.withUpdatedBotInfos(updatedBotInfos)
} else {
let filteredBotInfos = updatedData.botInfos.filter { $0.peerId != memberPeer.id }
updatedData = updatedData.withUpdatedBotInfos(filteredBotInfos)
}
}
}
return updatedData
} else {
return cachedData
}
})
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
peers[memberPeer.id] = memberPeer
if let presence = transaction.getPeerPresence(peerId: memberPeer.id) {
presences[memberPeer.id] = presence
}
if case let .member(_, _, _, maybeBanInfo, _, _) = updatedParticipant, let banInfo = maybeBanInfo {
if let peer = transaction.getPeer(banInfo.restrictedBy) {
peers[peer.id] = peer
}
}
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences), isMember)
}
}
} else {
return .complete()
}
}
|> switchToLatest
}
}
func _internal_updateDefaultChannelMemberBannedRights(account: Account, peerId: PeerId, rights: TelegramChatBannedRights) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let _ = transaction.getPeer(account.peerId) else {
return .complete()
}
return account.network.request(Api.functions.messages.editChatDefaultBannedRights(peer: inputPeer, bannedRights: rights.apiBannedRights))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
account.stateManager.addUpdates(result)
return account.postbox.transaction { transaction -> Void in
guard let peer = transaction.getPeer(peerId) else {
return
}
if let peer = peer as? TelegramGroup {
updatePeersCustom(transaction: transaction, peers: [peer.updateDefaultBannedRights(rights, version: peer.version)], update: { _, updated in
return updated
})
} else if let peer = peer as? TelegramChannel {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedDefaultBannedRights(rights)], update: { _, updated in
return updated
})
}
}
|> ignoreValues
}
}
|> switchToLatest
}
func _internal_updateChannelBoostsToUnlockRestrictions(account: Account, peerId: PeerId, boosts: Int32) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
guard let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) else {
return .complete()
}
return account.network.request(Api.functions.channels.setBoostsToUnblockRestrictions(channel: inputChannel, boosts: boosts))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
account.stateManager.addUpdates(result)
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in
if let cachedData = cachedData as? CachedChannelData {
return cachedData.withUpdatedBoostsToUnrestrict(boosts)
}
return cachedData
})
}
|> ignoreValues
}
}
|> switchToLatest
}
@@ -0,0 +1,131 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum CreateChannelError {
case generic
case restricted
case tooMuchJoined
case tooMuchLocationBasedGroups
case serverProvided(String)
}
public enum CreateChannelMode {
case channel
case supergroup(isForum: Bool)
}
private func createChannel(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, mode: CreateChannelMode, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32?) -> Signal<PeerId, CreateChannelError> {
return postbox.transaction { transaction -> Signal<PeerId, CreateChannelError> in
var flags: Int32 = 0
switch mode {
case .channel:
flags |= (1 << 0)
case let .supergroup(isForum):
flags |= (1 << 1)
if isForum {
flags |= (1 << 5)
}
}
if isForHistoryImport {
flags |= (1 << 3)
}
var geoPoint: Api.InputGeoPoint?
var address: String?
if let location = location {
flags |= (1 << 2)
geoPoint = .inputGeoPoint(flags: 0, lat: location.latitude, long: location.longitude, accuracyRadius: nil)
address = location.address
}
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers)
if ttlPeriod != nil {
flags |= (1 << 4)
}
return network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address, ttlPeriod: ttlPeriod), automaticFloodWait: false)
|> mapError { error -> CreateChannelError in
if error.errorCode == 406 {
return .serverProvided(error.errorDescription)
} else if error.errorDescription == "CHANNELS_TOO_MUCH" {
return .tooMuchJoined
} else if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCH" {
return .tooMuchLocationBasedGroups
} else if error.errorDescription == "USER_RESTRICTED" {
return .restricted
} else {
return .generic
}
}
|> mapToSignal { updates -> Signal<PeerId, CreateChannelError> in
stateManager.addUpdates(updates)
if let message = updates.messages.first, let peerId = apiMessagePeerId(message) {
return postbox.multiplePeersView([peerId])
|> filter { view in
return view.peers[peerId] != nil
}
|> take(1)
|> map { _ in
return peerId
}
|> castError(CreateChannelError.self)
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|> mapToSignal { peerId -> Signal<PeerId, CreateChannelError> in
if title.contains("*forum") {
return _internal_setChannelForumMode(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, isForum: true, displayForumAsTabs: true)
|> castError(CreateChannelError.self)
|> map { _ -> PeerId in
}
|> then(.single(peerId))
} else {
return .single(peerId)
}
}
} else {
return .fail(.generic)
}
}
}
|> castError(CreateChannelError.self)
|> switchToLatest
}
func _internal_createChannel(account: Account, title: String, description: String?, username: String?) -> Signal<PeerId, CreateChannelError> {
return createChannel(postbox: account.postbox, network: account.network, stateManager: account.stateManager, title: title, description: description, username: nil, mode: .channel, ttlPeriod: nil)
}
public func _internal_createSupergroup(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, isForum: Bool, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32? = nil) -> Signal<PeerId, CreateChannelError> {
return createChannel(postbox: postbox, network: network, stateManager: stateManager, title: title, description: description, username: username, mode: .supergroup(isForum: isForum), location: location, isForHistoryImport: isForHistoryImport, ttlPeriod: ttlPeriod)
}
public enum DeleteChannelError {
case generic
}
func _internal_deleteChannel(account: Account, peerId: PeerId) -> Signal<Void, DeleteChannelError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> mapError { _ -> DeleteChannelError in }
|> mapToSignal { inputChannel -> Signal<Void, DeleteChannelError> in
if let inputChannel = inputChannel {
return account.network.request(Api.functions.channels.deleteChannel(channel: inputChannel))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, DeleteChannelError> in
return .fail(.generic)
}
|> mapToSignal { updates -> Signal<Void, DeleteChannelError> in
if let updates = updates {
account.stateManager.addUpdates(updates)
}
return .complete()
}
} else {
return .fail(.generic)
}
}
}
@@ -0,0 +1,49 @@
import Postbox
import TelegramApi
import SwiftSignalKit
public enum ChannelHistoryAvailabilityError {
case generic
case hasNotPermissions
}
func _internal_updateChannelHistoryAvailabilitySettingsInteractively(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, historyAvailableForNewMembers: Bool) -> Signal<Void, ChannelHistoryAvailabilityError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> castError(ChannelHistoryAvailabilityError.self)
|> mapToSignal { peer in
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
return .fail(.generic)
}
return network.request(Api.functions.channels.togglePreHistoryHidden(channel: inputChannel, enabled: historyAvailableForNewMembers ? .boolFalse : .boolTrue))
|> `catch` { error -> Signal<Api.Updates, ChannelHistoryAvailabilityError> in
if error.errorDescription == "CHAT_ADMIN_REQUIRED" {
return .fail(.hasNotPermissions)
}
return .fail(.generic)
}
|> mapToSignal { updates -> Signal<Void, ChannelHistoryAvailabilityError> in
accountStateManager.addUpdates(updates)
return postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, currentData in
if let currentData = currentData as? CachedChannelData {
var flags = currentData.flags
if historyAvailableForNewMembers {
flags.insert(.preHistoryEnabled)
} else {
flags.remove(.preHistoryEnabled)
}
return currentData.withUpdatedFlags(flags)
} else {
return currentData
}
})
} |> castError(ChannelHistoryAvailabilityError.self)
}
}
}
@@ -0,0 +1,127 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum ChannelMembersCategoryFilter {
case all
case search(String)
}
public enum ChannelMembersCategory {
case recent(ChannelMembersCategoryFilter)
case admins
case contacts(ChannelMembersCategoryFilter)
case bots(ChannelMembersCategoryFilter)
case restricted(ChannelMembersCategoryFilter)
case banned(ChannelMembersCategoryFilter)
case mentions(threadId: MessageId?, filter: ChannelMembersCategoryFilter)
}
func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, category: ChannelMembersCategory = .recent(.all), offset: Int32 = 0, limit: Int32 = 64, hash: Int64 = 0) -> Signal<[RenderedChannelParticipant]?, NoError> {
return postbox.transaction { transaction -> Signal<[RenderedChannelParticipant]?, NoError> in
if let peer = transaction.getPeer(peerId) as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
if case .broadcast = peer.info {
if let _ = peer.adminRights {
} else {
return .single(nil)
}
}
if peer.flags.contains(.isMonoforum) {
return .single(nil)
}
let apiFilter: Api.ChannelParticipantsFilter
switch category {
case let .recent(filter):
switch filter {
case .all:
apiFilter = .channelParticipantsRecent
case let .search(query):
apiFilter = .channelParticipantsSearch(q: query)
}
case let .mentions(threadId, filter):
switch filter {
case .all:
var flags: Int32 = 0
if threadId != nil {
flags |= 1 << 1
}
apiFilter = .channelParticipantsMentions(flags: flags, q: nil, topMsgId: threadId?.id)
case let .search(query):
var flags: Int32 = 0
if threadId != nil {
flags |= 1 << 1
}
if !query.isEmpty {
flags |= 1 << 0
}
apiFilter = .channelParticipantsMentions(flags: flags, q: query.isEmpty ? nil : query, topMsgId: threadId?.id)
}
case .admins:
apiFilter = .channelParticipantsAdmins
case let .contacts(filter):
switch filter {
case .all:
apiFilter = .channelParticipantsContacts(q: "")
case let .search(query):
apiFilter = .channelParticipantsContacts(q: query)
}
case .bots:
apiFilter = .channelParticipantsBots
case let .restricted(filter):
switch filter {
case .all:
apiFilter = .channelParticipantsBanned(q: "")
case let .search(query):
apiFilter = .channelParticipantsBanned(q: query)
}
case let .banned(filter):
switch filter {
case .all:
apiFilter = .channelParticipantsKicked(q: "")
case let .search(query):
apiFilter = .channelParticipantsKicked(q: query)
}
}
return network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: apiFilter, offset: offset, limit: limit, hash: hash))
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<[RenderedChannelParticipant]?, NoError> in
guard let result else {
return .single(nil)
}
return postbox.transaction { transaction -> [RenderedChannelParticipant]? in
var items: [RenderedChannelParticipant] = []
switch result {
case let .channelParticipants(_, participants, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var peers: [PeerId: Peer] = [:]
for id in parsedPeers.allIds {
if let peer = transaction.getPeer(id) {
peers[peer.id] = peer
}
}
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
if let peer = parsedPeers.get(participant.peerId) {
var renderedPresences: [PeerId: PeerPresence] = [:]
if let presence = transaction.getPeerPresence(peerId: participant.peerId) {
renderedPresences[participant.peerId] = presence
}
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences))
}
}
case .channelParticipantsNotModified:
return nil
}
return items
}
}
} else {
return .single([])
}
} |> switchToLatest
}
@@ -0,0 +1,192 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public enum ChannelOwnershipTransferError {
case generic
case twoStepAuthMissing
case twoStepAuthTooFresh(Int32)
case authSessionTooFresh(Int32)
case limitExceeded
case requestPassword
case invalidPassword
case adminsTooMuch
case userPublicChannelsTooMuch
case userLocatedGroupsTooMuch
case tooMuchJoined
case restricted
case userBlocked
}
func _internal_checkOwnershipTranfserAvailability(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, memberId: PeerId) -> Signal<Never, ChannelOwnershipTransferError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(memberId)
}
|> castError(ChannelOwnershipTransferError.self)
|> mapToSignal { user -> Signal<Never, ChannelOwnershipTransferError> in
guard let user = user else {
return .fail(.generic)
}
guard let apiUser = apiInputUser(user) else {
return .fail(.generic)
}
return network.request(Api.functions.channels.editCreator(channel: .inputChannelEmpty, userId: apiUser, password: .inputCheckPasswordEmpty))
|> mapError { error -> ChannelOwnershipTransferError in
if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .requestPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "PASSWORD_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .twoStepAuthTooFresh(value)
}
} else if error.errorDescription.hasPrefix("SESSION_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "SESSION_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .authSessionTooFresh(value)
}
} else if error.errorDescription == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH" {
return .userPublicChannelsTooMuch
} else if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCH" {
return .userLocatedGroupsTooMuch
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .adminsTooMuch
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .restricted
} else if error.errorDescription == "USER_BLOCKED" {
return .userBlocked
} else if error.errorDescription == "CHANNELS_TOO_MUCH" {
return .tooMuchJoined
}
return .generic
}
|> mapToSignal { updates -> Signal<Never, ChannelOwnershipTransferError> in
accountStateManager.addUpdates(updates)
return .complete()
}
}
}
func _internal_updateChannelOwnership(account: Account, channelId: PeerId, memberId: PeerId, password: String) -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> {
guard !password.isEmpty else {
return .fail(.invalidPassword)
}
return combineLatest(_internal_fetchChannelParticipant(account: account, peerId: channelId, participantId: account.peerId), _internal_fetchChannelParticipant(account: account, peerId: channelId, participantId: memberId))
|> mapError { _ -> ChannelOwnershipTransferError in
}
|> mapToSignal { currentCreator, currentParticipant -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
return account.postbox.transaction { transaction -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
if let channel = transaction.getPeer(channelId) as? TelegramChannel, let inputChannel = apiInputChannel(channel), let accountUser = transaction.getPeer(account.peerId), let user = transaction.getPeer(memberId), let inputUser = apiInputUser(user) {
let flags: TelegramChatAdminRightsFlags = TelegramChatAdminRightsFlags.peerSpecific(peer: .channel(channel))
let updatedParticipant = ChannelParticipant.creator(id: user.id, adminInfo: nil, rank: currentParticipant?.rank)
let updatedPreviousCreator = ChannelParticipant.member(id: accountUser.id, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: flags), promotedBy: accountUser.id, canBeEditedByAccountPeer: false), banInfo: nil, rank: currentCreator?.rank, subscriptionUntilDate: nil)
let checkPassword = _internal_twoStepAuthData(account.network)
|> mapError { error -> ChannelOwnershipTransferError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
return .single(.inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1)))
} else {
return .fail(.twoStepAuthMissing)
}
}
return checkPassword
|> mapToSignal { password -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
return account.network.request(Api.functions.channels.editCreator(channel: inputChannel, userId: inputUser, password: password), automaticFloodWait: false)
|> mapError { error -> ChannelOwnershipTransferError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .invalidPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
return .twoStepAuthMissing
} else if error.errorDescription.hasPrefix("PASSWORD_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "PASSWORD_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .twoStepAuthTooFresh(value)
}
} else if error.errorDescription.hasPrefix("SESSION_TOO_FRESH_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "SESSION_TOO_FRESH_".count)...])
if let value = Int32(timeout) {
return .authSessionTooFresh(value)
}
} else if error.errorDescription == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH" {
return .userPublicChannelsTooMuch
} else if error.errorDescription == "CHANNELS_ADMIN_LOCATED_TOO_MUCH" {
return .userLocatedGroupsTooMuch
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .adminsTooMuch
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .restricted
} else if error.errorDescription == "USER_BLOCKED" {
return .userBlocked
}
return .generic
}
|> mapToSignal { updates -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
account.stateManager.addUpdates(updates)
return account.postbox.transaction { transaction -> [(ChannelParticipant?, RenderedChannelParticipant)] in
transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let adminCount = cachedData.participantsSummary.adminCount {
var updatedAdminCount = adminCount
var wasAdmin = false
if let currentParticipant = currentParticipant {
switch currentParticipant {
case .creator:
wasAdmin = true
case let .member(_, _, adminInfo, _, _, _):
if let _ = adminInfo {
wasAdmin = true
}
}
}
if !wasAdmin {
updatedAdminCount = adminCount + 1
}
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedAdminCount(updatedAdminCount))
} else {
return cachedData
}
})
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
peers[accountUser.id] = accountUser
if let presence = transaction.getPeerPresence(peerId: accountUser.id) {
presences[accountUser.id] = presence
}
peers[user.id] = user
if let presence = transaction.getPeerPresence(peerId: user.id) {
presences[user.id] = presence
}
return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: accountUser, peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))]
}
|> mapError { _ -> ChannelOwnershipTransferError in }
}
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> ChannelOwnershipTransferError in }
|> switchToLatest
}
}
@@ -0,0 +1,24 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public struct RenderedChannelParticipant: Equatable {
public let participant: ChannelParticipant
public let peer: Peer
public let peers: [PeerId: Peer]
public let presences: [PeerId: PeerPresence]
public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) {
self.participant = participant
self.peer = peer
self.peers = peers
self.presences = presences
}
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer)
}
}
@@ -0,0 +1,277 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
final class CachedRecommendedChannels: Codable {
public let peerIds: [EnginePeer.Id]
public let count: Int32
public let isHidden: Bool
public let timestamp: Int32?
public init(peerIds: [EnginePeer.Id], count: Int32, isHidden: Bool, timestamp: Int32?) {
self.peerIds = peerIds
self.count = count
self.isHidden = isHidden
self.timestamp = timestamp
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.peerIds = try container.decode([Int64].self, forKey: "l").map(EnginePeer.Id.init)
self.count = try container.decodeIfPresent(Int32.self, forKey: "c") ?? 0
self.isHidden = try container.decode(Bool.self, forKey: "h")
self.timestamp = try container.decodeIfPresent(Int32.self, forKey: "ts")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: "l")
try container.encode(self.count, forKey: "c")
try container.encode(self.isHidden, forKey: "h")
try container.encodeIfPresent(self.timestamp, forKey: "ts")
}
}
private func entryId(peerId: EnginePeer.Id?) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
if let peerId {
cacheKey.setInt64(0, value: peerId.toInt64())
} else {
cacheKey.setInt64(0, value: 0)
}
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.recommendedChannels, key: cacheKey)
}
private func appsEntryId() -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: 0)
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.recommendedApps, key: cacheKey)
}
func _internal_requestRecommendedChannels(account: Account, peerId: EnginePeer.Id?, forceUpdate: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> (Peer?, Bool) in
if let peerId {
guard let channel = transaction.getPeer(peerId) as? TelegramChannel, case .broadcast = channel.info else {
return (nil, false)
}
if let entry = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedRecommendedChannels.self), !entry.peerIds.isEmpty && !forceUpdate {
return (nil, false)
} else {
return (channel, true)
}
} else {
if let entry = transaction.retrieveItemCacheEntry(id: entryId(peerId: nil))?.get(CachedRecommendedChannels.self), !entry.peerIds.isEmpty && !forceUpdate {
var shouldUpdate = false
if let timestamp = entry.timestamp {
if timestamp + 60 * 60 < Int32(Date().timeIntervalSince1970) {
shouldUpdate = true
}
} else {
shouldUpdate = true
}
return (nil, shouldUpdate)
} else {
return (nil, true)
}
}
}
|> mapToSignal { channel, shouldUpdate in
if !shouldUpdate {
return .complete()
}
var inputChannel: Api.InputChannel?
if peerId != nil {
if let inputChannelValue = channel.flatMap(apiInputChannel) {
inputChannel = inputChannelValue
} else {
return .complete()
}
}
var flags: Int32 = 0
if inputChannel != nil {
flags |= (1 << 0)
}
return account.network.request(Api.functions.channels.getChannelRecommendations(flags: flags, channel: inputChannel))
|> retryRequest
|> mapToSignal { result -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> [EnginePeer] in
let chats: [Api.Chat]
let parsedPeers: AccumulatedPeers
var count: Int32
switch result {
case let .chats(apiChats):
chats = apiChats
count = Int32(apiChats.count)
case let .chatsSlice(apiCount, apiChats):
chats = apiChats
count = apiCount
}
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var peers: [EnginePeer] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(EnginePeer(peer))
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat, let participantsCount = participantsCount {
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
var current = current as? CachedChannelData ?? CachedChannelData()
var participantsSummary = current.participantsSummary
participantsSummary.memberCount = participantsCount
current = current.withUpdatedParticipantsSummary(participantsSummary)
return current
})
}
}
}
if let entry = CodableEntry(CachedRecommendedChannels(peerIds: peers.map(\.id), count: count, isHidden: false, timestamp: Int32(Date().timeIntervalSince1970))) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
return peers
}
|> ignoreValues
}
}
}
func _internal_requestRecommendedApps(account: Account, forceUpdate: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> (Peer?, Bool) in
if let entry = transaction.retrieveItemCacheEntry(id: appsEntryId())?.get(CachedRecommendedChannels.self), !entry.peerIds.isEmpty && !forceUpdate {
var shouldUpdate = false
if let timestamp = entry.timestamp {
if timestamp + 60 * 60 < Int32(Date().timeIntervalSince1970) {
shouldUpdate = true
}
} else {
shouldUpdate = true
}
return (nil, shouldUpdate)
} else {
return (nil, true)
}
}
|> mapToSignal { channel, shouldUpdate in
if !shouldUpdate {
return .complete()
}
return account.network.request(Api.functions.bots.getPopularAppBots(offset: "", limit: 100))
|> retryRequest
|> mapToSignal { result -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> [EnginePeer] in
let users: [Api.User]
let parsedPeers: AccumulatedPeers
switch result {
case let .popularAppBots(_, nextOffset, apiUsers):
let _ = nextOffset
users = apiUsers
}
parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var peers: [EnginePeer] = []
for user in users {
if let peer = transaction.getPeer(user.peerId) {
peers.append(EnginePeer(peer))
}
}
if let entry = CodableEntry(CachedRecommendedChannels(peerIds: peers.map(\.id), count: Int32(peers.count), isHidden: false, timestamp: Int32(Date().timeIntervalSince1970))) {
transaction.putItemCacheEntry(id: appsEntryId(), entry: entry)
}
return peers
}
|> ignoreValues
}
}
}
public struct RecommendedChannels: Equatable {
public struct Channel: Equatable {
public var peer: EnginePeer
public var subscribers: Int32
public init(peer: EnginePeer, subscribers: Int32) {
self.peer = peer
self.subscribers = subscribers
}
}
public var channels: [Channel]
public var count: Int32
public var isHidden: Bool
public init(channels: [Channel], count: Int32, isHidden: Bool) {
self.channels = channels
self.count = count
self.isHidden = isHidden
}
}
func _internal_recommendedChannelPeerIds(account: Account, peerId: EnginePeer.Id?) -> Signal<[EnginePeer.Id]?, NoError> {
let key = PostboxViewKey.cachedItem(entryId(peerId: peerId))
return account.postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<[EnginePeer.Id]?, NoError> in
guard let cachedChannels = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedChannels.self), !cachedChannels.peerIds.isEmpty else {
return .single(nil)
}
return .single(cachedChannels.peerIds)
}
}
func _internal_recommendedAppPeerIds(account: Account) -> Signal<[EnginePeer.Id]?, NoError> {
let key = PostboxViewKey.cachedItem(appsEntryId())
return account.postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<[EnginePeer.Id]?, NoError> in
guard let cachedChannels = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedChannels.self), !cachedChannels.peerIds.isEmpty else {
return .single(nil)
}
return .single(cachedChannels.peerIds)
}
}
func _internal_recommendedChannels(account: Account, peerId: EnginePeer.Id?) -> Signal<RecommendedChannels?, NoError> {
let key = PostboxViewKey.cachedItem(entryId(peerId: peerId))
return account.postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<RecommendedChannels?, NoError> in
guard let cachedChannels = (views.views[key] as? CachedItemView)?.value?.get(CachedRecommendedChannels.self) else {
return .single(nil)
}
if cachedChannels.peerIds.isEmpty {
if peerId != nil {
return .single(nil)
} else {
return .single(RecommendedChannels(channels: [], count: 0, isHidden: false))
}
}
return account.postbox.multiplePeersView(cachedChannels.peerIds)
|> mapToSignal { view in
return account.postbox.transaction { transaction -> RecommendedChannels? in
var channels: [RecommendedChannels.Channel] = []
for peerId in cachedChannels.peerIds {
if let peer = view.peers[peerId] as? TelegramChannel, let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
if case .member = peer.participationStatus {
} else {
channels.append(RecommendedChannels.Channel(peer: EnginePeer(peer), subscribers: cachedData.participantsSummary.memberCount ?? 0))
}
}
}
return RecommendedChannels(channels: channels, count: cachedChannels.count, isHidden: cachedChannels.isHidden)
}
}
}
}
func _internal_toggleRecommendedChannelsHidden(account: Account, peerId: EnginePeer.Id, hidden: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction in
if let cachedChannels = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedRecommendedChannels.self) {
if let entry = CodableEntry(CachedRecommendedChannels(peerIds: cachedChannels.peerIds, count: cachedChannels.count, isHidden: hidden, timestamp: cachedChannels.timestamp)) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
}
}
|> ignoreValues
}
@@ -0,0 +1,51 @@
import Postbox
import TelegramApi
import SwiftSignalKit
public enum UpdateChannelJoinToSendError {
case generic
}
func _internal_toggleChannelJoinToSend(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, enabled: Bool) -> Signal<Never, UpdateChannelJoinToSendError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> castError(UpdateChannelJoinToSendError.self)
|> mapToSignal { peer in
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
return .fail(.generic)
}
return network.request(Api.functions.channels.toggleJoinToSend(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse))
|> `catch` { _ -> Signal<Api.Updates, UpdateChannelJoinToSendError> in
return .fail(.generic)
}
|> mapToSignal { updates -> Signal<Never, UpdateChannelJoinToSendError> in
accountStateManager.addUpdates(updates)
return .complete()
}
}
}
public enum UpdateChannelJoinRequestError {
case generic
}
func _internal_toggleChannelJoinRequest(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, enabled: Bool) -> Signal<Never, UpdateChannelJoinRequestError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> castError(UpdateChannelJoinRequestError.self)
|> mapToSignal { peer in
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
return .fail(.generic)
}
return network.request(Api.functions.channels.toggleJoinRequest(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse))
|> `catch` { _ -> Signal<Api.Updates, UpdateChannelJoinRequestError> in
return .fail(.generic)
}
|> mapToSignal { updates -> Signal<Never, UpdateChannelJoinRequestError> in
accountStateManager.addUpdates(updates)
return .complete()
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
func _internal_chatOnlineMembers(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<Int32, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Int32, NoError> in
guard let inputPeer = inputPeer else {
return .single(0)
}
return network.request(Api.functions.messages.getOnlines(peer: inputPeer))
|> map { value -> Int32 in
switch value {
case let .chatOnlines(onlines):
return onlines
}
}
|> `catch` { _ -> Signal<Int32, NoError> in
return .single(0)
}
}
}
@@ -0,0 +1,19 @@
import Foundation
import Postbox
import SwiftSignalKit
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,782 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
var isEnabled = false
switch peer {
case let .channel(channel):
if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
isEnabled = true
} else if channel.username != nil || !channel.usernames.isEmpty {
if !channel.flags.contains(.requestToJoin) {
isEnabled = true
}
}
case let .legacyGroup(group):
if case .creator = group.role {
isEnabled = true
} else if case let .admin(rights, _) = group.role {
if rights.rights.contains(.canInviteUsers) {
isEnabled = true
}
}
default:
break
}
return isEnabled
}
public enum ExportChatFolderError {
case generic
case sharedFolderLimitExceeded(limit: Int32, premiumLimit: Int32)
case limitExceeded(limit: Int32, premiumLimit: Int32)
case tooManyChannels(limit: Int32, premiumLimit: Int32)
case tooManyChannelsInAccount(limit: Int32, premiumLimit: Int32)
case someUserTooManyChannels
}
public struct ExportedChatFolderLink: Equatable {
public var title: String
public var link: String
public var peerIds: [EnginePeer.Id]
public var isRevoked: Bool
public init(
title: String,
link: String,
peerIds: [EnginePeer.Id],
isRevoked: Bool
) {
self.title = title
self.link = link
self.peerIds = peerIds
self.isRevoked = isRevoked
}
}
public extension ExportedChatFolderLink {
var slug: String {
var slug = self.link
if slug.hasPrefix("https://t.me/addlist/") {
slug = String(slug[slug.index(slug.startIndex, offsetBy: "https://t.me/addlist/".count)...])
}
return slug
}
}
func _internal_exportChatFolder(account: Account, filterId: Int32, title: String, peerIds: [PeerId]) -> Signal<ExportedChatFolderLink, ExportChatFolderError> {
return account.postbox.transaction { transaction -> [Api.InputPeer] in
return peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
}
|> castError(ExportChatFolderError.self)
|> mapToSignal { inputPeers -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
return account.network.request(Api.functions.chatlists.exportChatlistInvite(chatlist: .inputChatlistDialogFilter(filterId: filterId), title: title, peers: inputPeers))
|> `catch` { error -> Signal<Api.chatlists.ExportedChatlistInvite, ExportChatFolderError> in
if error.errorDescription == "INVITES_TOO_MUCH" || error.errorDescription == "CHATLISTS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(ExportChatFolderError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.chatlists.ExportedChatlistInvite, ExportChatFolderError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if error.errorDescription == "CHATLISTS_TOO_MUCH" {
if isPremium {
return .fail(.sharedFolderLimitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
} else {
return .fail(.sharedFolderLimitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
}
} else {
if isPremium {
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
} else {
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
}
}
}
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
return .fail(.someUserTooManyChannels)
} else if error.errorDescription == "CHANNELS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(ExportChatFolderError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.chatlists.ExportedChatlistInvite, ExportChatFolderError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.tooManyChannelsInAccount(limit: userPremiumLimits.maxChannelsCount, premiumLimit: userPremiumLimits.maxChannelsCount))
} else {
return .fail(.tooManyChannelsInAccount(limit: userDefaultLimits.maxChannelsCount, premiumLimit: userPremiumLimits.maxChannelsCount))
}
}
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
return account.postbox.transaction { transaction -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
switch result {
case let .exportedChatlistInvite(filter, invite):
let parsedFilter = ChatListFilter(apiFilter: filter)
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
if let index = state.filters.firstIndex(where: { $0.id == filterId }) {
state.filters[index] = parsedFilter
} else {
state.filters.append(parsedFilter)
}
state.remoteFilters = state.filters
return state
})
switch invite {
case let .exportedChatlistInvite(flags, title, url, peers):
return .single(ExportedChatFolderLink(
title: title,
link: url,
peerIds: peers.map(\.peerId),
isRevoked: (flags & (1 << 0)) != 0
))
}
}
}
|> castError(ExportChatFolderError.self)
|> switchToLatest
}
}
}
func _internal_getExportedChatFolderLinks(account: Account, id: Int32) -> Signal<[ExportedChatFolderLink]?, NoError> {
let accountPeerId = account.peerId
return account.network.request(Api.functions.chatlists.getExportedInvites(chatlist: .inputChatlistDialogFilter(filterId: id)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.chatlists.ExportedInvites?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[ExportedChatFolderLink]?, NoError> in
guard let result = result else {
return .single(nil)
}
return account.postbox.transaction { transaction -> [ExportedChatFolderLink]? in
switch result {
case let .exportedInvites(invites, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var result: [ExportedChatFolderLink] = []
for invite in invites {
switch invite {
case let .exportedChatlistInvite(flags, title, url, peers):
result.append(ExportedChatFolderLink(
title: title,
link: url,
peerIds: peers.map(\.peerId),
isRevoked: (flags & (1 << 0)) != 0
))
}
}
return result
}
}
}
}
public enum EditChatFolderLinkError {
case generic
}
func _internal_editChatFolderLink(account: Account, filterId: Int32, link: ExportedChatFolderLink, title: String?, peerIds: [EnginePeer.Id]?, revoke: Bool) -> Signal<ExportedChatFolderLink, EditChatFolderLinkError> {
return account.postbox.transaction { transaction -> Signal<ExportedChatFolderLink, EditChatFolderLinkError> in
var flags: Int32 = 0
if revoke {
flags |= 1 << 0
}
if title != nil {
flags |= 1 << 1
}
var peers: [Api.InputPeer]?
if let peerIds = peerIds {
flags |= 1 << 2
peers = peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
}
return account.network.request(Api.functions.chatlists.editExportedInvite(flags: flags, chatlist: .inputChatlistDialogFilter(filterId: filterId), slug: link.slug, title: title, peers: peers))
|> mapError { _ -> EditChatFolderLinkError in
return .generic
}
|> map { result in
switch result {
case let .exportedChatlistInvite(flags, title, url, peers):
return ExportedChatFolderLink(
title: title,
link: url,
peerIds: peers.map(\.peerId),
isRevoked: (flags & (1 << 0)) != 0
)
}
}
}
|> castError(EditChatFolderLinkError.self)
|> switchToLatest
}
public enum RevokeChatFolderLinkError {
case generic
}
func _internal_deleteChatFolderLink(account: Account, filterId: Int32, link: ExportedChatFolderLink) -> Signal<Never, RevokeChatFolderLinkError> {
return account.network.request(Api.functions.chatlists.deleteExportedInvite(chatlist: .inputChatlistDialogFilter(filterId: filterId), slug: link.slug))
|> mapError { error -> RevokeChatFolderLinkError in
return .generic
}
|> ignoreValues
}
public enum CheckChatFolderLinkError {
case generic
}
public final class ChatFolderLinkContents {
public let localFilterId: Int32?
public let title: ChatFolderTitle?
public let peers: [EnginePeer]
public let alreadyMemberPeerIds: Set<EnginePeer.Id>
public let memberCounts: [EnginePeer.Id: Int]
public init(
localFilterId: Int32?,
title: ChatFolderTitle?,
peers: [EnginePeer],
alreadyMemberPeerIds: Set<EnginePeer.Id>,
memberCounts: [EnginePeer.Id: Int]
) {
self.localFilterId = localFilterId
self.title = title
self.peers = peers
self.alreadyMemberPeerIds = alreadyMemberPeerIds
self.memberCounts = memberCounts
}
}
func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> {
let accountPeerId = account.peerId
return account.network.request(Api.functions.chatlists.checkChatlistInvite(slug: slug))
|> mapError { _ -> CheckChatFolderLinkError in
return .generic
}
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
switch result {
case let .chatlistInvite(flags, title, emoticon, peers, chats, users):
let _ = emoticon
let disableTitleAnimation = (flags & (1 << 1)) != 0
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
var memberCounts: [PeerId: Int] = [:]
for chat in chats {
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat {
if let participantsCount = participantsCount {
memberCounts[chat.peerId] = Int(participantsCount)
}
}
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var resultPeers: [EnginePeer] = []
var alreadyMemberPeerIds = Set<EnginePeer.Id>()
for peer in peers {
if let peerValue = transaction.getPeer(peer.peerId) {
resultPeers.append(EnginePeer(peerValue))
if transaction.getPeerChatListIndex(peer.peerId) != nil {
alreadyMemberPeerIds.insert(peer.peerId)
}
}
}
let titleText: String
let titleEntities: [MessageTextEntity]
switch title {
case let .textWithEntities(text, entities):
titleText = text
titleEntities = messageTextEntitiesFromApiEntities(entities)
}
return ChatFolderLinkContents(localFilterId: nil, title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimation), peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
case let .chatlistInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
var memberCounts: [PeerId: Int] = [:]
for chat in chats {
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat {
if let participantsCount = participantsCount {
memberCounts[chat.peerId] = Int(participantsCount)
}
}
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let currentFilters = _internal_currentChatListFilters(transaction: transaction)
var currentFilterTitle: ChatFolderTitle?
if let index = currentFilters.firstIndex(where: { $0.id == filterId }) {
switch currentFilters[index] {
case let .filter(_, title, _, _):
currentFilterTitle = title
default:
break
}
}
var resultPeers: [EnginePeer] = []
var alreadyMemberPeerIds = Set<EnginePeer.Id>()
for peer in missingPeers {
if let peerValue = transaction.getPeer(peer.peerId) {
resultPeers.append(EnginePeer(peerValue))
}
}
for peer in alreadyPeers {
if !resultPeers.contains(where: { $0.id == peer.peerId }) {
if let peerValue = transaction.getPeer(peer.peerId) {
resultPeers.append(EnginePeer(peerValue))
}
}
alreadyMemberPeerIds.insert(peer.peerId)
}
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
}
}
|> castError(CheckChatFolderLinkError.self)
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
if result.localFilterId == nil && result.peers.isEmpty {
return .fail(.generic)
}
return .single(result)
}
}
}
public enum JoinChatFolderLinkError {
case generic
case dialogFilterLimitExceeded(limit: Int32, premiumLimit: Int32)
case sharedFolderLimitExceeded(limit: Int32, premiumLimit: Int32)
case tooManyChannels(limit: Int32, premiumLimit: Int32)
case tooManyChannelsInAccount(limit: Int32, premiumLimit: Int32)
}
public final class JoinChatFolderResult {
public let folderId: Int32
public let title: ChatFolderTitle
public let newChatCount: Int
public init(folderId: Int32, title: ChatFolderTitle, newChatCount: Int) {
self.folderId = folderId
self.title = title
self.newChatCount = newChatCount
}
}
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> {
return account.postbox.transaction { transaction -> ([Api.InputPeer], Int) in
var newChatCount = 0
for peerId in peerIds {
if transaction.getPeerChatListIndex(peerId) == nil {
var canJoin = true
if let peer = transaction.getPeer(peerId) {
if let channel = peer as? TelegramChannel {
if case .kicked = channel.participationStatus {
canJoin = false
}
}
}
if canJoin {
newChatCount += 1
}
}
}
return (peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer), newChatCount)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { inputPeers, newChatCount -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> in
return account.network.request(Api.functions.chatlists.joinChatlistInvite(slug: slug, peers: inputPeers))
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.tooManyChannels(limit: userPremiumLimits.maxFolderChatsCount, premiumLimit: userPremiumLimits.maxFolderChatsCount))
} else {
return .fail(.tooManyChannels(limit: userDefaultLimits.maxFolderChatsCount, premiumLimit: userPremiumLimits.maxFolderChatsCount))
}
}
} else if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.dialogFilterLimitExceeded(limit: userPremiumLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
} else {
return .fail(.dialogFilterLimitExceeded(limit: userDefaultLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
}
}
} else if error.errorDescription == "CHATLISTS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.sharedFolderLimitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
} else {
return .fail(.sharedFolderLimitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
}
}
} else if error.errorDescription == "CHANNELS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.tooManyChannelsInAccount(limit: userPremiumLimits.maxChannelsCount, premiumLimit: userPremiumLimits.maxChannelsCount))
} else {
return .fail(.tooManyChannelsInAccount(limit: userDefaultLimits.maxChannelsCount, premiumLimit: userPremiumLimits.maxChannelsCount))
}
}
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> in
account.stateManager.addUpdates(result)
var folderResult: JoinChatFolderResult?
for update in result.allUpdates {
if case let .updateDialogFilter(_, id, data) = update {
if let data = data, case let .filter(_, title, _, _) = ChatListFilter(apiFilter: data) {
folderResult = JoinChatFolderResult(folderId: id, title: title, newChatCount: newChatCount)
}
break
}
}
if let folderResult = folderResult {
return _internal_updatedChatListFilters(postbox: account.postbox)
|> castError(JoinChatFolderLinkError.self)
|> filter { filters -> Bool in
if filters.contains(where: { $0.id == folderResult.folderId }) {
return true
} else {
return false
}
}
|> take(1)
|> map { _ -> JoinChatFolderResult in
return folderResult
}
} else {
return .fail(.generic)
}
}
}
}
public final class ChatFolderUpdates: Equatable {
public let folderId: Int32
fileprivate let title: ChatFolderTitle
fileprivate let missingPeers: [EnginePeer]
fileprivate let memberCounts: [EnginePeer.Id: Int]
public var availableChatsToJoin: Int {
return self.missingPeers.count
}
public var chatFolderLinkContents: ChatFolderLinkContents {
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set(), memberCounts: self.memberCounts)
}
fileprivate init(
folderId: Int32,
title: ChatFolderTitle,
missingPeers: [EnginePeer],
memberCounts: [EnginePeer.Id: Int]
) {
self.folderId = folderId
self.title = title
self.missingPeers = missingPeers
self.memberCounts = memberCounts
}
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
if lhs.folderId != rhs.folderId {
return false
}
if lhs.missingPeers.map(\.id) != rhs.missingPeers.map(\.id) {
return false
}
return true
}
}
private struct FirstTimeFolderUpdatesKey: Hashable {
var accountId: AccountRecordId
var folderId: Int32
}
private var firstTimeFolderUpdates = Set<FirstTimeFolderUpdatesKey>()
func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> Signal<Never, NoError> {
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> (ChatListFiltersState, AppConfiguration) in
return (_internal_currentChatListFiltersState(transaction: transaction), currentAppConfiguration(transaction: transaction))
}
|> mapToSignal { state, appConfig -> Signal<Never, NoError> in
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let key = FirstTimeFolderUpdatesKey(accountId: account.id, folderId: folderId)
if firstTimeFolderUpdates.contains(key) {
if let current = state.updates.first(where: { $0.folderId == folderId }) {
var updateInterval: Int32 = 3600
if let data = appConfig.data {
if let value = data["chatlist_update_period"] as? Double {
updateInterval = Int32(value)
}
}
#if DEBUG
if "".isEmpty {
updateInterval = 5
}
#endif
if current.timestamp + updateInterval >= timestamp {
return .complete()
}
}
} else {
firstTimeFolderUpdates.insert(key)
}
return account.network.request(Api.functions.chatlists.getChatlistUpdates(chatlist: .inputChatlistDialogFilter(filterId: folderId)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.chatlists.ChatlistUpdates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result = result else {
return account.postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.updates.removeAll(where: { $0.folderId == folderId })
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: [], memberCounts: []))
return state
})
}
|> ignoreValues
}
switch result {
case let .chatlistUpdates(missingPeers, chats, users):
return account.postbox.transaction { transaction -> Void in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
var memberCounts: [ChatListFiltersState.ChatListFilterUpdates.MemberCount] = []
for chat in chats {
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat {
if let participantsCount = participantsCount {
memberCounts.append(ChatListFiltersState.ChatListFilterUpdates.MemberCount(id: chat.peerId, count: participantsCount))
}
}
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.updates.removeAll(where: { $0.folderId == folderId })
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId), memberCounts: memberCounts))
return state
})
}
|> ignoreValues
}
}
}
}
func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
struct InternalData: Equatable {
var title: ChatFolderTitle
var peerIds: [EnginePeer.Id]
var memberCounts: [EnginePeer.Id: Int]
}
return _internal_updatedChatListFiltersState(postbox: account.postbox)
|> map { state -> InternalData? in
guard let update = state.updates.first(where: { $0.folderId == folderId }) else {
return nil
}
guard let folder = state.filters.first(where: { $0.id == folderId }) else {
return nil
}
guard case let .filter(_, title, _, data) = folder, data.isShared else {
return nil
}
let filteredPeerIds: [PeerId] = update.peerIds.filter { !data.includePeers.peers.contains($0) }
var memberCounts: [PeerId: Int] = [:]
for item in update.memberCounts {
memberCounts[item.id] = Int(item.count)
}
return InternalData(title: title, peerIds: filteredPeerIds, memberCounts: memberCounts)
}
|> distinctUntilChanged
|> mapToSignal { internalData -> Signal<ChatFolderUpdates?, NoError> in
guard let internalData = internalData else {
return .single(nil)
}
if internalData.peerIds.isEmpty {
return .single(nil)
}
return account.postbox.transaction { transaction -> ChatFolderUpdates? in
var peers: [EnginePeer] = []
for peerId in internalData.peerIds {
if let peer = transaction.getPeer(peerId) {
peers.append(EnginePeer(peer))
}
}
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers, memberCounts: internalData.memberCounts)
}
}
}
func _internal_joinAvailableChatsInFolder(account: Account, updates: ChatFolderUpdates, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
return account.postbox.transaction { transaction -> [Api.InputPeer] in
return peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
return account.network.request(Api.functions.chatlists.joinChatlistUpdates(chatlist: .inputChatlistDialogFilter(filterId: updates.folderId), peers: inputPeers))
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.dialogFilterLimitExceeded(limit: userPremiumLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
} else {
return .fail(.dialogFilterLimitExceeded(limit: userDefaultLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
}
}
} else if error.errorDescription == "FILTERS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.sharedFolderLimitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
} else {
return .fail(.sharedFolderLimitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
}
}
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
account.stateManager.addUpdates(result)
return .complete()
}
}
}
func _internal_hideChatFolderUpdates(account: Account, folderId: Int32) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.updates.removeAll(where: { $0.folderId == folderId })
return state
})
}
|> mapToSignal { _ -> Signal<Never, NoError> in
return account.network.request(Api.functions.chatlists.hideChatlistUpdates(chatlist: .inputChatlistDialogFilter(filterId: folderId)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}
}
func _internal_leaveChatFolder(account: Account, folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> [Api.InputPeer] in
return removePeerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
}
|> mapToSignal { inputPeers -> Signal<Never, NoError> in
return account.network.request(Api.functions.chatlists.leaveChatlist(chatlist: .inputChatlistDialogFilter(filterId: folderId), peers: inputPeers))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates = updates {
account.stateManager.addUpdates(updates)
}
return account.postbox.transaction { transaction -> Void in
}
|> ignoreValues
}
}
}
func _internal_requestLeaveChatFolderSuggestions(account: Account, folderId: Int32) -> Signal<[EnginePeer.Id], NoError> {
return account.network.request(Api.functions.chatlists.getLeaveChatlistSuggestions(chatlist: .inputChatlistDialogFilter(filterId: folderId)))
|> map { result -> [EnginePeer.Id] in
return result.map(\.peerId)
}
|> `catch` { _ -> Signal<[EnginePeer.Id], NoError> in
return .single([])
}
}
@@ -0,0 +1,35 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
func _internal_importContactToken(account: Account, token: String) -> Signal<EnginePeer?, NoError> {
return account.network.request(Api.functions.contacts.importContactToken(token: token))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.User?, NoError> in
return .single(nil)
}
|> map { result -> EnginePeer? in
return result.flatMap { EnginePeer(TelegramUser(user: $0)) }
}
}
public struct ExportedContactToken {
public let url: String
public let expires: Int32
}
func _internal_exportContactToken(account: Account) -> Signal<ExportedContactToken?, NoError> {
return account.network.request(Api.functions.contacts.exportContactToken())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.ExportedContactToken?, NoError> in
return .single(nil)
}
|> map { result -> ExportedContactToken? in
if let result = result, case let .exportedContactToken(url, expires) = result {
return ExportedContactToken(url: url, expires: expires)
} else {
return nil
}
}
}
@@ -0,0 +1,83 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum ConvertGroupToSupergroupError {
case generic
case tooManyChannels
}
func _internal_convertGroupToSupergroup(account: Account, peerId: PeerId, additionalProcessing: ((EnginePeer.Id) -> Signal<Never, NoError>)?) -> Signal<PeerId, ConvertGroupToSupergroupError> {
return account.network.request(Api.functions.messages.migrateChat(chatId: peerId.id._internalGetInt64Value()))
|> mapError { error -> ConvertGroupToSupergroupError in
if error.errorDescription == "CHANNELS_TOO_MUCH" {
return .tooManyChannels
} else {
return .generic
}
}
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|> mapToSignal { updates -> Signal<PeerId, ConvertGroupToSupergroupError> in
var createdPeerId: PeerId?
for message in updates.messages {
if apiMessagePeerId(message) != peerId {
createdPeerId = apiMessagePeerId(message)
break
}
}
if let createdPeerId = createdPeerId {
let additionalProcessingValue: Signal<Never, NoError> = additionalProcessing?(createdPeerId) ?? Signal<Never, NoError>.complete()
return additionalProcessingValue
|> map { _ -> Bool in }
|> castError(ConvertGroupToSupergroupError.self)
|> then(Signal<Bool, ConvertGroupToSupergroupError>.single(true))
|> mapToSignal { _ ->Signal<PeerId, ConvertGroupToSupergroupError> in
account.stateManager.addUpdates(updates)
return _internal_fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: createdPeerId, network: account.network, postbox: account.postbox)
|> castError(ConvertGroupToSupergroupError.self)
|> mapToSignal { _ -> Signal<PeerId, ConvertGroupToSupergroupError> in
return account.postbox.multiplePeersView([createdPeerId])
|> filter { view in
return view.peers[createdPeerId] != nil
}
|> take(1)
|> map { _ in
return createdPeerId
}
|> mapError { _ -> ConvertGroupToSupergroupError in
}
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
}
}
} else {
account.stateManager.addUpdates(updates)
return .fail(.generic)
}
}
}
public enum ConvertGroupToGigagroupError {
case generic
}
public func convertGroupToGigagroup(account: Account, peerId: PeerId) -> Signal<Never, ConvertGroupToGigagroupError> {
return account.postbox.transaction { transaction -> Signal<Never, ConvertGroupToGigagroupError> in
guard let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) else {
return .fail(.generic)
}
return account.network.request(Api.functions.channels.convertToGigagroup(channel: inputChannel))
|> mapError { _ -> ConvertGroupToGigagroupError in return .generic }
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|> mapToSignal { updates -> Signal<Never, ConvertGroupToGigagroupError> in
account.stateManager.addUpdates(updates)
return .complete()
}
}
|> mapError { _ -> ConvertGroupToGigagroupError in }
|> switchToLatest
}
@@ -0,0 +1,17 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_toggleMessageCopyProtection(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.toggleNoForwards(peer: inputPeer, enabled: enabled ? .boolTrue : .boolFalse)) |> `catch` { _ in .complete() } |> map { updates -> Void in
account.stateManager.addUpdates(updates)
}
} else {
return .complete()
}
} |> switchToLatest
}
@@ -0,0 +1,102 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum CreateGroupError {
case generic
case privacy
case restricted
case tooMuchJoined
case tooMuchLocationBasedGroups
case serverProvided(String)
}
public struct CreateGroupResult {
public var peerId: EnginePeer.Id
public var result: TelegramInvitePeersResult
public init(
peerId: EnginePeer.Id,
result: TelegramInvitePeersResult
) {
self.peerId = peerId
self.result = result
}
}
func _internal_createGroup(account: Account, title: String, peerIds: [PeerId], ttlPeriod: Int32?) -> Signal<CreateGroupResult?, CreateGroupError> {
return account.postbox.transaction { transaction -> Signal<CreateGroupResult?, CreateGroupError> in
var inputUsers: [Api.InputUser] = []
for peerId in peerIds {
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
inputUsers.append(inputUser)
} else {
return .single(nil)
}
}
var ttlPeriod = ttlPeriod
if ttlPeriod == nil {
ttlPeriod = 0
}
var flags: Int32 = 0
if let _ = ttlPeriod {
flags |= 1 << 0
}
return account.network.request(Api.functions.messages.createChat(flags: flags, users: inputUsers, title: title, ttlPeriod: ttlPeriod))
|> mapError { error -> CreateGroupError in
if error.errorDescription == "USERS_TOO_FEW" {
return .privacy
}
return .generic
}
|> mapToSignal { result -> Signal<CreateGroupResult?, CreateGroupError> in
let updatesValue: Api.Updates
let missingInviteesValue: [Api.MissingInvitee]
switch result {
case let .invitedUsers(updates, missingInvitees):
updatesValue = updates
missingInviteesValue = missingInvitees
}
account.stateManager.addUpdates(updatesValue)
if let message = updatesValue.messages.first, let peerId = apiMessagePeerId(message) {
return account.postbox.multiplePeersView([peerId])
|> filter { view in
return view.peers[peerId] != nil
}
|> take(1)
|> castError(CreateGroupError.self)
|> mapToSignal { _ -> Signal<CreateGroupResult?, CreateGroupError> in
return account.postbox.transaction { transaction -> CreateGroupResult in
return CreateGroupResult(
peerId: peerId,
result: TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in
switch invitee {
case let .missingInvitee(flags, userId):
guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else {
return nil
}
return TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: (flags & (1 << 0)) != 0,
premiumRequiredToContact: (flags & (1 << 1)) != 0
)
}
})
)
}
|> castError(CreateGroupError.self)
}
} else {
return .single(nil)
}
}
}
|> castError(CreateGroupError.self)
|> switchToLatest
}
@@ -0,0 +1,56 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum CreateSecretChatError {
case generic
case limitExceeded
case premiumRequired(EnginePeer)
}
func _internal_createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId, CreateSecretChatError> {
return account.postbox.transaction { transaction -> Signal<PeerId, CreateSecretChatError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return validatedEncryptionConfig(postbox: account.postbox, network: account.network)
|> mapError { _ -> CreateSecretChatError in }
|> mapToSignal { config -> Signal<PeerId, CreateSecretChatError> in
let aBytes = malloc(256)!
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
var gValue: Int32 = config.g.byteSwapped
let g = Data(bytes: &gValue, count: 4)
let p = config.p.makeData()
let aData = a.makeData()
let ga = MTExp(account.network.encryptionProvider, g, aData, p)!
if !MTCheckIsSafeGAOrB(account.network.encryptionProvider, ga, p) {
return .fail(.generic)
}
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)), automaticFloodWait: false)
|> mapError { error -> CreateSecretChatError in
if error.errorDescription.hasPrefix("FLOOD_WAIT_") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PRIVACY_PREMIUM_REQUIRED") {
return .premiumRequired(.init(peer))
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<PeerId, CreateSecretChatError> in
return account.postbox.transaction { transaction -> PeerId in
updateSecretChat(encryptionProvider: account.network.encryptionProvider, accountPeerId: account.peerId, transaction: transaction, mediaBox: account.postbox.mediaBox, chat: result, requestData: SecretChatRequestData(g: config.g, p: config.p, a: a))
return result.peerId
} |> mapError { _ -> CreateSecretChatError in }
}
}
} else {
return .fail(.generic)
}
} |> mapError { _ -> CreateSecretChatError in } |> switchToLatest
}
@@ -0,0 +1,34 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
func _internal_findChannelById(accountPeerId: PeerId, postbox: Postbox, network: Network, channelId: Int64) -> Signal<Peer?, NoError> {
return network.request(Api.functions.channels.getChannels(id: [.inputChannel(channelId: channelId, accessHash: 0)]))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Chats?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Peer?, NoError> in
return postbox.transaction { transaction -> Peer? in
guard let result = result else {
return nil
}
let chats: [Api.Chat]
switch result {
case let .chats(apiChats):
chats = apiChats
case let .chatsSlice(_, apiChats):
chats = apiChats
}
guard let id = chats.first?.peerId else {
return nil
}
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
return transaction.getPeer(id)
}
}
}
@@ -0,0 +1,237 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
public enum GroupsInCommonDataState: Equatable {
case loading
case ready(canLoadMore: Bool)
}
public struct GroupsInCommonState: Equatable {
public var peers: [RenderedPeer]
public var count: Int?
public var dataState: GroupsInCommonDataState
}
private final class GroupsInCommonContextImpl {
private let queue: Queue
private let account: Account
private let peerId: PeerId
private let hintGroupInCommon: PeerId?
private let disposable = MetaDisposable()
private let cacheDisposable = MetaDisposable()
private var peers: [RenderedPeer] = []
private var count: Int?
private var dataState: GroupsInCommonDataState = .ready(canLoadMore: true)
private let stateValue = Promise<GroupsInCommonState>()
var state: Signal<GroupsInCommonState, NoError> {
return self.stateValue.get()
}
init(queue: Queue, account: Account, peerId: PeerId, hintGroupInCommon: PeerId?) {
self.queue = queue
self.account = account
self.peerId = peerId
self.hintGroupInCommon = hintGroupInCommon
if let hintGroupInCommon = hintGroupInCommon {
let _ = (self.account.postbox.loadedPeerWithId(hintGroupInCommon)
|> deliverOn(self.queue)).start(next: { [weak self] peer in
if let strongSelf = self {
strongSelf.peers.append(RenderedPeer(peer: peer))
strongSelf.pushState()
}
})
}
self.loadMore(limit: 32)
}
deinit {
self.disposable.dispose()
self.cacheDisposable.dispose()
}
func loadMore(limit: Int32) {
let peerId = self.peerId
if case .ready(true) = self.dataState {
if self.peers.isEmpty {
self.cacheDisposable.set((self.account.postbox.transaction { transaction -> ([RenderedPeer], Int32)? in
if let cached = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedGroupsInCommon.self) {
var peers: [RenderedPeer] = []
for peerId in cached.peerIds {
if let peer = transaction.getPeer(peerId) {
peers.append(RenderedPeer(peer: peer))
}
}
return (peers, cached.count)
}
return nil
} |> deliverOn(self.queue)).start(next: { [weak self] peersAndCount in
guard let self else {
return
}
if case .loading = self.dataState, let (peers, count) = peersAndCount {
self.peers = peers
self.count = Int(count)
self.pushState()
}
}))
}
self.dataState = .loading
self.pushState()
let maxId = self.peers.last?.peerId.id
let accountPeerId = self.account.peerId
let network = self.account.network
let postbox = self.account.postbox
let signal: Signal<([Peer], Int), NoError> = self.account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputUser -> Signal<([Peer], Int), NoError> in
guard let inputUser = inputUser else {
return .single(([], 0))
}
return network.request(Api.functions.messages.getCommonChats(userId: inputUser, maxId: maxId?._internalGetInt64Value() ?? 0, limit: limit))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Chats?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<([Peer], Int), NoError> in
let chats: [Api.Chat]
let count: Int?
if let result = result {
switch result {
case let .chats(apiChats):
chats = apiChats
count = nil
case let .chatsSlice(apiCount, apiChats):
chats = apiChats
count = Int(apiCount)
}
} else {
chats = []
count = nil
}
return postbox.transaction { transaction -> ([Peer], Int) in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var peers: [Peer] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(peer)
}
}
return (peers, count ?? 0)
}
}
}
self.disposable.set((signal
|> deliverOn(self.queue)).start(next: { [weak self] (peers, count) in
guard let strongSelf = self else {
return
}
var existingPeers = Set(strongSelf.peers.map { $0.peerId })
for peer in peers {
if !existingPeers.contains(peer.id) {
existingPeers.insert(peer.id)
strongSelf.peers.append(RenderedPeer(peer: peer))
}
}
if maxId == nil {
strongSelf.cacheDisposable.set(postbox.transaction { transaction in
if let entry = CodableEntry(CachedGroupsInCommon(peerIds: peers.map { $0.id }, count: Int32(count))) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
}.start())
}
let updatedCount = max(strongSelf.peers.count, count)
strongSelf.count = updatedCount
strongSelf.dataState = .ready(canLoadMore: count != 0 && updatedCount > strongSelf.peers.count)
strongSelf.pushState()
}))
}
}
private func pushState() {
self.stateValue.set(.single(GroupsInCommonState(peers: self.peers, count: self.count, dataState: self.dataState)))
}
}
public final class GroupsInCommonContext {
private let queue: Queue = .mainQueue()
private let impl: QueueLocalObject<GroupsInCommonContextImpl>
public var state: Signal<GroupsInCommonState, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.state.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public init(account: Account, peerId: PeerId, hintGroupInCommon: PeerId? = nil) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return GroupsInCommonContextImpl(queue: queue, account: account, peerId: peerId, hintGroupInCommon: hintGroupInCommon)
})
}
public func loadMore() {
self.impl.with { impl in
impl.loadMore(limit: 32)
}
}
}
private final class CachedGroupsInCommon: Codable {
enum CodingKeys: String, CodingKey {
case peerIds
case count
}
var peerIds: [PeerId]
let count: Int32
init(peerIds: [PeerId], count: Int32) {
self.peerIds = peerIds
self.count = count
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.peerIds = try container.decode([PeerId].self, forKey: .peerIds)
self.count = try container.decode(Int32.self, forKey: .count)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.peerIds, forKey: .peerIds)
try container.encode(self.count, forKey: .count)
}
}
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedGroupsInCommon, key: cacheKey)
}
@@ -0,0 +1,49 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public struct InactiveChannel : Equatable {
public let peer: Peer
public let lastActivityDate: Int32
public let participantsCount: Int32?
init(peer: Peer, lastActivityDate: Int32, participantsCount: Int32?) {
self.peer = peer
self.lastActivityDate = lastActivityDate
self.participantsCount = participantsCount
}
public static func ==(lhs: InactiveChannel, rhs: InactiveChannel) -> Bool {
return lhs.peer.isEqual(rhs.peer) && lhs.lastActivityDate == rhs.lastActivityDate && lhs.participantsCount == rhs.participantsCount
}
}
func _internal_inactiveChannelList(network: Network) -> Signal<[InactiveChannel], NoError> {
return network.request(Api.functions.channels.getInactiveChannels())
|> retryRequest
|> map { result in
switch result {
case let .inactiveChats(dates, chats, _):
let channels = chats.compactMap {
parseTelegramGroupOrChannel(chat: $0)
}
var participantsCounts: [PeerId: Int32] = [:]
for chat in chats {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _, _, _, _, _, _, _, _, _, _):
if let participantsCountValue = participantsCountValue {
participantsCounts[chat.peerId] = participantsCountValue
}
default:
break
}
}
var inactive: [InactiveChannel] = []
for (i, channel) in channels.enumerated() {
inactive.append(InactiveChannel(peer: channel, lastActivityDate: i < dates.count ? dates[i] : 0, participantsCount: participantsCounts[channel.id]))
}
return inactive
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
import MtProtoKit
public enum JoinChannelError {
case generic
case tooMuchJoined
case tooMuchUsers
case inviteRequestSent
}
func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
return account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> castError(JoinChannelError.self)
|> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in
let request: Signal<Api.Updates, MTRpcError>
if let hash = hash {
request = account.network.request(Api.functions.messages.importChatInvite(hash: hash))
} else if let inputChannel = apiInputChannel(peer) {
request = account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
} else {
request = .fail(.init())
}
return request
|> mapError { error -> JoinChannelError in
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":
return .tooMuchJoined
case "USERS_TOO_MUCH":
return .tooMuchUsers
case "INVITE_REQUEST_SENT":
return .inviteRequestSent
default:
return .generic
}
}
|> mapToSignal { updates -> Signal<RenderedChannelParticipant?, JoinChannelError> in
account.stateManager.addUpdates(updates)
let channels = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) }.compactMap(apiInputChannel)
if let inputChannel = channels.first {
return account.network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: .inputPeerSelf))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.channels.ChannelParticipant?, JoinChannelError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<RenderedChannelParticipant?, JoinChannelError> in
guard let result = result else {
return .fail(.generic)
}
return account.postbox.transaction { transaction -> RenderedChannelParticipant? in
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
guard let peer = transaction.getPeer(account.peerId) else {
return nil
}
peers[account.peerId] = peer
if let presence = transaction.getPeerPresence(peerId: account.peerId) {
presences[account.peerId] = presence
}
let updatedParticipant: ChannelParticipant
switch result {
case let .channelParticipant(participant, _, _):
updatedParticipant = ChannelParticipant(apiParticipant: participant)
}
if case let .member(_, _, maybeAdminInfo, _, _, _) = updatedParticipant {
if let adminInfo = maybeAdminInfo {
if let peer = transaction.getPeer(adminInfo.promotedBy) {
peers[peer.id] = peer
}
}
}
return RenderedChannelParticipant(participant: updatedParticipant, peer: peer, peers: peers, presences: presences)
}
|> castError(JoinChannelError.self)
}
} else {
return .fail(.generic)
}
}
|> afterCompleted {
if hash == nil {
let _ = _internal_requestRecommendedChannels(account: account, peerId: peerId, forceUpdate: true).startStandalone()
}
}
}
}
@@ -0,0 +1,211 @@
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum JoinLinkInfoError {
case generic
case flood
}
public enum JoinLinkError {
case generic
case tooMuchJoined
case tooMuchUsers
case requestSent
case flood
}
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 {
public struct Invite: Equatable {
public struct Flags: Equatable, Codable {
public let isChannel: Bool
public let isBroadcast: Bool
public let isPublic: Bool
public let isMegagroup: Bool
public let requestNeeded: Bool
public let isVerified: Bool
public let isScam: Bool
public let isFake: Bool
public let canRefulfillSubscription: Bool
}
public let flags: Flags
public let title: String
public let about: String?
public let photoRepresentation: TelegramMediaImageRepresentation?
public let participantsCount: Int32
public let participants: [EnginePeer]?
public let nameColor: PeerNameColor?
public let subscriptionPricing: StarsSubscriptionPricing?
public let subscriptionFormId: Int64?
public let verification: PeerVerification?
}
case invite(Invite)
case alreadyJoined(EnginePeer)
case invalidHash
case peek(EnginePeer, Int32)
}
func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal<PeerId?, JoinLinkError> {
return account.network.request(Api.functions.messages.importChatInvite(hash: hash), automaticFloodWait: false)
|> mapError { error -> JoinLinkError in
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":
return .tooMuchJoined
case "USERS_TOO_MUCH":
return .tooMuchUsers
case "INVITE_REQUEST_SENT":
return .requestSent
default:
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
} else {
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, JoinLinkInfoError> {
let accountPeerId = account.peerId
return account.network.request(Api.functions.messages.checkChatInvite(hash: hash), automaticFloodWait: false)
|> map(Optional.init)
|> `catch` { error -> Signal<Api.ChatInvite?, JoinLinkInfoError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.flood)
} else {
return .single(nil)
}
}
|> mapToSignal { result -> Signal<ExternalJoiningChatState, JoinLinkInfoError> in
if let result = result {
switch result {
case let .chatInvite(flags, title, about, invitePhoto, participantsCount, participants, nameColor, subscriptionPricing, subscriptionFormId, verification):
let photo = telegramMediaImageFromApiPhoto(invitePhoto).flatMap({ smallestImageRepresentation($0.representations) })
let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0, isVerified: (flags & (1 << 7)) != 0, isScam: (flags & (1 << 8)) != 0, isFake: (flags & (1 << 9)) != 0, canRefulfillSubscription: (flags & (1 << 11)) != 0)
return .single(.invite(ExternalJoiningChatState.Invite(flags: flags, title: title, about: about, photoRepresentation: photo, participantsCount: participantsCount, participants: participants?.map({ EnginePeer(TelegramUser(user: $0)) }), nameColor: PeerNameColor(rawValue: nameColor), subscriptionPricing: subscriptionPricing.flatMap { StarsSubscriptionPricing(apiStarsSubscriptionPricing: $0) }, subscriptionFormId: subscriptionFormId, verification: verification.flatMap { PeerVerification(apiBotVerification: $0) })))
case let .chatInviteAlready(chat):
if let peer = parseTelegramGroupOrChannel(chat: chat) {
return account.postbox.transaction({ (transaction) -> ExternalJoiningChatState in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [chat], users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
return .alreadyJoined(EnginePeer(peer))
})
|> castError(JoinLinkInfoError.self)
}
return .single(.invalidHash)
case let .chatInvitePeek(chat, expires):
if let peer = parseTelegramGroupOrChannel(chat: chat) {
return account.postbox.transaction({ (transaction) -> ExternalJoiningChatState in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [chat], users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
return .peek(EnginePeer(peer), expires)
})
|> castError(JoinLinkInfoError.self)
}
return .single(.invalidHash)
}
} else {
return .single(.invalidHash)
}
}
}
public final class JoinCallLinkInformation {
public let reference: InternalGroupCallReference
public let id: Int64
public let accessHash: Int64
public let inviter: EnginePeer?
public let members: [EnginePeer]
public let totalMemberCount: Int
public init(reference: InternalGroupCallReference, id: Int64, accessHash: Int64, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
self.reference = reference
self.id = id
self.accessHash = accessHash
self.inviter = inviter
self.members = members
self.totalMemberCount = totalMemberCount
}
}
func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Signal<JoinCallLinkInformation, JoinLinkInfoError> {
return _internal_getCurrentGroupCall(account: account, reference: .link(slug: hash))
|> mapError { error -> JoinLinkInfoError in
switch error {
case .generic:
return .generic
}
}
|> mapToSignal { call -> Signal<JoinCallLinkInformation, JoinLinkInfoError> in
guard let call = call else {
return .fail(.generic)
}
var members: [EnginePeer] = []
for participant in call.topParticipants {
if let peer = participant.peer {
members.append(peer)
}
}
return .single(JoinCallLinkInformation(reference: .link(slug: hash), id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
}
}
public enum JoinCallLinkInfoError {
case generic
case flood
case doesNotExist
}
func _internal_joinCallInvitationInformation(account: Account, messageId: MessageId) -> Signal<JoinCallLinkInformation, JoinCallLinkInfoError> {
return _internal_getCurrentGroupCall(account: account, reference: .message(id: messageId))
|> mapError { error -> JoinCallLinkInfoError in
switch error {
case .generic:
return .generic
}
}
|> mapToSignal { call -> Signal<JoinCallLinkInformation, JoinCallLinkInfoError> in
guard let call else {
return .fail(.doesNotExist)
}
var members: [EnginePeer] = []
for participant in call.topParticipants {
if let peer = participant.peer, peer.id != account.peerId {
members.append(peer)
}
}
return .single(JoinCallLinkInformation(reference: .message(id: messageId), id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
}
}
@@ -0,0 +1,112 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public enum AvailableChannelDiscussionGroupError {
case generic
}
func _internal_availableGroupsForChannelDiscussion(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<[Peer], AvailableChannelDiscussionGroupError> {
return network.request(Api.functions.channels.getGroupsForDiscussion())
|> mapError { error in
return .generic
}
|> mapToSignal { result -> Signal<[Peer], AvailableChannelDiscussionGroupError> in
let chats: [Api.Chat]
switch result {
case let .chats(c):
chats = c
case let .chatsSlice(_, c):
chats = c
}
return postbox.transaction { transaction -> [Peer] in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
let peers = chats.compactMap(parseTelegramGroupOrChannel)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
return peers
}
|> castError(AvailableChannelDiscussionGroupError.self)
}
}
public enum ChannelDiscussionGroupError {
case generic
case groupHistoryIsCurrentlyPrivate
case hasNotPermissions
case tooManyChannels
}
func _internal_updateGroupDiscussionForChannel(network: Network, postbox: Postbox, channelId: PeerId?, groupId: PeerId?) -> Signal<Bool, ChannelDiscussionGroupError> {
return postbox.transaction { transaction -> (channel: Peer?, group: Peer?) in
return (channel: channelId.flatMap(transaction.getPeer), group: groupId.flatMap(transaction.getPeer))
}
|> mapError { _ -> ChannelDiscussionGroupError in }
|> mapToSignal { channel, group -> Signal<Bool, ChannelDiscussionGroupError> in
let apiChannel = channel.flatMap(apiInputChannel) ?? Api.InputChannel.inputChannelEmpty
let apiGroup = group.flatMap(apiInputChannel) ?? Api.InputChannel.inputChannelEmpty
return network.request(Api.functions.channels.setDiscussionGroup(broadcast: apiChannel, group: apiGroup))
|> map { result in
switch result {
case .boolTrue:
return true
case .boolFalse:
return false
}
}
|> `catch` { error -> Signal<Bool, ChannelDiscussionGroupError> in
if error.errorDescription == "LINK_NOT_MODIFIED" {
return .single(true)
} else if error.errorDescription == "MEGAGROUP_PREHISTORY_HIDDEN" {
return .fail(.groupHistoryIsCurrentlyPrivate)
} else if error.errorDescription == "CHAT_ADMIN_REQUIRED" {
return .fail(.hasNotPermissions)
}
return .fail(.generic)
}
}
|> mapToSignal { result in
if result {
return postbox.transaction { transaction in
if let channelId = channelId {
var previousGroupId: CachedChannelData.LinkedDiscussionPeerId?
transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in
let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData()
previousGroupId = current.linkedDiscussionPeerId
return current.withUpdatedLinkedDiscussionPeerId(.known(groupId))
})
if case let .known(maybeValue)? = previousGroupId, let value = maybeValue, value != groupId {
transaction.updatePeerCachedData(peerIds: Set([value]), update: { (_, current) -> CachedPeerData? in
let cachedData = (current as? CachedChannelData ?? CachedChannelData())
return cachedData.withUpdatedLinkedDiscussionPeerId(.known(nil))
})
}
}
if let groupId = groupId {
var previousChannelId: CachedChannelData.LinkedDiscussionPeerId?
transaction.updatePeerCachedData(peerIds: Set([groupId]), update: { (_, current) -> CachedPeerData? in
let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData()
previousChannelId = current.linkedDiscussionPeerId
return current.withUpdatedLinkedDiscussionPeerId(.known(channelId))
})
if case let .known(maybeValue)? = previousChannelId, let value = maybeValue, value != channelId {
transaction.updatePeerCachedData(peerIds: Set([value]), update: { (_, current) -> CachedPeerData? in
let cachedData = (current as? CachedChannelData ?? CachedChannelData())
return cachedData.withUpdatedLinkedDiscussionPeerId(.known(nil))
})
}
}
}
|> castError(ChannelDiscussionGroupError.self)
|> map { _ in
return result
}
} else {
return .single(result)
}
}
}
@@ -0,0 +1,79 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
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(accountPeerId: PeerId, postbox: Postbox, network: Network, isStories: Bool) -> Signal<NotificationExceptionsList, NoError> {
var flags: Int32 = 0
if isStories {
flags |= 1 << 2
} else {
flags |= 1 << 1
}
return network.request(Api.functions.account.getNotifyExceptions(flags: flags, peer: nil))
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<NotificationExceptionsList, NoError> in
guard let result else {
return .single(NotificationExceptionsList(peers: [:], settings: [:]))
}
return postbox.transaction { transaction -> NotificationExceptionsList in
switch result {
case let .updates(updates, users, chats, _, _):
var settings: [PeerId: TelegramPeerNotificationSettings] = [:]
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId,peers: parsedPeers)
var peers: [PeerId: Peer] = [:]
for id in parsedPeers.allIds {
if let peer = transaction.getPeer(id) {
peers[peer.id] = peer
}
}
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,392 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
import MtProtoKit
public struct NotificationSoundSettings: Equatable {
public private(set) var maxDuration: Int = 5
public private(set) var maxSize: Int = 102400
public private(set) var maxSavedCount: Int = 100
private init(appConfiguration: AppConfiguration) {
if let data = appConfiguration.data {
let duration = data["ringtone_duration_max"] as? Double ?? 5
let size = data["ringtone_size_max"] as? Double ?? 102400
let count = data["ringtone_saved_count_max"] as? Double ?? 100
self.maxDuration = Int(duration)
self.maxSize = Int(size)
self.maxSavedCount = Int(count)
}
}
public static func extract(from appConfiguration: AppConfiguration) -> NotificationSoundSettings {
return self.init(appConfiguration: appConfiguration)
}
}
public final class NotificationSoundList: Equatable, Codable {
public final class NotificationSound: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case file
}
public let file: TelegramMediaFile
public init(
file: TelegramMediaFile
) {
self.file = file
}
public static func ==(lhs: NotificationSound, rhs: NotificationSound) -> Bool {
if lhs.file != rhs.file {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let fileData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .file)
self.file = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: fileData.data)))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.file), forKey: .file)
}
}
private enum CodingKeys: String, CodingKey {
case hash
case sounds
}
public let hash: Int64
public let sounds: [NotificationSound]
public init(
hash: Int64,
sounds: [NotificationSound]
) {
self.hash = hash
self.sounds = sounds
}
public static func ==(lhs: NotificationSoundList, rhs: NotificationSoundList) -> Bool {
if lhs.hash != rhs.hash {
return false
}
if lhs.sounds != rhs.sounds {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.hash = try container.decode(Int64.self, forKey: .hash)
self.sounds = try container.decode([NotificationSound].self, forKey: .sounds)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.hash, forKey: .hash)
try container.encode(self.sounds, forKey: .sounds)
}
}
private extension NotificationSoundList.NotificationSound {
convenience init?(apiDocument: Api.Document) {
guard let file = telegramMediaFileFromApiDocument(apiDocument, altDocuments: []) else {
return nil
}
self.init(file: file)
}
}
func _internal_cachedNotificationSoundList(postbox: Postbox) -> Signal<NotificationSoundList?, NoError> {
return postbox.transaction { transaction -> NotificationSoundList? in
return _internal_cachedNotificationSoundList(transaction: transaction)
}
}
func _internal_cachedNotificationSoundListCacheKey() -> ItemCacheEntryId {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.notificationSoundList, key: key)
}
public func _internal_cachedNotificationSoundList(transaction: Transaction) -> NotificationSoundList? {
let cached = transaction.retrieveItemCacheEntry(id: _internal_cachedNotificationSoundListCacheKey())?.get(NotificationSoundList.self)
if let cached = cached {
return cached
} else {
return nil
}
}
func _internal_setCachedNotificationSoundList(transaction: Transaction, notificationSoundList: NotificationSoundList) {
if let entry = CodableEntry(notificationSoundList) {
transaction.putItemCacheEntry(id: _internal_cachedNotificationSoundListCacheKey(), entry: entry)
}
}
public func ensureDownloadedNotificationSoundList(postbox: Postbox) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Signal<Never, NoError> in
var signals: [Signal<Never, NoError>] = []
if let notificationSoundList = _internal_cachedNotificationSoundList(transaction: transaction) {
var resources: [MediaResource] = []
for sound in notificationSoundList.sounds {
resources.append(sound.file.resource)
}
for resource in resources {
signals.append(
fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .file, reference: .soundList(resource: resource))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
)
}
}
return combineLatest(signals)
|> ignoreValues
}
|> switchToLatest
|> ignoreValues
}
func requestNotificationSoundList(network: Network, hash: Int64) -> Signal<NotificationSoundList?, NoError> {
return network.request(Api.functions.account.getSavedRingtones(hash: hash))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.account.SavedRingtones?, NoError> in
return .single(nil)
}
|> map { result -> NotificationSoundList? in
guard let result = result else {
return nil
}
switch result {
case let .savedRingtones(hash, ringtones):
let notificationSoundList = NotificationSoundList(
hash: hash,
sounds: ringtones.compactMap(NotificationSoundList.NotificationSound.init(apiDocument:))
)
return notificationSoundList
case .savedRingtonesNotModified:
return nil
}
}
}
private func pollNotificationSoundList(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
return Signal<Never, NoError> { subscriber in
let signal: Signal<Never, NoError> = _internal_cachedNotificationSoundList(postbox: postbox)
|> mapToSignal { current in
return (network.request(Api.functions.account.getSavedRingtones(hash: current?.hash ?? 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.account.SavedRingtones?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
switch result {
case let .savedRingtones(hash, ringtones):
let notificationSoundList = NotificationSoundList(
hash: hash,
sounds: ringtones.compactMap(NotificationSoundList.NotificationSound.init(apiDocument:))
)
_internal_setCachedNotificationSoundList(transaction: transaction, notificationSoundList: notificationSoundList)
case .savedRingtonesNotModified:
break
}
var signals: [Signal<Never, NoError>] = []
if let notificationSoundList = _internal_cachedNotificationSoundList(transaction: transaction) {
var resources: [MediaResource] = []
for sound in notificationSoundList.sounds {
resources.append(sound.file.resource)
}
for resource in resources {
signals.append(
fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .file, reference: .soundList(resource: resource))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
)
}
}
return combineLatest(signals)
|> ignoreValues
}
|> switchToLatest
})
}
return signal.start(completed: {
subscriber.putCompletion()
})
}
}
func managedSynchronizeNotificationSoundList(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
let poll = pollNotificationSoundList(postbox: postbox, network: network)
return (
poll
|> then(
.complete()
|> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())
)
)
|> restart
}
func _internal_saveNotificationSound(account: Account, file: FileMediaReference, unsave: Bool = false) -> Signal<Never, UploadNotificationSoundError> {
guard let resource = file.media.resource as? CloudDocumentMediaResource else {
return .fail(.generic)
}
let accountPeerId = account.peerId
return account.network.request(Api.functions.account.saveRingtone(id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), unsave: unsave ? .boolTrue : .boolFalse))
|> `catch` { error -> Signal<Api.account.SavedRingtone, MTRpcError> in
if error.errorDescription == "FILE_REFERENCE_EXPIRED" {
return revalidateMediaResourceReference(accountPeerId: accountPeerId, postbox: account.postbox, network: account.network, revalidationContext: account.mediaReferenceRevalidationContext, info: TelegramCloudMediaResourceFetchInfo(reference: file.abstract.resourceReference(file.media.resource), preferBackgroundReferenceRevalidation: false, continueInBackground: false), resource: file.media.resource)
|> mapError { _ -> MTRpcError in
return MTRpcError(errorCode: 500, errorDescription: "Internal")
}
|> mapToSignal { result -> Signal<Api.account.SavedRingtone, MTRpcError> in
guard let resource = result.updatedResource as? CloudDocumentMediaResource else {
return .fail(MTRpcError(errorCode: 500, errorDescription: "Internal"))
}
return account.network.request(Api.functions.account.saveRingtone(id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), unsave: unsave ? .boolTrue : .boolFalse))
}
} else {
return .fail(error)
}
}
|> mapError { _ -> UploadNotificationSoundError in
return .generic
}
|> mapToSignal { _ -> Signal<Never, UploadNotificationSoundError> in
return pollNotificationSoundList(postbox: account.postbox, network: account.network)
|> castError(UploadNotificationSoundError.self)
}
}
public enum UploadNotificationSoundError {
case generic
}
func _internal_uploadNotificationSound(account: Account, title: String, data: Data) -> Signal<NotificationSoundList.NotificationSound, UploadNotificationSoundError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false, tag: nil, hintFileSize: Int64(data.count), hintFileIsLarge: false, forceNoBigParts: true, useLargerParts: false, increaseParallelParts: false, useMultiplexedRequests: false, useCompression: false)
|> mapError { _ -> UploadNotificationSoundError in
return .generic
}
|> mapToSignal { value -> Signal<NotificationSoundList.NotificationSound, UploadNotificationSoundError> in
switch value {
case let .inputFile(file):
return account.network.request(Api.functions.account.uploadRingtone(file: file, fileName: title, mimeType: "audio/mpeg"))
|> mapError { _ -> UploadNotificationSoundError in
return .generic
}
|> mapToSignal { result -> Signal<NotificationSoundList.NotificationSound, UploadNotificationSoundError> in
guard let file = telegramMediaFileFromApiDocument(result, altDocuments: []) else {
return .fail(.generic)
}
return account.postbox.transaction { transaction -> NotificationSoundList.NotificationSound in
let item = NotificationSoundList.NotificationSound(file: file)
account.postbox.mediaBox.storeResourceData(file.resource.id, data: data, synchronous: true)
let notificationSoundList = _internal_cachedNotificationSoundList(transaction: transaction) ?? NotificationSoundList(hash: 0, sounds: [])
let updatedNotificationSoundList = NotificationSoundList(hash: notificationSoundList.hash, sounds: [item] + notificationSoundList.sounds)
_internal_setCachedNotificationSoundList(transaction: transaction, notificationSoundList: updatedNotificationSoundList)
return item
}
|> castError(UploadNotificationSoundError.self)
}
default:
return .never()
}
}
}
public enum DeleteNotificationSoundError {
case generic
}
func _internal_deleteNotificationSound(account: Account, fileId: Int64) -> Signal<Never, DeleteNotificationSoundError> {
return account.postbox.transaction { transaction -> NotificationSoundList.NotificationSound? in
return _internal_cachedNotificationSoundList(transaction: transaction).flatMap { list -> NotificationSoundList.NotificationSound? in
return list.sounds.first(where: { $0.file.fileId.id == fileId })
}
}
|> castError(DeleteNotificationSoundError.self)
|> mapToSignal { sound -> Signal<Never, DeleteNotificationSoundError> in
guard let sound = sound else {
return .fail(.generic)
}
guard let resource = sound.file.resource as? CloudDocumentMediaResource else {
return .fail(.generic)
}
return account.network.request(Api.functions.account.saveRingtone(id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), unsave: .boolTrue))
|> mapError { _ -> DeleteNotificationSoundError in
return .generic
}
|> mapToSignal { _ -> Signal<Never, DeleteNotificationSoundError> in
return account.postbox.transaction { transaction -> Void in
if let notificationSoundList = _internal_cachedNotificationSoundList(transaction: transaction) {
let updatedNotificationSoundList = NotificationSoundList(hash: notificationSoundList.hash, sounds: notificationSoundList.sounds.filter { item in
return item.file.fileId.id != fileId
})
_internal_setCachedNotificationSoundList(transaction: transaction, notificationSoundList: updatedNotificationSoundList)
}
}
|> castError(DeleteNotificationSoundError.self)
|> ignoreValues
}
}
}
public func resolvedNotificationSound(sound: PeerMessageSound, notificationSoundList: NotificationSoundList?) -> PeerMessageSound {
switch sound {
case let .cloud(fileId):
if let notificationSoundList = notificationSoundList {
for listSound in notificationSoundList.sounds {
if listSound.file.fileId.id == fileId {
return sound
}
}
return defaultCloudPeerNotificationSound
} else {
return .default
}
default:
return sound
}
}
@@ -0,0 +1,5 @@
import Postbox
public protocol EngineOpaqueChatState: AnyObject, Codable {
func isEqual(to other: EngineOpaqueChatState) -> Bool
}
@@ -0,0 +1,722 @@
import Foundation
import Postbox
public enum EnginePeer: Equatable {
public typealias Id = PeerId
public struct Presence: Equatable {
public enum Status: Comparable {
private struct SortKey: Comparable {
var major: Int
var minor: Int32
init(major: Int, minor: Int32) {
self.major = major
self.minor = minor
}
static func <(lhs: SortKey, rhs: SortKey) -> Bool {
if lhs.major != rhs.major {
return lhs.major < rhs.major
}
return lhs.minor < rhs.minor
}
}
case present(until: Int32)
case recently(isHidden: Bool)
case lastWeek(isHidden: Bool)
case lastMonth(isHidden: Bool)
case longTimeAgo
private var sortKey: SortKey {
switch self {
case let .present(until):
return SortKey(major: 6, minor: until)
case .recently:
return SortKey(major: 4, minor: 0)
case .lastWeek:
return SortKey(major: 3, minor: 0)
case .lastMonth:
return SortKey(major: 2, minor: 0)
case .longTimeAgo:
return SortKey(major: 1, minor: 0)
}
}
public static func <(lhs: Status, rhs: Status) -> Bool {
return lhs.sortKey < rhs.sortKey
}
}
public var status: Status
public var lastActivity: Int32
public init(status: Status, lastActivity: Int32) {
self.status = status
self.lastActivity = lastActivity
}
}
public struct NotificationSettings: Equatable {
public enum MuteState: Equatable {
case `default`
case unmuted
case muted(until: Int32)
}
public enum MessageSound: Equatable {
case none
case `default`
case bundledModern(id: Int32)
case bundledClassic(id: Int32)
case cloud(fileId: Int64)
}
public enum DisplayPreviews {
case `default`
case show
case hide
}
public typealias StorySettigs = PeerStoryNotificationSettings
public var muteState: MuteState
public var messageSound: MessageSound
public var displayPreviews: DisplayPreviews
public var storySettings: StorySettigs
public init(
muteState: MuteState,
messageSound: MessageSound,
displayPreviews: DisplayPreviews,
storySettings: StorySettigs
) {
self.muteState = muteState
self.messageSound = messageSound
self.displayPreviews = displayPreviews
self.storySettings = storySettings
}
}
public struct StatusSettings: Equatable {
public struct Flags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let canReport = Flags(rawValue: 1 << 1)
public static let canShareContact = Flags(rawValue: 1 << 2)
public static let canBlock = Flags(rawValue: 1 << 3)
public static let canAddContact = Flags(rawValue: 1 << 4)
public static let addExceptionWhenAddingContact = Flags(rawValue: 1 << 5)
public static let autoArchived = Flags(rawValue: 1 << 7)
public static let suggestAddMembers = Flags(rawValue: 1 << 8)
}
public var flags: Flags
public var geoDistance: Int32?
public var requestChatTitle: String?
public var requestChatDate: Int32?
public var requestChatIsChannel: Bool?
public init(
flags: Flags,
geoDistance: Int32?,
requestChatTitle: String?,
requestChatDate: Int32?,
requestChatIsChannel: Bool?
) {
self.flags = flags
self.geoDistance = geoDistance
self.requestChatTitle = requestChatTitle
self.requestChatDate = requestChatDate
self.requestChatIsChannel = requestChatIsChannel
}
public func contains(_ member: Flags) -> Bool {
return self.flags.contains(member)
}
}
public enum IndexName: Equatable {
case title(title: String, addressNames: [String])
case personName(first: String, last: String, addressNames: [String], phoneNumber: String?)
public var isEmpty: Bool {
switch self {
case let .title(title, addressNames):
if !title.isEmpty {
return false
}
if !addressNames.isEmpty {
return false
}
return true
case let .personName(first, last, addressNames, phoneNumber):
if !first.isEmpty {
return false
}
if !last.isEmpty {
return false
}
if !addressNames.isEmpty {
return false
}
if let phoneNumber = phoneNumber, !phoneNumber.isEmpty {
return false
}
return true
}
}
}
case user(TelegramUser)
case legacyGroup(TelegramGroup)
case channel(TelegramChannel)
case secretChat(TelegramSecretChat)
public static func ==(lhs: EnginePeer, rhs: EnginePeer) -> Bool {
switch lhs {
case let .user(user):
if case .user(user) = rhs {
return true
} else {
return false
}
case let .legacyGroup(legacyGroup):
if case .legacyGroup(legacyGroup) = rhs {
return true
} else {
return false
}
case let .channel(channel):
if case .channel(channel) = rhs {
return true
} else {
return false
}
case let .secretChat(secretChat):
if case .secretChat(secretChat) = rhs {
return true
} else {
return false
}
}
}
}
public struct EngineGlobalNotificationSettings: Equatable {
public struct CategorySettings: Equatable {
public var enabled: Bool
public var displayPreviews: Bool
public var sound: EnginePeer.NotificationSettings.MessageSound
public var storySettings: EnginePeer.NotificationSettings.StorySettigs
public init(enabled: Bool, displayPreviews: Bool, sound: EnginePeer.NotificationSettings.MessageSound, storySettings: EnginePeer.NotificationSettings.StorySettigs) {
self.enabled = enabled
self.displayPreviews = displayPreviews
self.sound = sound
self.storySettings = storySettings
}
}
public struct ReactionSettings: Equatable {
public var messages: PeerReactionNotificationSettings.Sources
public var stories: PeerReactionNotificationSettings.Sources
public var hideSender: PeerReactionNotificationSettings.HideSender
public var sound: EnginePeer.NotificationSettings.MessageSound
public init(messages: PeerReactionNotificationSettings.Sources, stories: PeerReactionNotificationSettings.Sources, hideSender: PeerReactionNotificationSettings.HideSender, sound: EnginePeer.NotificationSettings.MessageSound) {
self.messages = messages
self.stories = stories
self.hideSender = hideSender
self.sound = sound
}
}
public var privateChats: CategorySettings
public var groupChats: CategorySettings
public var channels: CategorySettings
public var reactionSettings: ReactionSettings
public var contactsJoined: Bool
public init(
privateChats: CategorySettings,
groupChats: CategorySettings,
channels: CategorySettings,
reactionSettings: ReactionSettings,
contactsJoined: Bool
) {
self.privateChats = privateChats
self.groupChats = groupChats
self.channels = channels
self.reactionSettings = reactionSettings
self.contactsJoined = contactsJoined
}
}
public extension EnginePeer.NotificationSettings.MuteState {
init(_ muteState: PeerMuteState) {
switch muteState {
case .default:
self = .default
case .unmuted:
self = .unmuted
case let .muted(until):
self = .muted(until: until)
}
}
func _asMuteState() -> PeerMuteState {
switch self {
case .default:
return .default
case .unmuted:
return .unmuted
case let .muted(until):
return .muted(until: until)
}
}
}
public extension EnginePeer.NotificationSettings.MessageSound {
init(_ messageSound: PeerMessageSound) {
switch messageSound {
case .none:
self = .none
case .default:
self = .default
case let .bundledClassic(id):
self = .bundledClassic(id: id)
case let .bundledModern(id):
self = .bundledModern(id: id)
case let .cloud(fileId):
self = .cloud(fileId: fileId)
}
}
func _asMessageSound() -> PeerMessageSound {
switch self {
case .none:
return .none
case .default:
return .default
case let .bundledClassic(id):
return .bundledClassic(id: id)
case let .bundledModern(id):
return .bundledModern(id: id)
case let .cloud(fileId):
return .cloud(fileId: fileId)
}
}
}
public extension EnginePeer.NotificationSettings.DisplayPreviews {
init(_ displayPreviews: PeerNotificationDisplayPreviews) {
switch displayPreviews {
case .default:
self = .default
case .show:
self = .show
case .hide:
self = .hide
}
}
func _asDisplayPreviews() -> PeerNotificationDisplayPreviews {
switch self {
case .default:
return .default
case .show:
return .show
case .hide:
return .hide
}
}
}
public extension EnginePeer.NotificationSettings {
init(_ notificationSettings: TelegramPeerNotificationSettings) {
self.init(
muteState: MuteState(notificationSettings.muteState),
messageSound: MessageSound(notificationSettings.messageSound),
displayPreviews: DisplayPreviews(notificationSettings.displayPreviews),
storySettings: notificationSettings.storySettings
)
}
func _asNotificationSettings() -> TelegramPeerNotificationSettings {
return TelegramPeerNotificationSettings(
muteState: self.muteState._asMuteState(),
messageSound: self.messageSound._asMessageSound(),
displayPreviews: self.displayPreviews._asDisplayPreviews(),
storySettings: self.storySettings
)
}
}
public extension EnginePeer.StatusSettings {
init(_ statusSettings: PeerStatusSettings) {
self.init(
flags: Flags(rawValue: statusSettings.flags.rawValue),
geoDistance: statusSettings.geoDistance,
requestChatTitle: statusSettings.requestChatTitle,
requestChatDate: statusSettings.requestChatDate,
requestChatIsChannel: statusSettings.requestChatIsChannel
)
}
}
public extension EnginePeer.Presence {
init(_ presence: PeerPresence) {
if let presence = presence as? TelegramUserPresence {
let mappedStatus: Status
switch presence.status {
case .none:
mappedStatus = .longTimeAgo
case let .present(until):
mappedStatus = .present(until: until)
case let .recently(isHidden):
mappedStatus = .recently(isHidden: isHidden)
case let .lastWeek(isHidden):
mappedStatus = .lastWeek(isHidden: isHidden)
case let .lastMonth(isHidden):
mappedStatus = .lastMonth(isHidden: isHidden)
}
self.init(status: mappedStatus, lastActivity: presence.lastActivity)
} else {
preconditionFailure()
}
}
func _asPresence() -> TelegramUserPresence {
let mappedStatus: UserPresenceStatus
switch self.status {
case .longTimeAgo:
mappedStatus = .none
case let .present(until):
mappedStatus = .present(until: until)
case let .recently(isHidden):
mappedStatus = .recently(isHidden: isHidden)
case let .lastWeek(isHidden):
mappedStatus = .lastWeek(isHidden: isHidden)
case let .lastMonth(isHidden):
mappedStatus = .lastMonth(isHidden: isHidden)
}
return TelegramUserPresence(status: mappedStatus, lastActivity: self.lastActivity)
}
}
public extension EnginePeer.IndexName {
init(_ indexName: PeerIndexNameRepresentation) {
switch indexName {
case let .title(title, addressNames):
self = .title(title: title, addressNames: addressNames)
case let .personName(first, last, addressNames, phoneNumber):
self = .personName(first: first, last: last, addressNames: addressNames, phoneNumber: phoneNumber)
}
}
func _asIndexName() -> PeerIndexNameRepresentation {
switch self {
case let .title(title, addressNames):
return .title(title: title, addressNames: addressNames)
case let .personName(first, last, addressNames, phoneNumber):
return .personName(first: first, last: last, addressNames: addressNames, phoneNumber: phoneNumber)
}
}
func matchesByTokens(_ other: String) -> Bool {
return self._asIndexName().matchesByTokens(other)
}
func matchesByTokens(_ other: [ValueBoxKey]) -> Bool {
return self._asIndexName().matchesByTokens(other)
}
func stringRepresentation(lastNameFirst: Bool) -> String {
switch self {
case let .title(title, _):
return title
case let .personName(first, last, _, _):
if lastNameFirst {
return last + first
} else {
return first + last
}
}
}
}
public extension EnginePeer {
var id: Id {
return self._asPeer().id
}
var addressName: String? {
return self._asPeer().addressName
}
var usernames: [TelegramPeerUsername] {
return self._asPeer().usernames
}
var indexName: EnginePeer.IndexName {
return EnginePeer.IndexName(self._asPeer().indexName)
}
var debugDisplayTitle: String {
return self._asPeer().debugDisplayTitle
}
func restrictionText(platform: String, contentSettings: ContentSettings) -> String? {
return self._asPeer().restrictionText(platform: platform, contentSettings: contentSettings)
}
var displayLetters: [String] {
return self._asPeer().displayLetters
}
var profileImageRepresentations: [TelegramMediaImageRepresentation] {
return self._asPeer().profileImageRepresentations
}
var smallProfileImage: TelegramMediaImageRepresentation? {
return self._asPeer().smallProfileImage
}
var largeProfileImage: TelegramMediaImageRepresentation? {
return self._asPeer().largeProfileImage
}
var isDeleted: Bool {
return self._asPeer().isDeleted
}
var isScam: Bool {
return self._asPeer().isScam
}
var isFake: Bool {
return self._asPeer().isFake
}
var isVerified: Bool {
return self._asPeer().isVerified
}
var isPremium: Bool {
return self._asPeer().isPremium
}
var isSubscription: Bool {
return self._asPeer().isSubscription
}
var isService: Bool {
if case let .user(peer) = self {
if peer.id.isReplies {
return true
}
if peer.id.isVerificationCodes {
return true
}
return (peer.id.namespace == Namespaces.Peer.CloudUser && (peer.id.id._internalGetInt64Value() == 777000 || peer.id.id._internalGetInt64Value() == 333000))
}
return false
}
var nameColor: PeerColor? {
return self._asPeer().nameColor
}
var verificationIconFileId: Int64? {
return self._asPeer().verificationIconFileId
}
var profileColor: PeerNameColor? {
return self._asPeer().profileColor
}
var effectiveProfileColor: PeerNameColor? {
return self._asPeer().effectiveProfileColor
}
var emojiStatus: PeerEmojiStatus? {
return self._asPeer().emojiStatus
}
var backgroundEmojiId: Int64? {
return self._asPeer().backgroundEmojiId
}
var profileBackgroundEmojiId: Int64? {
return self._asPeer().profileBackgroundEmojiId
}
}
public extension EnginePeer {
init(_ peer: Peer) {
switch peer {
case let user as TelegramUser:
self = .user(user)
case let group as TelegramGroup:
self = .legacyGroup(group)
case let channel as TelegramChannel:
self = .channel(channel)
case let secretChat as TelegramSecretChat:
self = .secretChat(secretChat)
default:
preconditionFailure("Unknown peer type")
}
}
func _asPeer() -> Peer {
switch self {
case let .user(user):
return user
case let .legacyGroup(legacyGroup):
return legacyGroup
case let .channel(channel):
return channel
case let .secretChat(secretChat):
return secretChat
}
}
}
public final class EngineRenderedPeer: Equatable {
public let peerId: EnginePeer.Id
public let peers: [EnginePeer.Id: EnginePeer]
public let associatedMedia: [EngineMedia.Id: Media]
public init(peerId: EnginePeer.Id, peers: [EnginePeer.Id: EnginePeer], associatedMedia: [EngineMedia.Id: Media]) {
self.peerId = peerId
self.peers = peers
self.associatedMedia = associatedMedia
}
public init(peer: EnginePeer) {
self.peerId = peer.id
self.peers = [peer.id: peer]
self.associatedMedia = [:]
}
public static func ==(lhs: EngineRenderedPeer, rhs: EngineRenderedPeer) -> Bool {
if lhs.peerId != rhs.peerId {
return false
}
if lhs.peers != rhs.peers {
return false
}
if !areMediaDictionariesEqual(lhs.associatedMedia, rhs.associatedMedia) {
return false
}
return true
}
public var peer: EnginePeer? {
return self.peers[self.peerId]
}
public var chatMainPeer: EnginePeer? {
if let peer = self.peers[self.peerId] {
if case let .secretChat(secretChat) = peer {
return self.peers[secretChat.regularPeerId]
} else {
return peer
}
} else {
return nil
}
}
public var chatOrMonoforumMainPeer: EnginePeer? {
if case let .channel(channel) = self.peer, channel.flags.contains(.isMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
return self.peers[linkedMonoforumId]
} else {
return self.chatMainPeer
}
}
}
public extension EngineRenderedPeer {
convenience init(_ renderedPeer: RenderedPeer) {
var mappedPeers: [EnginePeer.Id: EnginePeer] = [:]
for (id, peer) in renderedPeer.peers {
mappedPeers[id] = EnginePeer(peer)
}
self.init(peerId: renderedPeer.peerId, peers: mappedPeers, associatedMedia: renderedPeer.associatedMedia)
}
convenience init(message: EngineMessage) {
self.init(RenderedPeer(message: message._asMessage()))
}
}
public extension EngineGlobalNotificationSettings.CategorySettings {
init(_ categorySettings: MessageNotificationSettings) {
self.init(
enabled: categorySettings.enabled,
displayPreviews: categorySettings.displayPreviews,
sound: EnginePeer.NotificationSettings.MessageSound(categorySettings.sound),
storySettings: categorySettings.storySettings
)
}
func _asMessageNotificationSettings() -> MessageNotificationSettings {
return MessageNotificationSettings(
enabled: self.enabled,
displayPreviews: self.displayPreviews,
sound: self.sound._asMessageSound(),
storySettings: self.storySettings
)
}
}
public extension EngineGlobalNotificationSettings.ReactionSettings {
init(_ reactionSettings: PeerReactionNotificationSettings) {
self.init(
messages: reactionSettings.messages,
stories: reactionSettings.stories,
hideSender: reactionSettings.hideSender,
sound: EnginePeer.NotificationSettings.MessageSound(reactionSettings.sound)
)
}
func _asReactionSettings() -> PeerReactionNotificationSettings {
return PeerReactionNotificationSettings(
messages: self.messages,
stories: self.stories,
hideSender: self.hideSender,
sound: self.sound._asMessageSound()
)
}
}
public extension EngineGlobalNotificationSettings {
init(_ globalNotificationSettings: GlobalNotificationSettingsSet) {
self.init(
privateChats: CategorySettings(globalNotificationSettings.privateChats),
groupChats: CategorySettings(globalNotificationSettings.groupChats),
channels: CategorySettings(globalNotificationSettings.channels),
reactionSettings: ReactionSettings(globalNotificationSettings.reactionSettings),
contactsJoined: globalNotificationSettings.contactsJoined
)
}
func _asGlobalNotificationSettings() -> GlobalNotificationSettingsSet {
return GlobalNotificationSettingsSet(
privateChats: self.privateChats._asMessageNotificationSettings(),
groupChats: self.groupChats._asMessageNotificationSettings(),
channels: self.channels._asMessageNotificationSettings(),
reactionSettings: self.reactionSettings._asReactionSettings(),
contactsJoined: self.contactsJoined
)
}
}
@@ -0,0 +1,269 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum RemoveGroupAdminError {
case generic
}
func _internal_removeGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) -> Signal<Void, RemoveGroupAdminError> {
return account.postbox.transaction { transaction -> Signal<Void, RemoveGroupAdminError> in
if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(adminId), let inputUser = apiInputUser(adminPeer) {
if let group = peer as? TelegramGroup {
return account.network.request(Api.functions.messages.editChatAdmin(chatId: group.id.id._internalGetInt64Value(), userId: inputUser, isAdmin: .boolFalse))
|> mapError { _ -> RemoveGroupAdminError in return .generic }
|> mapToSignal { result -> Signal<Void, RemoveGroupAdminError> in
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedGroupData, let participants = current.participants {
var updatedParticipants = participants.participants
if case .boolTrue = result {
for i in 0 ..< updatedParticipants.count {
if updatedParticipants[i].peerId == adminId {
switch updatedParticipants[i] {
case let .admin(id, invitedBy, invitedAt):
updatedParticipants[i] = .member(id: id, invitedBy: invitedBy, invitedAt: invitedAt)
default:
break
}
break
}
}
}
return current.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version))
} else {
return current
}
})
} |> mapError { _ -> RemoveGroupAdminError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> RemoveGroupAdminError in }
|> switchToLatest
}
public enum AddGroupAdminError {
case generic
case addMemberError(AddGroupMemberError)
case adminsTooMuch
}
func _internal_addGroupAdmin(account: Account, peerId: PeerId, adminId: PeerId) -> Signal<Void, AddGroupAdminError> {
return account.postbox.transaction { transaction -> Signal<Void, AddGroupAdminError> in
if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(adminId), let inputUser = apiInputUser(adminPeer) {
if let group = peer as? TelegramGroup {
return account.network.request(Api.functions.messages.editChatAdmin(chatId: group.id.id._internalGetInt64Value(), userId: inputUser, isAdmin: .boolTrue))
|> `catch` { error -> Signal<Api.Bool, AddGroupAdminError> in
if error.errorDescription == "USER_NOT_PARTICIPANT" {
return _internal_addGroupMember(account: account, peerId: peerId, memberId: adminId)
|> mapError { error -> AddGroupAdminError in
return .addMemberError(error)
}
|> mapToSignal { _ -> Signal<Api.Bool, AddGroupAdminError> in
return .complete()
}
|> then(
account.network.request(Api.functions.messages.editChatAdmin(chatId: group.id.id._internalGetInt64Value(), userId: inputUser, isAdmin: .boolTrue))
|> mapError { error -> AddGroupAdminError in
return .generic
}
)
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.privacy(nil)))
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .fail(.adminsTooMuch)
}
return .fail(.generic)
}
|> mapToSignal { result -> Signal<Void, AddGroupAdminError> in
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedGroupData, let participants = current.participants {
var updatedParticipants = participants.participants
if case .boolTrue = result {
for i in 0 ..< updatedParticipants.count {
if updatedParticipants[i].peerId == adminId {
switch updatedParticipants[i] {
case let .member(id, invitedBy, invitedAt):
updatedParticipants[i] = .admin(id: id, invitedBy: invitedBy, invitedAt: invitedAt)
default:
break
}
break
}
}
}
return current.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version))
} else {
return current
}
})
} |> mapError { _ -> AddGroupAdminError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> AddGroupAdminError in }
|> switchToLatest
}
public enum UpdateChannelAdminRightsError {
case generic
case addMemberError(AddChannelMemberError)
case adminsTooMuch
}
func _internal_fetchChannelParticipant(account: Account, peerId: PeerId, participantId: PeerId) -> Signal<ChannelParticipant?, NoError> {
return account.postbox.transaction { transaction -> Signal<ChannelParticipant?, NoError> in
if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(participantId), let inputPeer = apiInputPeer(adminPeer) {
if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
return account.network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: inputPeer))
|> map { result -> ChannelParticipant? in
switch result {
case let .channelParticipant(participant, _, _):
return ChannelParticipant(apiParticipant: participant)
}
}
|> `catch` { _ -> Signal<ChannelParticipant?, NoError> in
return .single(nil)
}
} else {
return .single(nil)
}
} else {
return .single(nil)
}
} |> switchToLatest
}
func _internal_updateChannelAdminRights(account: Account, peerId: PeerId, adminId: PeerId, rights: TelegramChatAdminRights?, rank: String?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> {
return _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: adminId)
|> mapError { error -> UpdateChannelAdminRightsError in
}
|> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> in
return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> in
if let peer = transaction.getPeer(peerId), let adminPeer = transaction.getPeer(adminId), let inputUser = apiInputUser(adminPeer) {
if let channel = peer as? TelegramChannel, let inputChannel = apiInputChannel(channel) {
let updatedParticipant: ChannelParticipant
if let currentParticipant = currentParticipant, case let .member(_, invitedAt, currentAdminInfo, _, _, subscriptionUntilDate) = currentParticipant {
let adminInfo: ChannelParticipantAdminInfo?
if let rights = rights {
adminInfo = ChannelParticipantAdminInfo(rights: rights, promotedBy: currentAdminInfo?.promotedBy ?? account.peerId, canBeEditedByAccountPeer: true)
} else {
adminInfo = nil
}
updatedParticipant = .member(id: adminId, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: nil, rank: rank, subscriptionUntilDate: subscriptionUntilDate)
} else if let currentParticipant = currentParticipant, case .creator = currentParticipant {
let adminInfo: ChannelParticipantAdminInfo?
if let rights = rights {
adminInfo = ChannelParticipantAdminInfo(rights: rights, promotedBy: account.peerId, canBeEditedByAccountPeer: true)
} else {
adminInfo = nil
}
updatedParticipant = .creator(id: adminId, adminInfo: adminInfo, rank: rank)
} else {
let adminInfo: ChannelParticipantAdminInfo?
if let rights = rights {
adminInfo = ChannelParticipantAdminInfo(rights: rights, promotedBy: account.peerId, canBeEditedByAccountPeer: true)
} else {
adminInfo = nil
}
updatedParticipant = .member(id: adminId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: adminInfo, banInfo: nil, rank: rank, subscriptionUntilDate: nil)
}
return account.network.request(Api.functions.channels.editAdmin(channel: inputChannel, userId: inputUser, adminRights: rights?.apiAdminRights ?? .chatAdminRights(flags: 0), rank: rank ?? ""))
|> map { [$0] }
|> `catch` { error -> Signal<[Api.Updates], UpdateChannelAdminRightsError> in
if error.errorDescription == "USER_NOT_PARTICIPANT" {
return _internal_addChannelMember(account: account, peerId: peerId, memberId: adminId)
|> map { _ -> [Api.Updates] in
return []
}
|> mapError { error -> UpdateChannelAdminRightsError in
return .addMemberError(error)
}
|> then(
account.network.request(Api.functions.channels.editAdmin(channel: inputChannel, userId: inputUser, adminRights: rights?.apiAdminRights ?? .chatAdminRights(flags: 0), rank: rank ?? ""))
|> mapError { error -> UpdateChannelAdminRightsError in
return .generic
}
|> map { [$0] }
)
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .fail(.addMemberError(.notMutualContact))
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.restricted(nil)))
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
return .fail(.addMemberError(.tooMuchJoined))
} else if error.errorDescription == "ADMINS_TOO_MUCH" {
return .fail(.adminsTooMuch)
}
return .fail(.generic)
}
|> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), UpdateChannelAdminRightsError> in
for updates in result {
account.stateManager.addUpdates(updates)
}
return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let adminCount = cachedData.participantsSummary.adminCount {
var updatedAdminCount = adminCount
var wasAdmin = false
if let currentParticipant = currentParticipant {
switch currentParticipant {
case .creator:
wasAdmin = true
case let .member(_, _, adminInfo, _, _, _):
if let _ = adminInfo {
wasAdmin = true
}
}
}
if wasAdmin && rights == nil {
updatedAdminCount = max(1, adminCount - 1)
} else if !wasAdmin && rights != nil {
updatedAdminCount = adminCount + 1
}
return cachedData.withUpdatedParticipantsSummary(cachedData.participantsSummary.withUpdatedAdminCount(updatedAdminCount))
} else {
return cachedData
}
})
var peers: [PeerId: Peer] = [:]
var presences: [PeerId: PeerPresence] = [:]
peers[adminPeer.id] = adminPeer
if let presence = transaction.getPeerPresence(peerId: adminPeer.id) {
presences[adminPeer.id] = presence
}
if case let .member(_, _, maybeAdminInfo, _, _, _) = updatedParticipant, let adminInfo = maybeAdminInfo {
if let peer = transaction.getPeer(adminInfo.promotedBy) {
peers[peer.id] = peer
}
}
return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: adminPeer, peers: peers, presences: presences))
} |> mapError { _ -> UpdateChannelAdminRightsError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateChannelAdminRightsError in }
|> switchToLatest
}
}
@@ -0,0 +1,66 @@
import Foundation
import Postbox
import SwiftSignalKit
public struct PeerCommand: Hashable {
public let peer: Peer
public let command: BotCommand
public static func ==(lhs: PeerCommand, rhs: PeerCommand) -> Bool {
return lhs.peer.isEqual(rhs.peer) && lhs.command == rhs.command
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.peer.id)
hasher.combine(self.command)
}
}
public struct PeerCommands: Equatable {
public let commands: [PeerCommand]
public static func ==(lhs: PeerCommands, rhs: PeerCommands) -> Bool {
return lhs.commands == rhs.commands
}
}
func _internal_peerCommands(account: Account, id: PeerId) -> Signal<PeerCommands, NoError> {
return account.postbox.peerView(id: id) |> map { view -> PeerCommands in
if let cachedUserData = view.cachedData as? CachedUserData {
if let botInfo = cachedUserData.botInfo {
if let botPeer = view.peers[id] {
var commands: [PeerCommand] = []
for command in botInfo.commands {
commands.append(PeerCommand(peer: botPeer, command: command))
}
return PeerCommands(commands: commands)
}
}
return PeerCommands(commands: [])
} else if let cachedGroupData = view.cachedData as? CachedGroupData {
var commands: [PeerCommand] = []
for cachedBotInfo in cachedGroupData.botInfos {
if let botPeer = view.peers[cachedBotInfo.peerId] {
for command in cachedBotInfo.botInfo.commands {
commands.append(PeerCommand(peer: botPeer, command: command))
}
}
}
return PeerCommands(commands: commands)
} else if let cachedChannelData = view.cachedData as? CachedChannelData {
var commands: [PeerCommand] = []
for cachedBotInfo in cachedChannelData.botInfos {
if let botPeer = view.peers[cachedBotInfo.peerId] {
for command in cachedBotInfo.botInfo.commands {
commands.append(PeerCommand(peer: botPeer, command: command))
}
}
}
return PeerCommands(commands: commands)
} else {
return PeerCommands(commands: [])
}
}
|> distinctUntilChanged
}
@@ -0,0 +1,632 @@
import Foundation
import Postbox
import SwiftSignalKit
import MtProtoKit
import TelegramApi
public enum UpdatePeerPhotoStatus {
case progress(Float)
case complete([TelegramMediaImageRepresentation])
}
public enum UploadPeerPhotoError {
case generic
}
public enum UploadPeerPhotoMarkup {
case emoji(fileId: Int64, backgroundColors: [Int32])
case sticker(packReference: StickerPackReference, fileId: Int64, backgroundColors: [Int32])
}
func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, fallback: Bool, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
let photo: Signal<UploadedPeerPhotoData, NoError>?
if videoResource == nil && markup != nil, let resource = resource {
photo = .single(UploadedPeerPhotoData.withResource(resource))
} else {
photo = resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) })
}
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: photo, video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: fallback, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public enum SetCustomPeerPhotoMode {
case custom
case suggest
case customAndSuggest
}
func _internal_updateContactPhoto(account: Account, peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
let photo: Signal<UploadedPeerPhotoData, NoError>?
if videoResource == nil && markup != nil, let resource = resource {
photo = .single(UploadedPeerPhotoData.withResource(resource))
} else {
photo = resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) })
}
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: photo, video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, customPeerPhotoMode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public struct UploadedPeerPhotoData {
fileprivate let resource: MediaResource
fileprivate let content: UploadedPeerPhotoDataContent
fileprivate let local: Bool
public var isCompleted: Bool {
if case let .result(result) = content, case .inputFile = result {
return true
} else {
return false
}
}
static func withResource(_ resource: MediaResource) -> UploadedPeerPhotoData {
return UploadedPeerPhotoData(resource: resource, content: .result(.inputFile(.inputFile(id: 0, parts: 0, name: "", md5Checksum: ""))), local: true)
}
}
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, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> map { result -> UploadedPeerPhotoData in
return UploadedPeerPhotoData(resource: resource, content: .result(result), local: false)
}
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
return .single(UploadedPeerPhotoData(resource: resource, content: .error, local: false))
}
}
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, userContentType: .video), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> map { result -> UploadedPeerPhotoData in
return UploadedPeerPhotoData(resource: resource, content: .result(result), local: false)
}
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
return .single(UploadedPeerPhotoData(resource: resource, content: .error, local: false))
}
} else {
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> map { result -> UploadedPeerPhotoData in
return UploadedPeerPhotoData(resource: resource, content: .result(result), local: false)
}
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
return .single(UploadedPeerPhotoData(resource: resource, content: .error, local: false))
}
}
}
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, markup: UploadPeerPhotoMarkup? = nil, fallback: Bool = false, customPeerPhotoMode: SetCustomPeerPhotoMode? = 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, markup: markup, fallback: fallback, customPeerPhotoMode: customPeerPhotoMode, 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?, markup: UploadPeerPhotoMarkup? = nil, fallback: Bool = false, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return peer
|> mapError { _ -> UploadPeerPhotoError in }
|> mapToSignal { peer -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
var videoEmojiMarkup: Api.VideoSize?
if let markup = markup {
switch markup {
case let .emoji(fileId, backgroundColors):
videoEmojiMarkup = .videoSizeEmojiMarkup(emojiId: fileId, backgroundColors: backgroundColors)
case let .sticker(packReference, fileId, backgroundColors):
videoEmojiMarkup = .videoSizeStickerMarkup(stickerset: packReference.apiInputStickerSet, stickerId: fileId, backgroundColors: backgroundColors)
}
}
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 }
|> 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)
}
}
}
var photoFile: Api.InputFile?
if !photoResult.local {
photoFile = file
}
if peer is TelegramUser {
var flags: Int32 = 0
if let _ = photoFile {
flags = (1 << 0)
}
if let _ = videoFile {
flags |= (1 << 1)
if let _ = videoStartTimestamp {
flags |= (1 << 2)
}
}
let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId {
if fallback {
flags |= (1 << 3)
}
if let _ = videoEmojiMarkup {
flags |= (1 << 4)
}
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, bot: nil, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else if let user = peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit), let inputUser = apiInputUser(peer) {
if fallback {
flags |= (1 << 3)
}
if let _ = videoEmojiMarkup {
flags |= (1 << 4)
}
flags |= (1 << 5)
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, bot: inputUser, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else if let inputUser = apiInputUser(peer) {
if let customPeerPhotoMode = customPeerPhotoMode {
switch customPeerPhotoMode {
case .custom:
flags |= (1 << 4)
case .suggest:
flags |= (1 << 3)
case .customAndSuggest:
flags |= (1 << 3)
flags |= (1 << 4)
}
}
if let _ = videoEmojiMarkup {
flags |= (1 << 5)
}
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else {
request = .complete()
}
return request
|> mapError { _ in return UploadPeerPhotoError.generic }
|> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in
var representations: [TelegramMediaImageRepresentation] = []
var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = []
var image: TelegramMediaImage?
switch photo {
case let .photo(apiPhoto, _):
image = telegramMediaImageFromApiPhoto(apiPhoto)
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, hasVideo: false, isPersonal: false))
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, hasVideo: false, isPersonal: false))
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: Int64(size), fileReference: fileReference.makeData())
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, startTimestamp: videoStartTs))
case .videoSizeEmojiMarkup, .videoSizeStickerMarkup:
break
}
}
}
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) {
updatePeersCustom(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in
if let peer = peer as? TelegramUser {
if customPeerPhotoMode == .suggest || fallback {
return peer
} else {
return peer.withUpdatedPhoto(representations)
}
} else {
return peer
}
})
if fallback {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedFallbackPhoto(.known(image))
} else {
return nil
}
}
} else if let customPeerPhotoMode = customPeerPhotoMode, case .custom = customPeerPhotoMode {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedPersonalPhoto(.known(image))
} else {
return nil
}
}
} else if peer.id == accountPeerId && customPeerPhotoMode == nil {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedPhoto(.known(image))
} else {
return nil
}
}
}
}
return (.complete(representations), photoResult.resource, videoResult?.resource)
} |> mapError { _ -> UploadPeerPhotoError in }
}
} else {
var flags: Int32 = (1 << 0)
if let _ = videoFile {
flags |= (1 << 1)
if let _ = videoStartTimestamp {
flags |= (1 << 2)
}
}
if let _ = videoEmojiMarkup {
flags |= (1 << 3)
}
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, videoEmojiMarkup: videoEmojiMarkup)))
} 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, videoEmojiMarkup: videoEmojiMarkup)))
} 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
updatePeersCustom(transaction: transaction, peers: [groupOrChannel], update: { _, updated in
return updated
})
return (.complete(groupOrChannel.profileImageRepresentations), photoResult.resource, videoResult?.resource)
}
|> mapError { _ -> UploadPeerPhotoError in }
}
}
}
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 { _ -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
return .complete()
}
|> then(
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 user = peer as? TelegramUser {
let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId {
var flags: Int32 = 0
if fallback {
flags |= (1 << 0)
}
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, bot: nil, id: Api.InputPhoto.inputPhotoEmpty))
} else if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit), let inputUser = apiInputUser(peer) {
var flags: Int32 = (1 << 1)
if fallback {
flags |= (1 << 0)
}
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, bot: inputUser, id: Api.InputPhoto.inputPhotoEmpty))
} else if let inputUser = apiInputUser(peer) {
let flags: Int32 = (1 << 4)
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: nil, video: nil, videoStartTs: nil, videoEmojiMarkup: nil))
} else {
request = .complete()
}
return request
|> mapError { _ -> UploadPeerPhotoError in
return .generic
}
|> mapToSignal { photo -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if peer.id == accountPeerId {
var updatedImage: TelegramMediaImage?
var representations: [TelegramMediaImageRepresentation] = []
switch photo {
case let .photo(apiPhoto, _):
updatedImage = telegramMediaImageFromApiPhoto(apiPhoto)
switch apiPhoto {
case .photoEmpty:
break
case let .photo(_, id, _, _, _, sizes, _, 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, hasVideo: false, isPersonal: false))
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, hasVideo: false, isPersonal: false))
default:
break
}
}
}
}
return postbox.transaction { transaction -> UpdatePeerPhotoStatus in
if let peer = transaction.getPeer(peer.id) {
updatePeersCustom(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in
if let peer = peer as? TelegramUser {
if customPeerPhotoMode == .suggest || fallback {
return peer
} else {
return peer.withUpdatedPhoto(representations)
}
} else {
return peer
}
})
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedPersonalPhoto(.known(updatedImage))
} else {
return nil
}
}
}
return .complete([])
} |> mapError { _ -> UploadPeerPhotoError in }
} else {
var updatedUsers: [TelegramUser] = []
switch photo {
case let .photo(_, apiUsers):
updatedUsers = apiUsers.map { TelegramUser(user: $0) }
}
return postbox.transaction { transaction -> UpdatePeerPhotoStatus in
updatePeersCustom(transaction: transaction, peers: updatedUsers, update: { (_, updatedPeer) -> Peer? in
return updatedPeer
})
if fallback {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedFallbackPhoto(.known(nil))
} else {
return nil
}
}
} else if let customPeerPhotoMode = customPeerPhotoMode, case .custom = customPeerPhotoMode {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedPersonalPhoto(.known(nil))
} else {
return nil
}
}
}
return .complete([])
} |> mapError { _ -> UploadPeerPhotoError in }
}
}
} 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
updatePeersCustom(transaction: transaction, peers: [groupOrChannel], update: { _, updated in
return updated
})
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
if let current = current as? CachedChannelData {
return current.withUpdatedPhoto(nil)
} else if let current = current as? CachedGroupData {
return current.withUpdatedPhoto(nil)
} else {
return current
}
})
return .complete(groupOrChannel.profileImageRepresentations)
}
|> mapError { _ -> UploadPeerPhotoError in }
}
}
}
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(flags: 0, bot: nil, 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(account: Account, reference: TelegramMediaImageReference?, fallback: Bool) -> Signal<Void, NoError> {
if let reference = reference {
switch reference {
case let .cloud(imageId, accessHash, fileReference):
if let fileReference = fileReference {
return account.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 {
var flags: Int32 = 0
if fallback {
flags |= (1 << 0)
}
let api = Api.functions.photos.updateProfilePhoto(flags: flags, bot: nil, id: Api.InputPhoto.inputPhotoEmpty)
return account.network.request(api)
|> map { _ in }
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
if fallback {
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedFallbackPhoto(.known(nil))
} else {
return current
}
})
return Void()
}
} else {
return .complete()
}
}
}
}
@@ -0,0 +1,80 @@
import Foundation
import Postbox
import SwiftSignalKit
private struct WrappedStickerPackCollectionInfo: Equatable {
let info: StickerPackCollectionInfo?
static func ==(lhs: WrappedStickerPackCollectionInfo, rhs: WrappedStickerPackCollectionInfo) -> Bool {
return lhs.info == rhs.info
}
}
public struct PeerSpecificStickerPackData {
public let packInfo: (StickerPackCollectionInfo.Accessor, [ItemCollectionItem])?
public let canSetup: Bool
}
func _internal_peerSpecificStickerPack(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<PeerSpecificStickerPackData, NoError> {
if peerId.namespace == Namespaces.Peer.CloudChannel {
let signal: Signal<(WrappedStickerPackCollectionInfo, Bool), NoError> = postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)])
|> map { view -> (WrappedStickerPackCollectionInfo, Bool) in
let dataView = view.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView
return (WrappedStickerPackCollectionInfo(info: (dataView?.cachedPeerData as? CachedChannelData)?.stickerPack), (dataView?.cachedPeerData as? CachedChannelData)?.flags.contains(.canSetStickerSet) ?? false)
}
|> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in
return lhs.0 == rhs.0 && lhs.1 == rhs.1
})
return signal
|> mapToSignal { info, canInstall -> Signal<PeerSpecificStickerPackData, NoError> in
if let info = info.info {
return _internal_cachedStickerPack(postbox: postbox, network: network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceRemote: false)
|> map { result -> PeerSpecificStickerPackData in
if case let .result(info, items, _) = result {
return PeerSpecificStickerPackData(packInfo: (info, items), canSetup: canInstall)
} else {
return PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall)
}
}
} else {
return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall))
}
}
} else {
return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: false))
}
}
func _internal_peerSpecificEmojiPack(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<PeerSpecificStickerPackData, NoError> {
if peerId.namespace == Namespaces.Peer.CloudChannel {
let signal: Signal<(WrappedStickerPackCollectionInfo, Bool), NoError> = postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)])
|> map { view -> (WrappedStickerPackCollectionInfo, Bool) in
let dataView = view.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView
return (WrappedStickerPackCollectionInfo(info: (dataView?.cachedPeerData as? CachedChannelData)?.emojiPack), (dataView?.cachedPeerData as? CachedChannelData)?.flags.contains(.canSetStickerSet) ?? false)
}
|> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in
return lhs.0 == rhs.0 && lhs.1 == rhs.1
})
return signal
|> mapToSignal { info, canInstall -> Signal<PeerSpecificStickerPackData, NoError> in
if let info = info.info {
return _internal_cachedStickerPack(postbox: postbox, network: network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceRemote: false)
|> map { result -> PeerSpecificStickerPackData in
if case let .result(info, items, _) = result {
return PeerSpecificStickerPackData(packInfo: (info, items), canSetup: canInstall)
} else {
return PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall)
}
}
} else {
return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall))
}
}
} else {
return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: false))
}
}
@@ -0,0 +1,39 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_setMainProfileTab(account: Account, peerId: PeerId, tab: TelegramProfileTab) -> Signal<Never, NoError> {
return account.postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer in
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedMainProfileTab(tab)
} else if let current = current as? CachedChannelData {
return current.withUpdatedMainProfileTab(tab)
} else {
return current
}
})
if let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.setMainProfileTab(channel: inputChannel, tab: tab.apiTab))
|> `catch` { error in
return .complete()
}
|> mapToSignal { _ -> Signal<Never, NoError> in
return .complete()
}
} else {
return account.network.request(Api.functions.account.setMainProfileTab(tab: tab.apiTab))
|> `catch` { error in
return .complete()
}
|> mapToSignal { _ -> Signal<Never, NoError> in
return .complete()
}
}
} |> switchToLatest
}
}
@@ -0,0 +1,333 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
public enum RecentPeers {
case peers([Peer])
case disabled
}
func cachedRecentPeersEntryId() -> ItemCacheEntryId {
return ItemCacheEntryId(collectionId: 101, key: CachedRecentPeers.cacheKey())
}
func cachedRecentAppsEntryId() -> ItemCacheEntryId {
return ItemCacheEntryId(collectionId: 102, key: CachedRecentPeers.cacheKey())
}
public func _internal_recentPeers(accountPeerId: EnginePeer.Id, postbox: Postbox) -> Signal<RecentPeers, NoError> {
let key = PostboxViewKey.cachedItem(cachedRecentPeersEntryId())
return postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<RecentPeers, NoError> in
if let value = (views.views[key] as? CachedItemView)?.value?.get(CachedRecentPeers.self) {
if value.enabled {
return postbox.multiplePeersView(value.ids)
|> map { view -> RecentPeers in
var peers: [Peer] = []
for id in value.ids {
if let peer = view.peers[id], id != accountPeerId {
peers.append(peer)
}
}
return .peers(peers)
}
} else {
return .single(.disabled)
}
} else {
return .single(.peers([]))
}
}
}
public func _internal_getRecentPeers(transaction: Transaction) -> [PeerId] {
guard let entry = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId())?.get(CachedRecentPeers.self) else {
return []
}
return entry.ids
}
public func _internal_managedUpdatedRecentPeers(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let key = PostboxViewKey.cachedItem(cachedRecentPeersEntryId())
let peersEnabled = postbox.combinedView(keys: [key])
|> map { views -> Bool in
if let value = (views.views[key] as? CachedItemView)?.value?.get(CachedRecentPeers.self) {
return value.enabled
} else {
return true
}
}
|> distinctUntilChanged
let updateOnce =
network.request(Api.functions.contacts.getTopPeers(flags: 1 << 0, offset: 0, limit: 50, hash: 0))
|> `catch` { _ -> Signal<Api.contacts.TopPeers, NoError> in
return .complete()
}
|> mapToSignal { result -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Void in
switch result {
case let .topPeers(_, _, users):
let parsedPeers = AccumulatedPeers(users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
if let entry = CodableEntry(CachedRecentPeers(enabled: true, ids: users.map { $0.peerId })) {
transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: entry)
}
case .topPeersNotModified:
break
case .topPeersDisabled:
if let entry = CodableEntry(CachedRecentPeers(enabled: false, ids: [])) {
transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: entry)
}
}
}
}
return peersEnabled |> mapToSignal { _ -> Signal<Void, NoError> in
return updateOnce
}
}
func _internal_removeRecentPeer(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
guard let entry = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId())?.get(CachedRecentPeers.self) else {
return .complete()
}
if let index = entry.ids.firstIndex(of: peerId) {
var updatedIds = entry.ids
updatedIds.remove(at: index)
if let entry = CodableEntry(CachedRecentPeers(enabled: entry.enabled, ids: updatedIds)) {
transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: entry)
}
}
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.contacts.resetTopPeerRating(category: .topPeerCategoryCorrespondents, peer: apiPeer))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
func _internal_updateRecentPeersEnabled(postbox: Postbox, network: Network, enabled: Bool) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in
var currentValue = true
if let entry = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId())?.get(CachedRecentPeers.self) {
currentValue = entry.enabled
}
if currentValue == enabled {
return .complete()
}
return network.request(Api.functions.contacts.toggleTopPeers(enabled: enabled ? .boolTrue : .boolFalse))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Void in
if !enabled {
if let entry = CodableEntry(CachedRecentPeers(enabled: false, ids: [])) {
transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: entry)
}
} else {
let entry = transaction.retrieveItemCacheEntry(id: cachedRecentPeersEntryId())?.get(CachedRecentPeers.self)
if let codableEntry = CodableEntry(CachedRecentPeers(enabled: true, ids: entry?.ids ?? [])) {
transaction.putItemCacheEntry(id: cachedRecentPeersEntryId(), entry: codableEntry)
}
}
}
}
} |> switchToLatest
}
func _internal_managedRecentlyUsedInlineBots(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
let remotePeers = network.request(Api.functions.contacts.getTopPeers(flags: 1 << 2, offset: 0, limit: 16, hash: 0))
|> retryRequestIfNotFrozen
|> map { result -> (AccumulatedPeers, [(PeerId, Double)])? in
switch result {
case .topPeersDisabled:
break
case let .topPeers(categories, _, users):
let parsedPeers = AccumulatedPeers(users: users)
var peersWithRating: [(PeerId, Double)] = []
for category in categories {
switch category {
case let .topPeerCategoryPeers(_, _, topPeers):
for topPeer in topPeers {
switch topPeer {
case let .topPeer(apiPeer, rating):
peersWithRating.append((apiPeer.peerId, rating))
}
}
}
}
return (parsedPeers, peersWithRating)
case .topPeersNotModified:
break
default:
break
}
return (AccumulatedPeers(), [])
}
let updatedRemotePeers = remotePeers
|> mapToSignal { peersAndPresences -> Signal<Void, NoError> in
if let (parsedPeers, peersWithRating) = peersAndPresences {
return postbox.transaction { transaction -> Void in
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let sortedPeersWithRating = peersWithRating.sorted(by: { $0.1 > $1.1 })
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots, items: sortedPeersWithRating.compactMap { (peerId, rating) in
if let entry = CodableEntry(RecentPeerItem(rating: rating)) {
return OrderedItemListEntry(id: RecentPeerItemId(peerId).rawValue, contents: entry)
} else {
return nil
}
})
}
} else {
return .complete()
}
}
return updatedRemotePeers
}
func _internal_addRecentlyUsedInlineBot(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
var maxRating = 1.0
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots) {
if let contents = entry.contents.get(RecentPeerItem.self) {
maxRating = max(maxRating, contents.rating)
}
}
if let entry = CodableEntry(RecentPeerItem(rating: maxRating)) {
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots, item: OrderedItemListEntry(id: RecentPeerItemId(peerId).rawValue, contents: entry), removeTailIfCountExceeds: 20)
}
}
}
func _internal_recentlyUsedInlineBots(postbox: Postbox) -> Signal<[(Peer, Double)], NoError> {
return postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentInlineBots)])
|> take(1)
|> mapToSignal { view -> Signal<[(Peer, Double)], NoError> in
return postbox.transaction { transaction -> [(Peer, Double)] in
var peers: [(Peer, Double)] = []
if let view = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentInlineBots)] as? OrderedItemListView {
for item in view.items {
let peerId = RecentPeerItemId(item.id).peerId
if let peer = transaction.getPeer(peerId), let contents = item.contents.get(RecentPeerItem.self) {
peers.append((peer, contents.rating))
}
}
}
return peers
}
}
}
func _internal_removeRecentlyUsedInlineBot(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots, itemId: RecentPeerItemId(peerId).rawValue)
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.contacts.resetTopPeerRating(category: .topPeerCategoryBotsInline, peer: apiPeer))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
public func _internal_recentApps(accountPeerId: PeerId, postbox: Postbox) -> Signal<[EnginePeer.Id], NoError> {
let key = PostboxViewKey.cachedItem(cachedRecentAppsEntryId())
return postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<[EnginePeer.Id], NoError> in
if let value = (views.views[key] as? CachedItemView)?.value?.get(CachedRecentPeers.self) {
return .single(value.ids)
} else {
return .single([])
}
}
}
public func _internal_managedUpdatedRecentApps(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let key = PostboxViewKey.cachedItem(cachedRecentAppsEntryId())
let peersEnabled = postbox.combinedView(keys: [key])
|> map { views -> Bool in
if let value = (views.views[key] as? CachedItemView)?.value?.get(CachedRecentPeers.self) {
return value.enabled
} else {
return true
}
}
|> distinctUntilChanged
let updateOnce =
network.request(Api.functions.contacts.getTopPeers(flags: 1 << 16, offset: 0, limit: 50, hash: 0))
|> `catch` { _ -> Signal<Api.contacts.TopPeers, NoError> in
return .complete()
}
|> mapToSignal { result -> Signal<Void, NoError> in
return postbox.transaction { transaction -> Void in
switch result {
case let .topPeers(_, _, users):
let parsedPeers = AccumulatedPeers(users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
if let entry = CodableEntry(CachedRecentPeers(enabled: true, ids: users.map { $0.peerId })) {
transaction.putItemCacheEntry(id: cachedRecentAppsEntryId(), entry: entry)
}
case .topPeersNotModified:
break
case .topPeersDisabled:
if let entry = CodableEntry(CachedRecentPeers(enabled: false, ids: [])) {
transaction.putItemCacheEntry(id: cachedRecentAppsEntryId(), entry: entry)
}
}
}
}
return peersEnabled |> mapToSignal { _ -> Signal<Void, NoError> in
return updateOnce
}
}
func _internal_removeRecentlyUsedApp(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let entry = transaction.retrieveItemCacheEntry(id: cachedRecentAppsEntryId()), let recentPeers = entry.get(CachedRecentPeers.self) {
let updatedRecentPeers = CachedRecentPeers(enabled: recentPeers.enabled, ids: recentPeers.ids.filter({ $0 != peerId }))
if let updatedEntry = CodableEntry(updatedRecentPeers) {
transaction.putItemCacheEntry(id: cachedRecentAppsEntryId(), entry: updatedEntry)
}
}
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.contacts.resetTopPeerRating(category: .topPeerCategoryBotsApp, peer: apiPeer))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
@@ -0,0 +1,137 @@
import Foundation
import Postbox
import SwiftSignalKit
func _internal_addRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
if let entry = CodableEntry(RecentPeerItem(rating: 0.0)) {
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, item: OrderedItemListEntry(id: RecentPeerItemId(peerId).rawValue, contents: entry), removeTailIfCountExceeds: 20)
}
}
}
func _internal_removeRecentlySearchedPeer(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(peerId).rawValue)
}
}
func _internal_clearRecentlySearchedPeers(postbox: Postbox) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, items: [])
}
}
public struct RecentlySearchedPeerSubpeerSummary: Equatable {
public let count: Int
public init(count: Int) {
self.count = count
}
}
public struct RecentlySearchedPeer: Equatable {
public let peer: RenderedPeer
public let presence: TelegramUserPresence?
public let notificationSettings: TelegramPeerNotificationSettings?
public let unreadCount: Int32
public let subpeerSummary: RecentlySearchedPeerSubpeerSummary?
public init(peer: RenderedPeer, presence: TelegramUserPresence?, notificationSettings: TelegramPeerNotificationSettings?, unreadCount: Int32, subpeerSummary: RecentlySearchedPeerSubpeerSummary?) {
self.peer = peer
self.presence = presence
self.notificationSettings = notificationSettings
self.unreadCount = unreadCount
self.subpeerSummary = subpeerSummary
}
}
public func _internal_recentlySearchedPeers(postbox: Postbox) -> Signal<[RecentlySearchedPeer], NoError> {
return postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.RecentlySearchedPeerIds)])
|> mapToSignal { view -> Signal<[RecentlySearchedPeer], NoError> in
var peerIds: [PeerId] = []
if let view = view.views[.orderedItemList(id: Namespaces.OrderedItemList.RecentlySearchedPeerIds)] as? OrderedItemListView {
for item in view.items {
let peerId = RecentPeerItemId(item.id).peerId
peerIds.append(peerId)
}
}
var keys: [PostboxViewKey] = []
let unreadCountsKey: PostboxViewKey = .unreadCounts(items: peerIds.map { UnreadMessageCountsItem.peer(id: $0, handleThreads: true) })
keys.append(unreadCountsKey)
keys.append(contentsOf: peerIds.map({ .peer(peerId: $0, components: .all) }))
return postbox.combinedView(keys: keys)
|> mapToSignal { view -> Signal<[RecentlySearchedPeer], NoError> in
var result: [RecentlySearchedPeer] = []
var unreadCounts: [PeerId: Int32] = [:]
if let unreadCountsView = view.views[unreadCountsKey] as? UnreadMessageCountsView {
for entry in unreadCountsView.entries {
if case let .peer(peerId, state) = entry {
unreadCounts[peerId] = state?.count ?? 0
}
}
}
var migratedPeerIds: [EnginePeer.Id: EnginePeer.Id] = [:]
for peerId in peerIds {
if let peerView = view.views[.peer(peerId: peerId, components: .all)] as? PeerView {
var presence: TelegramUserPresence?
var unreadCount = unreadCounts[peerId] ?? 0
var subpeerSummary: RecentlySearchedPeerSubpeerSummary?
if let cachedData = peerView.cachedData as? CachedChannelData {
let count: Int32 = cachedData.participantsSummary.memberCount ?? 0
subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count))
}
if let peer = peerView.peers[peerId] {
if peer is TelegramSecretChat, let associatedPeerId = peer.associatedPeerId {
presence = peerView.peerPresences[associatedPeerId] as? TelegramUserPresence
} else {
presence = peerView.peerPresences[peerId] as? TelegramUserPresence
}
if let channel = peer as? TelegramChannel {
if case .member = channel.participationStatus {
} else {
unreadCount = 0
}
if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId {
subpeerSummary = nil
if let cachedData = peerView.associatedCachedData[linkedMonoforumId] as? CachedChannelData {
let count: Int32 = cachedData.participantsSummary.memberCount ?? 0
subpeerSummary = RecentlySearchedPeerSubpeerSummary(count: Int(count))
}
}
}
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
migratedPeerIds = [group.id: migrationReference.peerId]
}
}
result.append(RecentlySearchedPeer(peer: RenderedPeer(peerId: peerId, peers: SimpleDictionary(peerView.peers), associatedMedia: peerView.media), presence: presence, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, unreadCount: unreadCount, subpeerSummary: subpeerSummary))
}
}
if !migratedPeerIds.isEmpty {
return postbox.transaction { transaction -> Signal<[RecentlySearchedPeer], NoError> in
for (previousPeerId, updatedPeerId) in migratedPeerIds {
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(previousPeerId).rawValue)
if let entry = CodableEntry(RecentPeerItem(rating: 0.0)) {
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, item: OrderedItemListEntry(id: RecentPeerItemId(updatedPeerId).rawValue, contents: entry), removeTailIfCountExceeds: 20)
}
}
return .complete()
}
|> switchToLatest
} else {
return .single(result)
}
}
}
}
@@ -0,0 +1,81 @@
import Foundation
import Postbox
import SwiftSignalKit
func _internal_removePeerChat(account: Account, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool = false) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
_internal_removePeerChat(account: account, transaction: transaction, mediaBox: account.postbox.mediaBox, peerId: peerId, reportChatSpam: reportChatSpam, deleteGloballyIfPossible: deleteGloballyIfPossible)
}
}
func _internal_terminateSecretChat(transaction: Transaction, peerId: PeerId, requestRemoteHistoryRemoval: Bool) {
if let state = transaction.getPeerChatState(peerId) as? SecretChatState, state.embeddedState != .terminated {
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: false, requestRemoteHistoryRemoval: requestRemoteHistoryRemoval), state: state).withUpdatedEmbeddedState(.terminated)
if updatedState != state {
transaction.setPeerChatState(peerId, state: updatedState)
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
return updated
})
}
}
}
}
func _internal_removePeerChat(account: Account, transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool) {
if let _ = transaction.getPeerChatInterfaceState(peerId) {
transaction.setPeerChatInterfaceState(peerId, state: nil)
}
_internal_updateChatListFiltersInteractively(transaction: transaction, { filters in
var updatedFilters: [ChatListFilter] = []
for i in 0 ..< filters.count {
let filter = filters[i]
if case let .filter(id, title, emoticon, data) = filter {
var updatedData = data
if updatedData.includePeers.peers.contains(peerId) {
updatedData.includePeers.setPeers(data.includePeers.peers.filter { $0 != peerId })
}
if updatedData.excludePeers.contains(peerId) {
updatedData.excludePeers = data.excludePeers.filter { $0 != peerId }
}
updatedFilters.append(.filter(id: id, title: title, emoticon: emoticon, data: updatedData))
} else {
updatedFilters.append(filter)
}
}
return updatedFilters
})
if peerId.namespace == Namespaces.Peer.SecretChat {
if let state = transaction.getPeerChatState(peerId) as? SecretChatState, state.embeddedState != .terminated {
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: reportChatSpam, requestRemoteHistoryRemoval: deleteGloballyIfPossible), state: state).withUpdatedEmbeddedState(.terminated)
if updatedState != state {
transaction.setPeerChatState(peerId, state: updatedState)
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
return updated
})
}
}
}
_internal_clearHistory(transaction: transaction, mediaBox: mediaBox, peerId: peerId, threadId: nil, namespaces: .all)
transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded)
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(peerId).rawValue)
} else {
cloudChatAddRemoveChatOperation(transaction: transaction, peerId: peerId, reportChatSpam: reportChatSpam, deleteGloballyIfPossible: deleteGloballyIfPossible)
if peerId.namespace == Namespaces.Peer.CloudUser {
transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded)
_internal_clearHistory(transaction: transaction, mediaBox: mediaBox, peerId: peerId, threadId: nil, namespaces: .all)
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded)
_internal_clearHistory(transaction: transaction, mediaBox: mediaBox, peerId: peerId, threadId: nil, namespaces: .all)
} else {
transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded)
}
}
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(peerId).rawValue)
if peerId.namespace == Namespaces.Peer.CloudChannel {
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers)
}
}
@@ -0,0 +1,61 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_removePeerMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal<Void, NoError> {
if peerId.namespace == Namespaces.Peer.CloudChannel {
return _internal_updateChannelMemberBannedRights(account: account, peerId: peerId, memberId: memberId, rights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: 0))
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) {
if let group = peer as? TelegramGroup {
return account.network.request(Api.functions.messages.deleteChatUser(flags: 0, chatId: group.id.id._internalGetInt64Value(), userId: inputUser))
|> mapError { error -> Void in
return Void()
}
|> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete()
}
|> mapToSignal { result -> Signal<Void, NoError> in
account.stateManager.addUpdates(result)
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants {
var updatedData = cachedData
var updatedParticipants = participants.participants
for i in 0 ..< participants.participants.count {
if participants.participants[i].peerId == memberId {
updatedParticipants.remove(at: i)
break
}
}
updatedData = updatedData.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version))
if let memberPeer = memberPeer as? TelegramUser, let _ = memberPeer.botInfo {
let filteredBotInfos = updatedData.botInfos.filter { $0.peerId != memberPeer.id }
updatedData = updatedData.withUpdatedBotInfos(filteredBotInfos)
}
return updatedData
} else {
return cachedData
}
})
}
}
} else {
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
@@ -0,0 +1,276 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_reportPeer(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId) {
if let peer = peer as? TelegramSecretChat {
return account.network.request(Api.functions.messages.reportEncryptedSpam(peer: Api.InputEncryptedChat.inputEncryptedChat(chatId: Int32(peer.id.id._internalGetInt64Value()), accessHash: peer.accessHash)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
return account.postbox.transaction { transaction -> Void in
if result != nil {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedGroupData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedChannelData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else {
return current
}
})
}
}
}
} else if let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.reportSpam(peer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
return account.postbox.transaction { transaction -> Void in
if result != nil {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedGroupData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedChannelData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else {
return current
}
})
}
}
}
} else {
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
public enum ReportReason: Equatable {
case spam
case fake
case violence
case porno
case childAbuse
case copyright
case irrelevantLocation
case illegalDrugs
case personalDetails
case custom
}
private extension ReportReason {
var apiReason: Api.ReportReason {
switch self {
case .spam:
return .inputReportReasonSpam
case .fake:
return .inputReportReasonFake
case .violence:
return .inputReportReasonViolence
case .porno:
return .inputReportReasonPornography
case .childAbuse:
return .inputReportReasonChildAbuse
case .copyright:
return .inputReportReasonCopyright
case .irrelevantLocation:
return .inputReportReasonGeoIrrelevant
case .illegalDrugs:
return .inputReportReasonIllegalDrugs
case .personalDetails:
return .inputReportReasonPersonalDetails
case .custom:
return .inputReportReasonOther
}
}
}
func _internal_reportPeer(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.account.reportPeer(peer: inputPeer, reason: reason.apiReason, message: message))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
func _internal_reportPeerPhoto(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.account.reportProfilePhoto(peer: inputPeer, photoId: .inputPhotoEmpty, reason: reason.apiReason, message: message))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
func _internal_reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason, message: String) -> Signal<Void, NoError> {
return .complete()
// return account.postbox.transaction { transaction -> Signal<Void, NoError> in
// let groupedIds = messagesIdsGroupedByPeerId(messageIds)
// let signals = groupedIds.values.compactMap { ids -> Signal<Void, NoError>? in
// guard let peerId = ids.first?.peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
// return nil
// }
// return account.network.request(Api.functions.messages.report(peer: inputPeer, id: ids.map { $0.id }, reason: reason.apiReason, message: message))
// |> `catch` { _ -> Signal<Api.Bool, NoError> in
// return .single(.boolFalse)
// }
// |> mapToSignal { _ -> Signal<Void, NoError> in
// return .complete()
// }
// }
//
// return combineLatest(signals)
// |> mapToSignal { _ -> Signal<Void, NoError> in
// return .complete()
// }
// } |> switchToLatest
}
func _internal_reportPeerStory(account: Account, peerId: PeerId, storyId: Int32, reason: ReportReason, message: String) -> Signal<Void, NoError> {
return .complete()
// return account.postbox.transaction { transaction -> Signal<Void, NoError> in
// if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
// return account.network.request(Api.functions.stories.report(peer: inputPeer, id: [storyId], reason: reason.apiReason, message: message))
// |> `catch` { _ -> Signal<Api.Bool, NoError> in
// return .single(.boolFalse)
// }
// |> mapToSignal { _ -> Signal<Void, NoError> in
// return .complete()
// }
// } else {
// return .complete()
// }
// } |> switchToLatest
}
func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId: MessageId) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputPeer)? in
guard let peer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
return nil
}
guard let author = transaction.getPeer(authorId).flatMap(apiInputPeer) else {
return nil
}
return (peer, author)
}
|> mapToSignal { inputData -> Signal<Never, NoError> in
guard let (inputPeer, authorPeer) = inputData else {
return .complete()
}
return account.network.request(Api.functions.messages.reportReaction(peer: inputPeer, id: messageId.id, reactionPeer: authorPeer))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}
}
func _internal_dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedGroupData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedChannelData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else if let current = current as? CachedSecretChatData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
} else {
return current
}
})
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.hidePeerSettingsBar(peer: inputPeer))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
func _internal_reportRepliesMessage(account: Account, messageId: MessageId, deleteMessage: Bool, deleteHistory: Bool, reportSpam: Bool) -> Signal<Never, NoError> {
if messageId.namespace != Namespaces.Message.Cloud {
return .complete()
}
var flags: Int32 = 0
if deleteMessage {
flags |= 1 << 0
}
if deleteHistory {
flags |= 1 << 1
}
if reportSpam {
flags |= 1 << 2
}
return account.network.request(Api.functions.contacts.blockFromReplies(flags: flags, msgId: messageId.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Never, NoError> in
if let updates = updates {
account.stateManager.addUpdates(updates)
}
return .complete()
}
}
@@ -0,0 +1,150 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public struct TelegramPeerPhoto {
public let image: TelegramMediaImage
public let reference: TelegramMediaImageReference?
public let date: Int32
public let index: Int
public let totalCount: Int
public let messageId: MessageId?
public init(image: TelegramMediaImage, reference: TelegramMediaImageReference?, date: Int32, index: Int, totalCount: Int, messageId: MessageId?) {
self.image = image
self.reference = reference
self.date = date
self.index = index
self.totalCount = totalCount
self.messageId = messageId
}
}
func _internal_requestPeerPhotos(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> {
return postbox.transaction{ transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> mapToSignal { peer -> Signal<[TelegramPeerPhoto], NoError> in
if let peer = peer as? TelegramUser, let inputUser = apiInputUser(peer) {
return network.request(Api.functions.photos.getUserPhotos(userId: inputUser, offset: 0, maxId: 0, limit: 100))
|> map {Optional($0)}
|> mapError {_ in}
|> `catch` { _ -> Signal<Api.photos.Photos?, NoError> in
return .single(nil)
}
|> map { result -> [TelegramPeerPhoto] in
if let result = result {
let totalCount:Int
let photos: [Api.Photo]
switch result {
case let .photos(photosValue, _):
photos = photosValue
totalCount = photos.count
case let .photosSlice(count, photosValue, _):
photos = photosValue
totalCount = Int(count)
}
var images: [TelegramPeerPhoto] = []
for i in 0 ..< photos.count {
if let image = telegramMediaImageFromApiPhoto(photos[i]), let reference = image.reference {
var date: Int32 = 0
switch photos[i] {
case let .photo(_, _, _, _, apiDate, _, _, _):
date = apiDate
case .photoEmpty:
break
}
images.append(TelegramPeerPhoto(image: image, reference: reference, date: date, index: i, totalCount: totalCount, messageId: nil))
}
}
return images
} else {
return []
}
}
} else if let peer = peer, let inputPeer = apiInputPeer(peer) {
return network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, savedPeerId: nil, savedReaction: nil, topMsgId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[TelegramPeerPhoto], NoError> in
if let result = result {
let messages: [Api.Message]
let chats: [Api.Chat]
let users: [Api.User]
switch result {
case let .channelMessages(_, _, _, _, apiMessages, _, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .messages(apiMessages, _, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .messagesSlice(_, _, _, _, _, apiMessages, _, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case .messagesNotModified:
messages = []
chats = []
users = []
}
return postbox.transaction { transaction -> [Message] in
var peers: [PeerId: Peer] = [:]
for user in users {
if let user = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) {
peers[user.id] = user
}
}
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers[groupOrChannel.id] = groupOrChannel
}
}
var renderedMessages: [Message] = []
for message in messages {
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForumOrMonoForum), let renderedMessage = locallyRenderedMessage(message: message, peers: peers) {
renderedMessages.append(renderedMessage)
}
}
return renderedMessages
} |> map { messages -> [TelegramPeerPhoto] in
var photos: [TelegramPeerPhoto] = []
var index:Int = 0
for message in messages {
if let media = message.media.first as? TelegramMediaAction {
switch media.action {
case let .photoUpdated(image):
if let image = image {
photos.append(TelegramPeerPhoto(image: image, reference: image.reference, date: message.timestamp, index: index, totalCount: messages.count, messageId: message.id))
}
default:
break
}
}
index += 1
}
return photos
}
} else {
return .single([])
}
}
} else {
return .single([])
}
}
}
@@ -0,0 +1,131 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
public enum ResolvePeerByNameOptionCached {
case none
case cached
case cachedIfLaterThan(timestamp: Int32)
}
public enum ResolvePeerByNameOptionRemote {
case updateIfEarlierThan(timestamp: Int32)
case update
}
public enum ResolvePeerIdByNameResult {
case progress
case result(PeerId?)
}
public enum ResolvePeerResult {
case progress
case result(EnginePeer?)
}
func _internal_resolvePeerByName(account: Account, name: String, referrer: String?, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<ResolvePeerIdByNameResult, NoError> {
return _internal_resolvePeerByName(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, name: name, referrer: referrer, ageLimit: ageLimit)
}
func _internal_resolvePeerByName(postbox: Postbox, network: Network, accountPeerId: PeerId, name: String, referrer: String?, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<ResolvePeerIdByNameResult, NoError> {
var normalizedName = name
if normalizedName.hasPrefix("@") {
normalizedName = String(normalizedName[name.index(after: name.startIndex)...])
}
return postbox.transaction { transaction -> CachedResolvedByNamePeer? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByNamePeers, key: CachedResolvedByNamePeer.key(name: normalizedName)))?.get(CachedResolvedByNamePeer.self)
}
|> mapToSignal { cachedEntry -> Signal<ResolvePeerIdByNameResult, NoError> in
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if referrer == nil, let cachedEntry = cachedEntry, cachedEntry.timestamp <= timestamp && cachedEntry.timestamp >= timestamp - ageLimit {
return .single(.result(cachedEntry.peerId))
} else {
var flags: Int32 = 0
if referrer != nil {
flags |= 1 << 0
}
return .single(.progress)
|> then(network.request(Api.functions.contacts.resolveUsername(flags: flags, username: normalizedName, referer: referrer))
|> mapError { _ -> Void in
return Void()
}
|> mapToSignal { result -> Signal<ResolvePeerIdByNameResult, Void> in
return postbox.transaction { transaction -> ResolvePeerIdByNameResult in
var peerId: PeerId? = nil
switch result {
case let .resolvedPeer(apiPeer, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
if let peer = parsedPeers.get(apiPeer.peerId) {
peerId = peer.id
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
}
}
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if let entry = CodableEntry(CachedResolvedByNamePeer(peerId: peerId, timestamp: timestamp)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByNamePeers, key: CachedResolvedByNamePeer.key(name: normalizedName)), entry: entry)
}
return .result(peerId)
}
|> castError(Void.self)
}
|> `catch` { _ -> Signal<ResolvePeerIdByNameResult, NoError> in
return .single(.result(nil))
})
}
}
}
func _internal_resolvePeerByPhone(account: Account, phone: String, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<PeerId?, NoError> {
var normalizedPhone = phone
if normalizedPhone.hasPrefix("+") {
normalizedPhone = String(normalizedPhone[normalizedPhone.index(after: normalizedPhone.startIndex)...])
}
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> CachedResolvedByPhonePeer? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByPhonePeers, key: CachedResolvedByPhonePeer.key(name: normalizedPhone)))?.get(CachedResolvedByPhonePeer.self)
} |> mapToSignal { cachedEntry -> Signal<PeerId?, NoError> in
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if let cachedEntry = cachedEntry, cachedEntry.timestamp <= timestamp && cachedEntry.timestamp >= timestamp - ageLimit {
return .single(cachedEntry.peerId)
} else {
return account.network.request(Api.functions.contacts.resolvePhone(phone: normalizedPhone))
|> mapError { _ -> Void in
return Void()
}
|> mapToSignal { result -> Signal<PeerId?, Void> in
return account.postbox.transaction { transaction -> PeerId? in
var peerId: PeerId? = nil
switch result {
case let .resolvedPeer(apiPeer, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
if let peer = parsedPeers.get(apiPeer.peerId) {
peerId = peer.id
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
}
}
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if let entry = CodableEntry(CachedResolvedByPhonePeer(peerId: peerId, timestamp: timestamp)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.resolvedByPhonePeers, key: CachedResolvedByPhonePeer.key(name: normalizedPhone)), entry: entry)
}
return peerId
}
|> castError(Void.self)
}
|> `catch` { _ -> Signal<PeerId?, NoError> in
return .single(nil)
}
}
}
}
@@ -0,0 +1,464 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum AddSavedMusicError {
case generic
}
func revalidatedMusic<T>(account: Account, file: FileMediaReference, signal: @escaping (CloudDocumentMediaResource) -> Signal<T, MTRpcError>) -> Signal<T, MTRpcError> {
guard let resource = file.media.resource as? CloudDocumentMediaResource else {
return .fail(MTRpcError(errorCode: 500, errorDescription: "Internal"))
}
return signal(resource)
|> `catch` { error -> Signal<T, MTRpcError> in
if error.errorDescription == "FILE_REFERENCE_EXPIRED" {
return revalidateMediaResourceReference(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, revalidationContext: account.mediaReferenceRevalidationContext, info: TelegramCloudMediaResourceFetchInfo(reference: file.resourceReference(resource), preferBackgroundReferenceRevalidation: false, continueInBackground: false), resource: resource)
|> mapError { _ -> MTRpcError in
return MTRpcError(errorCode: 500, errorDescription: "Internal")
}
|> mapToSignal { result -> Signal<T, MTRpcError> in
guard let resource = result.updatedResource as? CloudDocumentMediaResource else {
return .fail(MTRpcError(errorCode: 500, errorDescription: "Internal"))
}
return signal(resource)
}
} else {
return .fail(error)
}
}
}
public final class SavedMusicIdsList: Codable, Equatable {
public let items: [Int64]
public init(items: [Int64]) {
self.items = items
}
public static func ==(lhs: SavedMusicIdsList, rhs: SavedMusicIdsList) -> Bool {
if lhs === rhs {
return true
}
if lhs.items != rhs.items {
return false
}
return true
}
}
func _internal_getSavedMusicById(postbox: Postbox, network: Network, peer: PeerReference, file: TelegramMediaFile) -> Signal<TelegramMediaFile?, NoError> {
let inputUser = peer.inputUser
guard let inputUser, let resource = file.resource as? CloudDocumentMediaResource else {
return .single(nil)
}
return network.request(Api.functions.users.getSavedMusicByID(id: inputUser, documents: [.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))]))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.users.SavedMusic?, NoError> in
return .single(nil)
}
|> map { result -> TelegramMediaFile? in
if let result {
switch result {
case let .savedMusic(_, documents):
if let file = documents.first.flatMap({ telegramMediaFileFromApiDocument($0, altDocuments: nil) }) {
return file
}
default:
break
}
}
return nil
}
}
func _internal_savedMusicIds(postbox: Postbox) -> Signal<Set<Int64>?, NoError> {
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.savedMusicIds()]))
return postbox.combinedView(keys: [viewKey])
|> map { views -> Set<Int64>? in
guard let view = views.views[viewKey] as? PreferencesView else {
return nil
}
guard let value = view.values[PreferencesKeys.savedMusicIds()]?.get(SavedMusicIdsList.self) else {
return nil
}
return Set(value.items)
}
}
func _internal_keepSavedMusicIdsUpdated(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal<Never, NoError> {
let updateSignal = _internal_savedMusicIds(postbox: postbox)
|> take(1)
|> mapToSignal { list -> Signal<Never, NoError> in
//TODO:release
return network.request(Api.functions.account.getSavedMusicIds(hash: 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.account.SavedMusicIds?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result else {
return .complete()
}
return postbox.transaction { transaction in
switch result {
case let .savedMusicIds(ids):
let savedMusicIdsList = SavedMusicIdsList(items: ids)
transaction.setPreferencesEntry(key: PreferencesKeys.savedMusicIds(), value: PreferencesEntry(savedMusicIdsList))
case .savedMusicIdsNotModified:
break
}
}
|> ignoreValues
}
}
return updateSignal
}
func managedSavedMusicIdsUpdates(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal<Never, NoError> {
let poll = _internal_keepSavedMusicIdsUpdated(postbox: postbox, network: network, accountPeerId: accountPeerId)
return (poll |> then(.complete() |> suspendAwareDelay(0.5 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
func _internal_addSavedMusic(account: Account, file: FileMediaReference, afterFile: FileMediaReference?) -> Signal<Never, AddSavedMusicError> {
return account.postbox.transaction { transaction in
if let cachedSavedMusic = transaction.retrieveItemCacheEntry(id: entryId(peerId: account.peerId))?.get(CachedProfileSavedMusic.self) {
var updatedFiles = cachedSavedMusic.files
var updatedCount = cachedSavedMusic.count
if let fromIndex = updatedFiles.firstIndex(where: { $0.fileId == file.media.fileId }) {
let anchorIdxOpt: Int? = afterFile.flatMap { af in
updatedFiles.firstIndex(where: { $0.fileId == af.media.fileId })
}
updatedFiles.remove(at: fromIndex)
let insertIndex: Int
if let anchorIndex = anchorIdxOpt {
if anchorIndex == fromIndex {
insertIndex = min(fromIndex + 1, updatedFiles.count)
} else {
let adjustedAnchor = anchorIndex > fromIndex ? (anchorIndex - 1) : anchorIndex
insertIndex = updatedFiles.index(after: adjustedAnchor)
}
} else if afterFile != nil {
insertIndex = 0
} else {
insertIndex = 0
}
updatedFiles.insert(file.media, at: insertIndex)
} else {
if let afterFile, let anchor = updatedFiles.firstIndex(where: { $0.fileId == afterFile.media.fileId }) {
updatedFiles.insert(file.media, at: updatedFiles.index(after: anchor))
} else if afterFile != nil {
updatedFiles.append(file.media)
} else {
updatedFiles.insert(file.media, at: 0)
}
updatedCount = updatedCount + 1
}
if let entry = CodableEntry(CachedProfileSavedMusic(files: updatedFiles, count: updatedCount)) {
transaction.putItemCacheEntry(id: entryId(peerId: account.peerId), entry: entry)
}
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.savedMusicIds())?.get(SavedMusicIdsList.self) {
var ids = Set(entry.items)
ids.insert(file.media.fileId.id)
let savedMusicIdsList = SavedMusicIdsList(items: Array(ids))
transaction.setPreferencesEntry(key: PreferencesKeys.savedMusicIds(), value: PreferencesEntry(savedMusicIdsList))
}
if afterFile == nil {
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedUserData {
var updatedData = cachedData
updatedData = updatedData.withUpdatedSavedMusic(file.media)
return updatedData
} else {
return cachedData
}
})
}
}
return revalidatedMusic(account: account, file: file, signal: { resource in
var flags: Int32 = 0
var afterId: Api.InputDocument?
if let afterFile, let resource = afterFile.media.resource as? CloudDocumentMediaResource {
flags = 1 << 1
afterId = .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))
}
return account.network.request(Api.functions.account.saveMusic(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), afterId: afterId))
})
|> mapError { _ -> AddSavedMusicError in
return .generic
}
|> mapToSignal { _ in
return .complete()
}
}
|> castError(AddSavedMusicError.self)
|> switchToLatest
}
func _internal_removeSavedMusic(account: Account, file: FileMediaReference) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction in
if let cachedSavedMusic = transaction.retrieveItemCacheEntry(id: entryId(peerId: account.peerId))?.get(CachedProfileSavedMusic.self) {
let updatedFiles = cachedSavedMusic.files.filter { $0.fileId != file.media.id }
let updatedCount = max(0, cachedSavedMusic.count - 1)
if let entry = CodableEntry(CachedProfileSavedMusic(files: updatedFiles, count: updatedCount)) {
transaction.putItemCacheEntry(id: entryId(peerId: account.peerId), entry: entry)
}
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.savedMusicIds())?.get(SavedMusicIdsList.self) {
var ids = Set(entry.items)
ids.remove(file.media.fileId.id)
let savedMusicIdsList = SavedMusicIdsList(items: Array(ids))
transaction.setPreferencesEntry(key: PreferencesKeys.savedMusicIds(), value: PreferencesEntry(savedMusicIdsList))
}
if updatedCount == 0 {
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedUserData {
var updatedData = cachedData
updatedData = updatedData.withUpdatedSavedMusic(nil)
return updatedData
} else {
return cachedData
}
})
}
}
let flags: Int32 = 1 << 0
return revalidatedMusic(account: account, file: file, signal: { resource in
return account.network.request(Api.functions.account.saveMusic(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), afterId: nil))
})
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .complete()
}
|> mapToSignal { _ in
return .complete()
}
}
|> switchToLatest
}
private final class CachedProfileSavedMusic: Codable {
enum CodingKeys: String, CodingKey {
case files
case count
}
let files: [TelegramMediaFile]
let count: Int32
init(files: [TelegramMediaFile], count: Int32) {
self.files = files
self.count = count
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.files = try container.decode([TelegramMediaFile].self, forKey: .files)
self.count = try container.decode(Int32.self, forKey: .count)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.files, forKey: .files)
try container.encode(self.count, forKey: .count)
}
}
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedProfileSavedMusic, key: cacheKey)
}
public final class ProfileSavedMusicContext {
public struct State: Equatable {
public enum DataState: Equatable {
case loading
case ready(canLoadMore: Bool)
}
public var files: [TelegramMediaFile]
public var count: Int32?
public var dataState: DataState
}
private let queue: Queue = .mainQueue()
private let account: Account
public let peerId: EnginePeer.Id
private let disposable = MetaDisposable()
private let cacheDisposable = MetaDisposable()
private var files: [TelegramMediaFile] = []
private var count: Int32?
private var dataState: ProfileSavedMusicContext.State.DataState = .ready(canLoadMore: true)
private let stateValue = Promise<State>()
public var state: Signal<State, NoError> {
return self.stateValue.get()
}
public init(account: Account, peerId: EnginePeer.Id) {
self.account = account
self.peerId = peerId
self.loadMore()
}
deinit {
self.disposable.dispose()
self.cacheDisposable.dispose()
}
public func reload() {
self.files = []
self.dataState = .ready(canLoadMore: true)
self.loadMore(reload: true)
}
public func loadMore(reload: Bool = false) {
let peerId = self.peerId
let network = self.account.network
let postbox = self.account.postbox
let dataState = self.dataState
let offset = Int32(self.files.count)
guard case .ready(true) = dataState else {
return
}
if self.files.isEmpty, !reload {
self.cacheDisposable.set((postbox.transaction { transaction -> CachedProfileSavedMusic? in
return transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileSavedMusic.self)
} |> deliverOn(self.queue)).start(next: { [weak self] cachedSavedMusic in
guard let self, let cachedSavedMusic else {
return
}
self.files = cachedSavedMusic.files
self.count = cachedSavedMusic.count
if case .loading = self.dataState {
self.pushState()
}
}))
}
self.dataState = .loading
if !reload {
self.pushState()
}
let signal = postbox.loadedPeerWithId(peerId)
|> castError(MTRpcError.self)
|> mapToSignal { peer -> Signal<([TelegramMediaFile], Int32), MTRpcError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
}
return network.request(Api.functions.users.getSavedMusic(id: inputUser, offset: offset, limit: 32, hash: 0))
|> map { result -> ([TelegramMediaFile], Int32) in
switch result {
case let .savedMusic(count, documents):
return (documents.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: nil) }, count)
case let .savedMusicNotModified(count):
return ([], count)
}
}
}
self.disposable.set((signal
|> deliverOn(self.queue)).start(next: { [weak self] files, count in
guard let self else {
return
}
if offset == 0 || reload {
self.files = files
self.cacheDisposable.set(self.account.postbox.transaction { transaction in
if let entry = CodableEntry(CachedProfileSavedMusic(files: files, count: count)) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
}.start())
} else {
self.files.append(contentsOf: files)
}
let updatedCount = max(Int32(self.files.count), count)
self.count = updatedCount
self.dataState = .ready(canLoadMore: count != 0 && updatedCount > self.files.count)
self.pushState()
}))
}
public func addMusic(file: FileMediaReference, afterFile: FileMediaReference? = nil, apply: Bool = true) -> Signal<Never, AddSavedMusicError> {
var updatedFiles = self.files
let fromIdx = updatedFiles.firstIndex { $0.fileId == file.media.fileId }
let anchorIdxOpt = afterFile.flatMap { af in
updatedFiles.firstIndex { $0.fileId == af.media.fileId }
}
if let fromIdx = fromIdx {
updatedFiles.remove(at: fromIdx)
let insertIdx: Int
if let anchorIdx = anchorIdxOpt {
if anchorIdx == fromIdx {
insertIdx = min(fromIdx + 1, updatedFiles.count)
} else {
let adjustedAnchor = anchorIdx > fromIdx ? (anchorIdx - 1) : anchorIdx
insertIdx = updatedFiles.index(after: adjustedAnchor)
}
} else if afterFile != nil {
insertIdx = updatedFiles.count
} else {
insertIdx = 0
}
updatedFiles.insert(file.media, at: insertIdx)
} else {
if let anchorIdx = anchorIdxOpt {
updatedFiles.insert(file.media, at: updatedFiles.index(after: anchorIdx))
} else if afterFile != nil {
updatedFiles.append(file.media)
} else {
updatedFiles.insert(file.media, at: 0)
}
if let count = self.count {
self.count = count + 1
}
}
self.files = updatedFiles
self.pushState()
if apply {
return _internal_addSavedMusic(account: self.account, file: file, afterFile: afterFile)
} else {
return .complete()
}
}
public func removeMusic(file: FileMediaReference) -> Signal<Never, NoError> {
self.files.removeAll(where: { $0.fileId == file.media.id })
if let count = self.count {
self.count = max(0, count - 1)
}
self.pushState()
return _internal_removeSavedMusic(account: self.account, file: file)
}
private func pushState() {
let state = State(
files: self.files,
count: self.count,
dataState: self.dataState
)
self.stateValue.set(.single(state))
}
}
@@ -0,0 +1,103 @@
import Foundation
import Postbox
import SwiftSignalKit
private struct PeerParticipants: Equatable {
let peers: [Peer]
static func ==(lhs: PeerParticipants, rhs: PeerParticipants) -> Bool {
if lhs.peers.count != rhs.peers.count {
return false
}
for i in 0 ..< lhs.peers.count {
if !lhs.peers[i].isEqual(rhs.peers[i]) {
return false
}
}
return true
}
}
private func peerParticipants(postbox: Postbox, id: PeerId) -> Signal<[Peer], NoError> {
return postbox.peerView(id: id) |> map { view -> PeerParticipants in
if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
var peers: [Peer] = []
for participant in participants.participants {
if let peer = view.peers[participant.peerId] {
peers.append(peer)
}
}
return PeerParticipants(peers: peers)
} else {
return PeerParticipants(peers: [])
}
}
|> distinctUntilChanged |> map { participants in
return participants.peers
}
}
private func searchLocalGroupMembers(postbox: Postbox, peerId: PeerId, query: String) -> Signal<[Peer], NoError> {
return peerParticipants(postbox: postbox, id: peerId)
|> map { peers -> [Peer] in
let normalizedQuery = query.lowercased()
if normalizedQuery.isEmpty {
return peers
}
return peers.filter { peer in
if peer.debugDisplayTitle.isEmpty {
return false
}
if peer.indexName.matchesByTokens(normalizedQuery) {
return true
}
if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
return true
}
return false
}
}
}
func _internal_searchGroupMembers(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, query: String) -> Signal<[Peer], NoError> {
if peerId.namespace == Namespaces.Peer.CloudChannel && !query.isEmpty {
return searchLocalGroupMembers(postbox: postbox, peerId: peerId, query: query)
|> mapToSignal { local -> Signal<[Peer], NoError> in
let localResult: Signal<[Peer], NoError>
if local.isEmpty {
localResult = .complete()
} else {
localResult = .single(local)
}
return localResult
|> then(
_internal_channelMembers(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, category: .recent(.search(query)))
|> map { participants -> [Peer] in
var result: [Peer] = local
let existingIds = Set(local.map { $0.id })
let filtered: [Peer]
if let participants = participants {
filtered = participants.map({ $0.peer }).filter({ peer in
if existingIds.contains(peer.id) {
return false
}
if peer.debugDisplayTitle.isEmpty {
return false
}
return true
})
} else {
filtered = []
}
result.append(contentsOf: filtered)
return result
}
)
}
} else {
return searchLocalGroupMembers(postbox: postbox, peerId: peerId, query: query)
}
}
@@ -0,0 +1,190 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public struct FoundPeer: Equatable {
public let peer: Peer
public let subscribers: Int32?
public init(peer: Peer, subscribers: Int32?) {
self.peer = peer
self.subscribers = subscribers
}
public static func ==(lhs: FoundPeer, rhs: FoundPeer) -> Bool {
return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers
}
}
public enum TelegramSearchPeersScope: Equatable {
case everywhere
case channels
case groups
case privateChats
case globalPosts(allowPaidStars: Int?)
}
public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, network: Network, query: String, scope: TelegramSearchPeersScope) -> Signal<([FoundPeer], [FoundPeer]), NoError> {
let searchResult = network.request(Api.functions.contacts.search(q: query, limit: 20), automaticFloodWait: false)
|> map(Optional.init)
|> `catch` { _ in
return Signal<Api.contacts.Found?, NoError>.single(nil)
}
let processedSearchResult = searchResult
|> mapToSignal { result -> Signal<([FoundPeer], [FoundPeer]), NoError> in
if let result = result {
switch result {
case let .found(myResults, results, chats, users):
return postbox.transaction { transaction -> ([FoundPeer], [FoundPeer]) in
var subscribers: [PeerId: Int32] = [:]
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _):
if let participantsCount = participantsCount {
subscribers[groupOrChannel.id] = participantsCount
}
default:
break
}
}
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var renderedMyPeers: [FoundPeer] = []
for result in myResults {
let peerId: PeerId = result.peerId
if let peer = parsedPeers.get(peerId) {
if let group = peer as? TelegramGroup, group.migrationReference != nil {
continue
}
if let user = peer as? TelegramUser {
renderedMyPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount))
} else {
renderedMyPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId]))
}
}
}
var renderedPeers: [FoundPeer] = []
for result in results {
let peerId: PeerId = result.peerId
if let peer = parsedPeers.get(peerId) {
if let group = peer as? TelegramGroup, group.migrationReference != nil {
continue
}
if let user = peer as? TelegramUser {
renderedPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount))
} else {
renderedPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId]))
}
}
}
switch scope {
case .everywhere:
break
case .channels:
renderedMyPeers = renderedMyPeers.filter { item in
if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info {
return true
} else {
return false
}
}
renderedPeers = renderedPeers.filter { item in
if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info {
return true
} else {
return false
}
}
case .groups:
renderedMyPeers = renderedMyPeers.filter { item in
if let channel = item.peer as? TelegramChannel, case .group = channel.info {
return true
} else if item.peer is TelegramGroup {
return true
} else {
return false
}
}
renderedPeers = renderedPeers.filter { item in
if let channel = item.peer as? TelegramChannel, case .group = channel.info {
return true
} else if item.peer is TelegramGroup {
return true
} else {
return false
}
}
case .privateChats:
renderedMyPeers = renderedMyPeers.filter { item in
if item.peer is TelegramUser {
return true
} else {
return false
}
}
renderedPeers = renderedPeers.filter { item in
if item.peer is TelegramUser {
return true
} else {
return false
}
}
case .globalPosts:
renderedMyPeers = []
renderedPeers = []
}
return (renderedMyPeers, renderedPeers)
}
}
} else {
return .single(([], []))
}
}
return processedSearchResult
}
func _internal_searchLocalSavedMessagesPeers(account: Account, query: String, indexNameMapping: [EnginePeer.Id: [PeerIndexNameRepresentation]]) -> Signal<[EnginePeer], NoError> {
return account.postbox.transaction { transaction -> [EnginePeer] in
return transaction.searchSubPeers(peerId: account.peerId, query: query, indexNameMapping: indexNameMapping).map(EnginePeer.init)
}
}
func _internal_requestMessageAuthor(account: Account, id: EngineMessage.Id) -> Signal<EnginePeer?, NoError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(id.peerId).flatMap(apiInputChannel)
}
|> mapToSignal { inputChannel -> Signal<EnginePeer?, NoError> in
guard let inputChannel else {
return .single(nil)
}
if id.namespace != Namespaces.Message.Cloud {
return .single(nil)
}
return account.network.request(Api.functions.channels.getMessageAuthor(channel: inputChannel, id: id.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.User?, NoError> in
return .single(nil)
}
|> mapToSignal { user -> Signal<EnginePeer?, NoError> in
guard let user else {
return .single(nil)
}
return account.postbox.transaction { transaction -> EnginePeer? in
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: [user]))
return transaction.getPeer(user.peerId).flatMap(EnginePeer.init)
}
}
}
}
@@ -0,0 +1,36 @@
import Postbox
import TelegramApi
import SwiftSignalKit
public enum UpdateChannelSlowModeError {
case generic
case tooManyChannels
}
func _internal_updateChannelSlowModeInteractively(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, timeout: Int32?) -> Signal<Void, UpdateChannelSlowModeError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> castError(UpdateChannelSlowModeError.self)
|> mapToSignal { peer in
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
return .fail(.generic)
}
return network.request(Api.functions.channels.toggleSlowMode(channel: inputChannel, seconds: timeout ?? 0))
|> `catch` { _ -> Signal<Api.Updates, UpdateChannelSlowModeError> in
return .fail(.generic)
}
|> mapToSignal { updates -> Signal<Void, UpdateChannelSlowModeError> in
accountStateManager.addUpdates(updates)
return postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, currentData in
let currentData = currentData as? CachedChannelData ?? CachedChannelData()
return currentData.withUpdatedSlowModeTimeout(timeout)
})
}
|> castError(UpdateChannelSlowModeError.self)
}
}
}
@@ -0,0 +1,27 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum SuggestBirthdayError {
case generic
}
func _internal_suggestBirthday(account: Account, peerId: EnginePeer.Id, birthday: TelegramBirthday) -> Signal<Never, SuggestBirthdayError> {
return account.postbox.loadedPeerWithId(peerId)
|> castError(SuggestBirthdayError.self)
|> mapToSignal { peer in
guard let inputUser = apiInputUser(peer) else {
return .complete()
}
return account.network.request(Api.functions.users.suggestBirthday(id: inputUser, birthday: birthday.apiBirthday))
|> mapError { _ in
return .generic
}
|> mapToSignal { updates in
account.stateManager.addUpdates(updates)
return .complete()
}
}
}
@@ -0,0 +1,28 @@
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_supportPeerId(account: Account) -> Signal<PeerId?, NoError> {
let accountPeerId = account.peerId
return account.network.request(Api.functions.help.getSupport())
|> map(Optional.init)
|> `catch` { _ in
return Signal<Api.help.Support?, NoError>.single(nil)
}
|> mapToSignal { support -> Signal<PeerId?, NoError> in
if let support = support {
switch support {
case let .support(_, user):
return account.postbox.transaction { transaction -> PeerId in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: [user])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
return user.peerId
}
}
}
return .single(nil)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_toggleShouldChannelMessagesSignatures(account: Account, peerId: PeerId, signaturesEnabled: Bool, profilesEnabled: Bool) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId) as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
var flags: Int32 = 0
if signaturesEnabled {
flags |= 1 << 0
}
if profilesEnabled {
flags |= 1 << 1
}
return account.network.request(Api.functions.channels.toggleSignatures(flags: flags, channel: inputChannel)) |> retryRequest |> map { updates -> Void in
account.stateManager.addUpdates(updates)
}
} else {
return .complete()
}
} |> switchToLatest
}
@@ -0,0 +1,142 @@
import Foundation
import Postbox
import SwiftSignalKit
public enum TogglePeerChatPinnedLocation {
case group(PeerGroupId)
case filter(Int32)
}
public enum TogglePeerChatPinnedResult {
case done
case limitExceeded(count: Int, limit: Int)
}
func _internal_toggleItemPinned(postbox: Postbox, accountPeerId: PeerId, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
return postbox.transaction { transaction -> TogglePeerChatPinnedResult in
let isPremium = transaction.getPeer(accountPeerId)?.isPremium ?? false
let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue
let userLimitsConfiguration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: isPremium)
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 limitCount: Int
if case .root = groupId {
limitCount = Int(userLimitsConfiguration.maxPinnedChatCount)
} else {
limitCount = Int(userLimitsConfiguration.maxArchivedPinnedChatCount)
}
let count = sameKind.count + additionalCount
if count > limitCount, itemIds.firstIndex(of: itemId) == nil {
return .limitExceeded(count: sameKind.count, limit: 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 }), case let .filter(id, title, emoticon, data) = filters[index] {
switch itemId {
case let .peer(peerId):
var updatedData = data
if updatedData.includePeers.pinnedPeers.contains(peerId) {
updatedData.includePeers.removePinnedPeer(peerId)
} else {
let _ = updatedData.includePeers.addPinnedPeer(peerId)
if updatedData.includePeers.peers.count > userLimitsConfiguration.maxFolderChatsCount {
result = .limitExceeded(count: updatedData.includePeers.peers.count, limit: Int(userLimitsConfiguration.maxFolderChatsCount))
updatedData = data
}
}
filters[index] = .filter(id: id, title: title, emoticon: emoticon, data: updatedData)
}
}
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 }), case let .filter(_, _, _, data) = filters[index] {
itemIds = 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 }), case let .filter(id, title, emoticon, data) = filters[index] {
let peerIds: [PeerId] = itemIds.map { itemId -> PeerId in
switch itemId {
case let .peer(peerId):
return peerId
}
}
var updatedData = data
if updatedData.includePeers.pinnedPeers != peerIds {
updatedData.includePeers.reorderPinnedPeers(peerIds)
filters[index] = .filter(id: id, title: title, emoticon: emoticon, data: updatedData)
result = true
}
}
return filters
})
return result
}
}
@@ -0,0 +1,201 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum UpdateBotInfoError {
case generic
}
func _internal_updateBotName(account: Account, peerId: PeerId, name: String) -> Signal<Void, UpdateBotInfoError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateBotInfoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
var flags: Int32 = 1 << 2
flags |= (1 << 3)
return account.network.request(Api.functions.bots.setBotInfo(flags: flags, bot: inputUser, langCode: "", name: name, about: nil, description: nil))
|> mapError { _ -> UpdateBotInfoError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateBotInfoError> in
return account.postbox.transaction { transaction -> Void in
if case .boolTrue = result {
var previousBotName: String?
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData, let editableBotInfo = current.editableBotInfo {
previousBotName = editableBotInfo.name
return current.withUpdatedEditableBotInfo(editableBotInfo.withUpdatedName(name))
} else {
return current
}
})
updatePeersCustom(transaction: transaction, peers: [peer]) { _, peer in
var updatedPeer = peer
if let user = peer as? TelegramUser, user.firstName == previousBotName {
updatedPeer = user.withUpdatedNames(firstName: name, lastName: nil)
}
return updatedPeer
}
}
}
|> mapError { _ -> UpdateBotInfoError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateBotInfoError in }
|> switchToLatest
}
func _internal_updateBotAbout(account: Account, peerId: PeerId, about: String) -> Signal<Void, UpdateBotInfoError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateBotInfoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
var flags: Int32 = 1 << 2
flags |= (1 << 0)
return account.network.request(Api.functions.bots.setBotInfo(flags: flags, bot: inputUser, langCode: "", name: nil, about: about, description: nil))
|> mapError { _ -> UpdateBotInfoError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateBotInfoError> 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? CachedUserData, let editableBotInfo = current.editableBotInfo {
var updatedAbout = current.about
if (current.about ?? "") == editableBotInfo.about {
updatedAbout = about
}
return current.withUpdatedEditableBotInfo(editableBotInfo.withUpdatedAbout(about)).withUpdatedAbout(updatedAbout)
} else {
return current
}
})
}
}
|> mapError { _ -> UpdateBotInfoError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateBotInfoError in }
|> switchToLatest
}
func _internal_updateBotDescription(account: Account, peerId: PeerId, description: String) -> Signal<Void, UpdateBotInfoError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateBotInfoError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
var flags: Int32 = 1 << 2
flags |= (1 << 1)
return account.network.request(Api.functions.bots.setBotInfo(flags: flags, bot: inputUser, langCode: "", name: nil, about: nil, description: description))
|> mapError { _ -> UpdateBotInfoError in
return .generic
}
|> mapToSignal { result -> Signal<Void, UpdateBotInfoError> 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? CachedUserData, let editableBotInfo = current.editableBotInfo {
if let botInfo = current.botInfo {
var updatedBotInfo = botInfo
if botInfo.description == editableBotInfo.description {
updatedBotInfo = BotInfo(description: description, photo: botInfo.photo, video: botInfo.video, commands: botInfo.commands, menuButton: botInfo.menuButton, privacyPolicyUrl: botInfo.privacyPolicyUrl, appSettings: botInfo.appSettings, verifierSettings: botInfo.verifierSettings)
}
return current.withUpdatedEditableBotInfo(editableBotInfo.withUpdatedDescription(description)).withUpdatedBotInfo(updatedBotInfo)
} else {
return current.withUpdatedEditableBotInfo(editableBotInfo.withUpdatedDescription(description))
}
} else {
return current
}
})
}
}
|> mapError { _ -> UpdateBotInfoError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateBotInfoError in }
|> switchToLatest
}
public enum ToggleBotEmojiStatusAccessError {
case generic
}
func _internal_toggleBotEmojiStatusAccess(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Never, ToggleBotEmojiStatusAccessError> {
return account.postbox.transaction { transaction -> Signal<Void, ToggleBotEmojiStatusAccessError> in
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.bots.toggleUserEmojiStatusPermission(bot: inputUser, enabled: enabled ? .boolTrue : .boolFalse))
|> mapError { _ -> ToggleBotEmojiStatusAccessError in
return .generic
}
|> mapToSignal { result -> Signal<Void, ToggleBotEmojiStatusAccessError> 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? CachedUserData {
var updatedFlags: CachedUserFlags = current.flags
if enabled {
updatedFlags.insert(.botCanManageEmojiStatus)
} else {
updatedFlags.remove(.botCanManageEmojiStatus)
}
return current.withUpdatedFlags(updatedFlags)
} else {
return current
}
})
}
}
|> mapError { _ -> ToggleBotEmojiStatusAccessError in }
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> ToggleBotEmojiStatusAccessError in }
|> switchToLatest
|> ignoreValues
}
public enum UpdateCustomVerificationError {
case generic
}
public enum UpdateCustomVerificationValue {
case enabled(description: String?)
case disabled
}
func _internal_updateCustomVerification(account: Account, botId: PeerId, peerId: PeerId, value: UpdateCustomVerificationValue) -> Signal<Never, UpdateCustomVerificationError> {
return account.postbox.transaction { transaction -> Signal<Api.Bool, UpdateCustomVerificationError> in
if let bot = transaction.getPeer(botId), let inputBot = apiInputUser(bot), let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var flags: Int32 = (1 << 0)
var customDescription: String?
switch value {
case let .enabled(description):
flags |= (1 << 1)
if let description, !description.isEmpty {
flags |= (1 << 2)
customDescription = description
}
case .disabled:
break
}
return account.network.request(Api.functions.bots.setCustomVerification(flags: flags, bot: inputBot, peer: inputPeer, customDescription: customDescription))
|> mapError { _ -> UpdateCustomVerificationError in
return .generic
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> UpdateCustomVerificationError in }
|> switchToLatest
|> ignoreValues
}
@@ -0,0 +1,990 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountPeerId: 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
}
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
return .single(false)
}
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: [], managingBot: nil)
} else {
peerStatusSettings = PeerStatusSettings(flags: [.canReport], managingBot: nil)
}
} else {
peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil)
}
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))
|> retryRequestIfNotFrozen
|> mapToSignal { peerSettings -> Signal<Bool, NoError> in
guard let peerSettings else {
return .single(false)
}
return postbox.transaction { transaction -> Bool in
let parsedPeers: AccumulatedPeers
let peerStatusSettings: PeerStatusSettings
switch peerSettings {
case let .peerSettings(settings, chats, users):
peerStatusSettings = PeerStatusSettings(apiSettings: settings)
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
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, rawPeer, rawPeerId)
} else {
return (apiInputUser(peer), peer, peer.id)
}
}
|> mapToSignal { inputUser, maybePeer, peerId -> Signal<Bool, NoError> in
if let inputUser = inputUser {
let editableBotInfo: Signal<EditableBotInfo?, NoError>
if let user = maybePeer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
let flags: Int32 = (1 << 0)
editableBotInfo = network.request(Api.functions.bots.getBotInfo(flags: flags, bot: inputUser, langCode: ""))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.bots.BotInfo?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<EditableBotInfo?, NoError> in
if let result = result {
switch result {
case let .botInfo(name, about, description):
return .single(EditableBotInfo(name: name, about: about, description: description))
}
} else {
return .single(nil)
}
}
} else {
editableBotInfo = .single(nil)
}
let botPreview: Signal<CachedUserData.BotPreview?, NoError>
if let user = maybePeer as? TelegramUser, let botInfo = user.botInfo {
if botInfo.flags.contains(.canEdit) {
botPreview = _internal_requestBotAdminPreview(network: network, peerId: user.id, inputUser: inputUser, language: nil)
} else {
botPreview = _internal_requestBotUserPreview(network: network, peerId: user.id, inputUser: inputUser)
}
} else {
botPreview = .single(nil)
}
var additionalConnectedBots: Signal<Api.account.ConnectedBots?, NoError> = .single(nil)
if rawPeerId == accountPeerId {
additionalConnectedBots = network.request(Api.functions.account.getConnectedBots())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.account.ConnectedBots?, NoError> in
return .single(nil)
}
}
return combineLatest(
network.request(Api.functions.users.getFullUser(id: inputUser))
|> retryRequest,
editableBotInfo,
botPreview,
additionalConnectedBots
)
|> mapToSignal { result, editableBotInfo, botPreview, additionalConnectedBots -> Signal<Bool, NoError> in
return postbox.transaction { transaction -> Bool in
switch result {
case let .userFull(fullUser, chats, users):
var accountUser: Api.User?
var parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
for user in users {
if user.peerId == accountPeerId {
accountUser = user
}
}
let _ = accountUser
var mappedConnectedBot: TelegramAccountConnectedBot?
if let additionalConnectedBots {
switch additionalConnectedBots {
case let .connectedBots(connectedBots, users):
parsedPeers = parsedPeers.union(with: AccumulatedPeers(transaction: transaction, chats: [], users: users))
if let apiBot = connectedBots.first {
switch apiBot {
case let .connectedBot(_, botId, recipients, rights):
mappedConnectedBot = TelegramAccountConnectedBot(
id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)),
recipients: TelegramBusinessRecipients(apiValue: recipients),
rights: TelegramBusinessBotRights(apiValue: rights)
)
}
}
}
}
switch fullUser {
case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)])
}
transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, current in
let previous: CachedUserData
if let current = current as? CachedUserData {
previous = current
} else {
previous = CachedUserData()
}
switch fullUser {
case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic, note):
let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:))
let isBlocked = (userFullFlags & (1 << 0)) != 0
let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0
let videoCallsAvailable = (userFullFlags & (1 << 13)) != 0
let voiceMessagesAvailable = (userFullFlags & (1 << 20)) == 0
let readDatesPrivate = (userFullFlags & (1 << 30)) != 0
let premiumRequired = (userFullFlags & (1 << 29)) != 0
let translationsDisabled = (userFullFlags & (1 << 23)) != 0
let adsEnabled = (userFullFlags2 & (1 << 7)) != 0
let canViewRevenue = (userFullFlags2 & (1 << 9)) != 0
let botCanManageEmojiStatus = (userFullFlags2 & (1 << 10)) != 0
let displayGiftButton = (userFullFlags2 & (1 << 16)) != 0
var flags: CachedUserFlags = previous.flags
if premiumRequired {
flags.insert(.premiumRequired)
} else {
flags.remove(.premiumRequired)
}
if readDatesPrivate {
flags.insert(.readDatesPrivate)
} else {
flags.remove(.readDatesPrivate)
}
if translationsDisabled {
flags.insert(.translationHidden)
} else {
flags.remove(.translationHidden)
}
if adsEnabled {
flags.insert(.adsEnabled)
} else {
flags.remove(.adsEnabled)
}
if canViewRevenue {
flags.insert(.canViewRevenue)
} else {
flags.remove(.canViewRevenue)
}
if botCanManageEmojiStatus {
flags.insert(.botCanManageEmojiStatus)
} else {
flags.remove(.botCanManageEmojiStatus)
}
if displayGiftButton {
flags.insert(.displayGiftButton)
} else {
flags.remove(.displayGiftButton)
}
let callsPrivate = (userFullFlags & (1 << 5)) != 0
let canPinMessages = (userFullFlags & (1 << 7)) != 0
let pinnedMessageId = userFullPinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })
let peerStatusSettings = PeerStatusSettings(apiSettings: userFullSettings)
let hasScheduledMessages = (userFullFlags & 1 << 12) != 0
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(userFullTtlPeriod))
let personalPhoto = personalPhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
let photo = profilePhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
let fallbackPhoto = fallbackPhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
let wallpaper = userWallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) }
var mappedBusinessHours: TelegramBusinessHours?
if let businessWorkHours {
mappedBusinessHours = TelegramBusinessHours(apiWorkingHours: businessWorkHours)
}
var mappedBusinessLocation: TelegramBusinessLocation?
if let businessLocation {
mappedBusinessLocation = TelegramBusinessLocation(apiLocation: businessLocation)
}
var mappedGreetingMessage: TelegramBusinessGreetingMessage?
if let greetingMessage {
mappedGreetingMessage = TelegramBusinessGreetingMessage(apiGreetingMessage: greetingMessage)
}
var mappedAwayMessage: TelegramBusinessAwayMessage?
if let awayMessage {
mappedAwayMessage = TelegramBusinessAwayMessage(apiAwayMessage: awayMessage)
}
var mappedBusinessIntro: TelegramBusinessIntro?
if let businessIntro {
mappedBusinessIntro = TelegramBusinessIntro(apiBusinessIntro: businessIntro)
}
var mappedBirthday: TelegramBirthday?
if let birthday {
mappedBirthday = TelegramBirthday(apiBirthday: birthday)
}
var personalChannel: TelegramPersonalChannel?
if let personalChannelId {
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(personalChannelId))
var subscriberCount: Int32?
for chat in chats {
if chat.peerId == channelPeerId {
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _) = chat {
subscriberCount = participantsCount
}
}
}
personalChannel = TelegramPersonalChannel(
peerId: channelPeerId,
subscriberCount: subscriberCount,
topMessageId: personalChannelMessage
)
}
var mappedStarRefProgram: TelegramStarRefProgram?
if let starRefProgram {
mappedStarRefProgram = TelegramStarRefProgram(apiStarRefProgram: starRefProgram)
}
let verification = verification.flatMap { PeerVerification(apiBotVerification: $0) }
let sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) }
let disallowedGifts = TelegramDisallowedGifts(apiDisallowedGifts: disallowedStarGifts)
let botGroupAdminRights = groupAdminRights.flatMap { TelegramChatAdminRights(apiAdminRights: $0) }
let botChannelAdminRights = channelAdminRights.flatMap { TelegramChatAdminRights(apiAdminRights: $0) }
let mappedStarRating = starsRating.flatMap(TelegramStarRating.init(apiRating:))
var pendingRating: TelegramStarPendingRating?
if let starsMyPendingRating, let starsMyPendingRatingDate {
pendingRating = TelegramStarPendingRating(
rating: TelegramStarRating(apiRating: starsMyPendingRating),
timestamp: starsMyPendingRatingDate
)
}
let mappedMainProfileTab = mainTab.flatMap { TelegramProfileTab(apiTab: $0) }
let mappedSavedMusic = savedMusic.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: nil) }
let mappedChatTheme: ChatTheme? = userFullChatTheme.flatMap { ChatTheme(apiChatTheme: $0) }
var mappedNote: CachedUserData.Note?
if let note {
switch note {
case let .textWithEntities(text, entities):
mappedNote = CachedUserData.Note(text: text, entities: messageTextEntitiesFromApiEntities(entities))
}
}
return previous.withUpdatedAbout(userFullAbout)
.withUpdatedBotInfo(botInfo)
.withUpdatedEditableBotInfo(editableBotInfo)
.withUpdatedCommonGroupCount(userFullCommonChatsCount)
.withUpdatedIsBlocked(isBlocked)
.withUpdatedVoiceCallsAvailable(voiceCallsAvailable)
.withUpdatedVideoCallsAvailable(videoCallsAvailable)
.withUpdatedCallsPrivate(callsPrivate)
.withUpdatedCanPinMessages(canPinMessages)
.withUpdatedPeerStatusSettings(peerStatusSettings)
.withUpdatedPinnedMessageId(pinnedMessageId)
.withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedChatTheme(mappedChatTheme)
.withUpdatedPhoto(.known(photo))
.withUpdatedPersonalPhoto(.known(personalPhoto))
.withUpdatedFallbackPhoto(.known(fallbackPhoto))
.withUpdatedVoiceMessagesAvailable(voiceMessagesAvailable)
.withUpdatedWallpaper(wallpaper)
.withUpdatedFlags(flags)
.withUpdatedBusinessHours(mappedBusinessHours)
.withUpdatedBusinessLocation(mappedBusinessLocation)
.withUpdatedGreetingMessage(mappedGreetingMessage)
.withUpdatedAwayMessage(mappedAwayMessage)
.withUpdatedConnectedBot(mappedConnectedBot)
.withUpdatedBusinessIntro(mappedBusinessIntro)
.withUpdatedBirthday(mappedBirthday)
.withUpdatedPersonalChannel(personalChannel)
.withUpdatedBotPreview(botPreview)
.withUpdatedStarGiftsCount(starGiftsCount)
.withUpdatedStarRefProgram(mappedStarRefProgram)
.withUpdatedVerification(verification)
.withUpdatedSendPaidMessageStars(sendPaidMessageStars)
.withUpdatedDisallowedGifts(disallowedGifts)
.withUpdatedBotGroupAdminRights(botGroupAdminRights)
.withUpdatedBotChannelAdminRights(botChannelAdminRights)
.withUpdatedStarRating(mappedStarRating)
.withUpdatedPendingStarRating(pendingRating)
.withUpdatedMainProfileTab(mappedMainProfileTab)
.withUpdatedSavedMusic(mappedSavedMusic)
.withUpdatedNote(mappedNote)
}
})
}
return true
}
}
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
return network.request(Api.functions.messages.getFullChat(chatId: peerId.id._internalGetInt64Value()))
|> retryRequestIfNotFrozen
|> mapToSignal { result -> Signal<Bool, NoError> in
guard let result else {
return .single(false)
}
return postbox.transaction { transaction -> Bool in
switch result {
case let .chatFull(fullChat, chats, users):
switch fullChat {
case let .chatFull(_, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _):
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)])
case .channelFull:
break
}
switch fullChat {
case let .chatFull(chatFullFlags, _, chatFullAbout, chatFullParticipants, chatFullChatPhoto, _, chatFullExportedInvite, chatFullBotInfo, chatFullPinnedMsgId, _, chatFullCall, chatTtlPeriod, chatFullGroupcallDefaultJoinAs, chatFullThemeEmoticon, chatFullRequestsPending, _, allowedReactions, reactionsLimit):
var botInfos: [CachedPeerBotInfo] = []
for botInfo in chatFullBotInfo ?? [] {
switch botInfo {
case let .botInfo(_, userId, _, _, _, _, _, _, _, _):
if let userId = 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: chatFullParticipants)
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(chatTtlPeriod))
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? = chatFullChatPhoto.flatMap(telegramMediaImageFromApiPhoto)
let exportedInvitation = chatFullExportedInvite.flatMap { ExportedInvitation(apiExportedInvite: $0) }
let pinnedMessageId = chatFullPinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var flags = CachedGroupFlags()
if (chatFullFlags & 1 << 7) != 0 {
flags.insert(.canChangeUsername)
}
var hasScheduledMessages = false
if (chatFullFlags & 1 << 8) != 0 {
hasScheduledMessages = true
}
let groupCallDefaultJoinAs = chatFullGroupcallDefaultJoinAs
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 = chatFullCall {
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, isStream: previous.activeCall?.isStream)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
break
}
}
let mappedAllowedReactions: PeerAllowedReactions
if let allowedReactions = allowedReactions {
switch allowedReactions {
case .chatReactionsAll:
mappedAllowedReactions = .all
case let .chatReactionsSome(reactions):
mappedAllowedReactions = .limited(reactions.compactMap(MessageReaction.Reaction.init(apiReaction:)))
case .chatReactionsNone:
mappedAllowedReactions = .empty
}
} else {
mappedAllowedReactions = .empty
}
let mappedReactionSettings = PeerReactionSettings(allowedReactions: mappedAllowedReactions, maxReactionCount: reactionsLimit, starsAllowed: nil)
let mappedChatTheme: ChatTheme? = chatFullThemeEmoticon.flatMap { .emoticon($0) }
return previous.withUpdatedParticipants(participants)
.withUpdatedExportedInvitation(exportedInvitation)
.withUpdatedBotInfos(botInfos)
.withUpdatedPinnedMessageId(pinnedMessageId)
.withUpdatedAbout(chatFullAbout)
.withUpdatedFlags(flags)
.withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedInvitedBy(invitedBy)
.withUpdatedPhoto(photo)
.withUpdatedActiveCall(updatedActiveCall)
.withUpdatedCallJoinPeerId(groupCallDefaultJoinAs?.peerId)
.withUpdatedChatTheme(mappedChatTheme)
.withUpdatedInviteRequestsPending(chatFullRequestsPending)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedReactionSettings(.known(mappedReactionSettings))
})
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: Signal<Api.channels.ChannelParticipant?, NoError>
if let channel = maybePeer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
participantSignal = .single(nil)
} else {
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(_, _, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)])
case .chatFull:
break
}
switch fullChat {
case let .channelFull(flags, flags2, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs, allowedReactions, reactionsLimit, _, wallpaper, appliedBoosts, boostsUnrestrict, emojiSet, verification, starGiftsCount, sendPaidMessageStars, mainTab):
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)
}
if (flags2 & (1 << 0)) != 0 {
channelFlags.insert(.canDeleteHistory)
}
if (flags2 & Int32(1 << 1)) != 0 {
channelFlags.insert(.antiSpamEnabled)
}
if (flags2 & Int32(1 << 3)) != 0 {
channelFlags.insert(.translationHidden)
}
if (flags2 & Int32(1 << 11)) != 0 {
channelFlags.insert(.adsRestricted)
}
if (flags2 & Int32(1 << 12)) != 0 {
channelFlags.insert(.canViewRevenue)
}
if (flags2 & Int32(1 << 14)) != 0 {
channelFlags.insert(.paidMediaAllowed)
}
if (flags2 & Int32(1 << 15)) != 0 {
channelFlags.insert(.canViewStarsRevenue)
}
if (flags2 & Int32(1 << 19)) != 0 {
channelFlags.insert(.starGiftsAvailable)
}
if (flags2 & Int32(1 << 20)) != 0 {
channelFlags.insert(.paidMessagesAvailable)
}
let sendAsPeerId = defaultSendAs?.peerId
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, _, _, _, _, _, _, _, _):
if let userId = 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 parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
if let participantResult = participantResult {
switch participantResult {
case let .channelParticipant(_, chats, users):
parsedPeers = parsedPeers.union(with: AccumulatedPeers(transaction: transaction, chats: chats, users: users))
}
}
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
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 if (flags & (1 << 7)) != 0 {
namespace = Namespaces.ItemCollection.CloudEmojiPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
return StickerPackCollectionInfo(apiSet: apiSet, namespace: namespace)
}
var hasScheduledMessages = false
if (flags & (1 << 19)) != 0 {
hasScheduledMessages = true
}
var invitedBy: PeerId?
var invitedOn: Int32?
if let participantResult = participantResult {
switch participantResult {
case let .channelParticipant(participant, _, _):
switch participant {
case let .channelParticipantSelf(flags, _, inviterId, invitedDate, _):
invitedBy = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId))
if (flags & (1 << 0)) != 0 {
invitedOn = invitedDate
}
default:
break
}
}
}
let photo = telegramMediaImageFromApiPhoto(chatPhoto)
let emojiPack: StickerPackCollectionInfo? = emojiSet.flatMap { apiSet -> StickerPackCollectionInfo in
let namespace: ItemCollectionId.Namespace
switch apiSet {
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
if (flags & (1 << 3)) != 0 {
namespace = Namespaces.ItemCollection.CloudMaskPacks
} else if (flags & (1 << 7)) != 0 {
namespace = Namespaces.ItemCollection.CloudEmojiPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
return StickerPackCollectionInfo(apiSet: apiSet, namespace: namespace)
}
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, isStream: previous.activeCall?.isStream)
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
break
}
}
let mappedAllowedReactions: PeerAllowedReactions
if let allowedReactions = allowedReactions {
switch allowedReactions {
case .chatReactionsAll:
mappedAllowedReactions = .all
case let .chatReactionsSome(reactions):
mappedAllowedReactions = .limited(reactions.compactMap(MessageReaction.Reaction.init(apiReaction:)))
case .chatReactionsNone:
mappedAllowedReactions = .empty
}
} else {
mappedAllowedReactions = .empty
}
let starsAllowed: Bool = (flags2 & (1 << 16)) != 0
let mappedReactionSettings = PeerReactionSettings(allowedReactions: mappedAllowedReactions, maxReactionCount: reactionsLimit, starsAllowed: starsAllowed)
let membersHidden = (flags2 & (1 << 2)) != 0
let forumViewAsMessages = (flags2 & (1 << 6)) != 0
let wallpaper = wallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) }
let verification = verification.flatMap { PeerVerification(apiBotVerification: $0) }
let mappedSendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) }
let mappedMainProfileTab = mainTab.flatMap { TelegramProfileTab(apiTab: $0) }
let mappedChatTheme: ChatTheme? = themeEmoticon.flatMap { .emoticon($0) }
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)
.withUpdatedInvitedOn(invitedOn)
.withUpdatedPhoto(photo)
.withUpdatedActiveCall(updatedActiveCall)
.withUpdatedCallJoinPeerId(groupcallDefaultJoinAs?.peerId)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedPendingSuggestions(pendingSuggestions ?? [])
.withUpdatedChatTheme(mappedChatTheme)
.withUpdatedInviteRequestsPending(requestsPending)
.withUpdatedSendAsPeerId(sendAsPeerId)
.withUpdatedReactionSettings(.known(mappedReactionSettings))
.withUpdatedMembersHidden(.known(PeerMembersHidden(value: membersHidden)))
.withUpdatedViewForumAsMessages(.known(forumViewAsMessages))
.withUpdatedWallpaper(wallpaper)
.withUpdatedBoostsToUnrestrict(boostsUnrestrict)
.withUpdatedAppliedBoosts(appliedBoosts)
.withUpdatedEmojiPack(emojiPack)
.withUpdatedVerification(verification)
.withUpdatedStarGiftsCount(starGiftsCount)
.withUpdatedSendPaidMessageStars(mappedSendPaidMessageStars)
.withUpdatedMainProfileTab(mappedMainProfileTab)
})
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {
var resourceIds: [MediaResourceId] = []
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(Array(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
}
}
}
func _internal_requestBotAdminPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser, language: String?) -> Signal<CachedUserData.BotPreview?, NoError> {
return network.request(Api.functions.bots.getPreviewInfo(bot: inputUser, langCode: language ?? ""))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.bots.PreviewInfo?, NoError> in
return .single(nil)
}
|> map { result -> CachedUserData.BotPreview? in
guard let result else {
return nil
}
switch result {
case let .previewInfo(media, langCodes):
return CachedUserData.BotPreview(
items: media.compactMap { item -> CachedUserData.BotPreview.Item? in
switch item {
case let .botPreviewMedia(date, media):
let value = textMediaAndExpirationTimerFromApiMedia(media, peerId)
if let media = value.media {
return CachedUserData.BotPreview.Item(media: media, timestamp: date)
} else {
return nil
}
}
},
alternativeLanguageCodes: langCodes
)
}
}
}
func _internal_requestBotUserPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser) -> Signal<CachedUserData.BotPreview?, NoError> {
return network.request(Api.functions.bots.getPreviewMedias(bot: inputUser))
|> map(Optional.init)
|> `catch` { _ -> Signal<[Api.BotPreviewMedia]?, NoError> in
return .single(nil)
}
|> map { result -> CachedUserData.BotPreview? in
guard let result else {
return nil
}
return CachedUserData.BotPreview(
items: result.compactMap { item -> CachedUserData.BotPreview.Item? in
switch item {
case let .botPreviewMedia(date, media):
let value = textMediaAndExpirationTimerFromApiMedia(media, peerId)
if let media = value.media {
return CachedUserData.BotPreview.Item(media: media, timestamp: date)
} else {
return nil
}
}
},
alternativeLanguageCodes: []
)
}
}
@@ -0,0 +1,80 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
public enum UpdateGroupSpecificStickersetError {
case generic
}
func _internal_updateGroupSpecificStickerset(postbox: Postbox, network: Network, peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal<Void, UpdateGroupSpecificStickersetError> {
return postbox.loadedPeerWithId(peerId)
|> castError(UpdateGroupSpecificStickersetError.self)
|> mapToSignal { peer -> Signal<Void, UpdateGroupSpecificStickersetError> in
let inputStickerset: Api.InputStickerSet
if let info = info {
inputStickerset = Api.InputStickerSet.inputStickerSetShortName(shortName: info.shortName)
} else {
inputStickerset = Api.InputStickerSet.inputStickerSetEmpty
}
if let inputChannel = apiInputChannel(peer) {
return network.request(Api.functions.channels.setStickers(channel: inputChannel, stickerset: inputStickerset))
|> mapError { _ -> UpdateGroupSpecificStickersetError in
return .generic
}
|> mapToSignal { value -> Signal<Void, UpdateGroupSpecificStickersetError> in
switch value {
case .boolTrue:
return postbox.transaction { transaction -> Void in
return transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current -> CachedPeerData? in
return (current as? CachedChannelData)?.withUpdatedStickerPack(info)
})
}
|> castError(UpdateGroupSpecificStickersetError.self)
default:
return .complete()
}
}
}
return .complete()
}
}
public enum UpdateGroupSpecificEmojisetError {
case generic
}
func _internal_updateGroupSpecificEmojiset(postbox: Postbox, network: Network, peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal<Void, UpdateGroupSpecificEmojisetError> {
return postbox.loadedPeerWithId(peerId)
|> castError(UpdateGroupSpecificEmojisetError.self)
|> mapToSignal { peer -> Signal<Void, UpdateGroupSpecificEmojisetError> in
let inputStickerset: Api.InputStickerSet
if let info = info {
inputStickerset = Api.InputStickerSet.inputStickerSetShortName(shortName: info.shortName)
} else {
inputStickerset = Api.InputStickerSet.inputStickerSetEmpty
}
if let inputChannel = apiInputChannel(peer) {
return network.request(Api.functions.channels.setEmojiStickers(channel: inputChannel, stickerset: inputStickerset))
|> mapError { _ -> UpdateGroupSpecificEmojisetError in
return .generic
}
|> mapToSignal { value -> Signal<Void, UpdateGroupSpecificEmojisetError> in
switch value {
case .boolTrue:
return postbox.transaction { transaction -> Void in
return transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current -> CachedPeerData? in
return (current as? CachedChannelData)?.withUpdatedEmojiPack(info)
})
}
|> castError(UpdateGroupSpecificEmojisetError.self)
default:
return .complete()
}
}
}
return .complete()
}
}
@@ -0,0 +1,368 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum UpdatePeerTitleError {
case generic
}
func _internal_updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
let accountPeerId = account.peerId
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 parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
}
} |> 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 parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
}
} |> mapError { _ -> UpdatePeerTitleError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
} |> mapError { _ -> UpdatePeerTitleError in } |> 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 }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
} |> mapError { _ -> UpdatePeerDescriptionError in } |> switchToLatest
}
public enum UpdatePeerNameColorAndEmojiError {
case generic
case channelBoostRequired
}
func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64?, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal<Void, UpdatePeerNameColorAndEmojiError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerNameColorAndEmojiError> in
if let peer = transaction.getPeer(peerId) {
if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
var flagsReplies: Int32 = (1 << 2)
if backgroundEmojiId != nil {
flagsReplies |= 1 << 0
}
var flagsProfile: Int32 = (1 << 1)
if profileBackgroundEmojiId != nil {
flagsProfile |= 1 << 0
}
if profileColor != nil {
flagsProfile |= (1 << 2)
}
return combineLatest(
account.network.request(Api.functions.channels.updateColor(flags: flagsReplies, channel: inputChannel, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, MTRpcError> in
if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") {
return .single(nil)
} else {
return .fail(error)
}
},
account.network.request(Api.functions.channels.updateColor(flags: flagsProfile, channel: inputChannel, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, MTRpcError> in
if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") {
return .single(nil)
} else {
return .fail(error)
}
}
)
|> mapError { error -> UpdatePeerNameColorAndEmojiError in
if error.errorDescription.hasPrefix("BOOSTS_REQUIRED") {
return .channelBoostRequired
}
return .generic
}
|> mapToSignal { repliesResult, profileResult -> Signal<Void, UpdatePeerNameColorAndEmojiError> in
if let repliesResult = repliesResult {
account.stateManager.addUpdates(repliesResult)
}
if let profileResult = profileResult {
account.stateManager.addUpdates(profileResult)
}
return account.postbox.transaction { transaction -> Void in
if let repliesResult = repliesResult, let apiChat = apiUpdatesGroups(repliesResult).first {
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: [])
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
}
if let profileResult = profileResult, let apiChat = apiUpdatesGroups(profileResult).first {
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: [])
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
}
}
|> mapError { _ -> UpdatePeerNameColorAndEmojiError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> castError(UpdatePeerNameColorAndEmojiError.self)
|> switchToLatest
}
func _internal_updatePeerNameColor(account: Account, peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64?) -> Signal<Void, UpdatePeerNameColorAndEmojiError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerNameColorAndEmojiError> in
if let peer = transaction.getPeer(peerId) {
if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
var flagsReplies: Int32 = (1 << 2)
if backgroundEmojiId != nil {
flagsReplies |= 1 << 0
}
return account.network.request(Api.functions.channels.updateColor(flags: flagsReplies, channel: inputChannel, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, MTRpcError> in
if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") {
return .single(nil)
} else {
return .fail(error)
}
}
|> mapError { error -> UpdatePeerNameColorAndEmojiError in
if error.errorDescription.hasPrefix("BOOSTS_REQUIRED") {
return .channelBoostRequired
}
return .generic
}
|> mapToSignal { repliesResult -> Signal<Void, UpdatePeerNameColorAndEmojiError> in
if let repliesResult = repliesResult {
account.stateManager.addUpdates(repliesResult)
}
return account.postbox.transaction { transaction -> Void in
if let repliesResult = repliesResult, let apiChat = apiUpdatesGroups(repliesResult).first {
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: [])
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
}
}
|> mapError { _ -> UpdatePeerNameColorAndEmojiError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> castError(UpdatePeerNameColorAndEmojiError.self)
|> switchToLatest
}
func _internal_updatePeerProfileColor(account: Account, peerId: EnginePeer.Id, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal<Void, UpdatePeerNameColorAndEmojiError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerNameColorAndEmojiError> in
if let peer = transaction.getPeer(peerId) {
if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
var flagsProfile: Int32 = (1 << 1)
if profileBackgroundEmojiId != nil {
flagsProfile |= 1 << 0
}
if profileColor != nil {
flagsProfile |= (1 << 2)
}
return account.network.request(Api.functions.channels.updateColor(flags: flagsProfile, channel: inputChannel, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.Updates?, MTRpcError> in
if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") {
return .single(nil)
} else {
return .fail(error)
}
}
|> mapError { error -> UpdatePeerNameColorAndEmojiError in
if error.errorDescription.hasPrefix("BOOSTS_REQUIRED") {
return .channelBoostRequired
}
return .generic
}
|> mapToSignal { profileResult -> Signal<Void, UpdatePeerNameColorAndEmojiError> in
if let profileResult = profileResult {
account.stateManager.addUpdates(profileResult)
}
return account.postbox.transaction { transaction -> Void in
if let profileResult = profileResult, let apiChat = apiUpdatesGroups(profileResult).first {
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: [])
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
}
}
|> mapError { _ -> UpdatePeerNameColorAndEmojiError in }
}
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
|> castError(UpdatePeerNameColorAndEmojiError.self)
|> switchToLatest
}
public enum UpdatePeerEmojiStatusError {
case generic
}
func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: Int64?, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in
let updatedStatus = fileId.flatMap {
PeerEmojiStatus(content: .emoji(fileId: $0), expirationDate: expirationDate)
}
if let peer = transaction.getPeer(peerId) as? TelegramChannel {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(updatedStatus)], update: { _, updated in updated })
}
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> castError(UpdatePeerEmojiStatusError.self)
|> mapToSignal { inputChannel -> Signal<Never, UpdatePeerEmojiStatusError> in
guard let inputChannel = inputChannel else {
return .fail(.generic)
}
let mappedStatus: Api.EmojiStatus
if let fileId = fileId {
var flags: Int32 = 0
if let _ = expirationDate {
flags |= (1 << 0)
}
mappedStatus = .emojiStatus(flags: flags, documentId: fileId, until: expirationDate)
} else {
mappedStatus = .emojiStatusEmpty
}
return account.network.request(Api.functions.channels.updateEmojiStatus(channel: inputChannel, emojiStatus: mappedStatus))
|> ignoreValues
|> `catch` { error -> Signal<Never, UpdatePeerEmojiStatusError> in
if error.errorDescription == "CHAT_NOT_MODIFIED" {
return .complete()
} else {
return .fail(.generic)
}
}
}
}
func _internal_updatePeerStarGiftStatus(account: Account, peerId: PeerId, starGift: StarGift.UniqueGift, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> {
var flags: Int32 = 0
if let _ = expirationDate {
flags |= (1 << 0)
}
var file: TelegramMediaFile?
var patternFile: TelegramMediaFile?
var innerColor: Int32?
var outerColor: Int32?
var patternColor: Int32?
var textColor: Int32?
for attribute in starGift.attributes {
switch attribute {
case let .model(_, fileValue, _):
file = fileValue
case let .pattern(_, patternFileValue, _):
patternFile = patternFileValue
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
innerColor = innerColorValue
outerColor = outerColorValue
patternColor = patternColorValue
textColor = textColorValue
default:
break
}
}
let apiEmojiStatus: Api.EmojiStatus
var emojiStatus: PeerEmojiStatus?
if let file, let patternFile, let innerColor, let outerColor, let patternColor, let textColor {
apiEmojiStatus = .inputEmojiStatusCollectible(flags: flags, collectibleId: starGift.id, until: expirationDate)
emojiStatus = PeerEmojiStatus(content: .starGift(id: starGift.id, fileId: file.fileId.id, title: starGift.title, slug: starGift.slug, patternFileId: patternFile.fileId.id, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor), expirationDate: expirationDate)
} else {
apiEmojiStatus = .emojiStatusEmpty
}
return account.postbox.transaction { transaction -> Api.InputChannel? in
if let peer = transaction.getPeer(peerId) as? TelegramChannel {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(emojiStatus)], update: { _, updated in updated })
}
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> castError(UpdatePeerEmojiStatusError.self)
|> mapToSignal { inputChannel -> Signal<Never, UpdatePeerEmojiStatusError> in
guard let inputChannel = inputChannel else {
return .fail(.generic)
}
return account.network.request(Api.functions.channels.updateEmojiStatus(channel: inputChannel, emojiStatus: apiEmojiStatus))
|> ignoreValues
|> `catch` { error -> Signal<Never, UpdatePeerEmojiStatusError> in
if error.errorDescription == "CHAT_NOT_MODIFIED" {
return .complete()
} else {
return .fail(.generic)
}
}
}
}