import Foundation import Postbox import TelegramApi import SwiftSignalKit func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { return postbox.combinedView(keys: [.basicPeer(rawPeerId)]) |> mapToSignal { views -> Signal 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 in return postbox.transaction { transaction -> Signal 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 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 { return postbox.combinedView(keys: [.basicPeer(rawPeerId)]) |> mapToSignal { views -> Signal 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 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 in if let inputUser = inputUser { let editableBotInfo: Signal 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 in return .single(nil) } |> mapToSignal { result -> Signal 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 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 = .single(nil) if rawPeerId == accountPeerId { additionalConnectedBots = network.request(Api.functions.account.getConnectedBots()) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } } return combineLatest( network.request(Api.functions.users.getFullUser(id: inputUser)) |> retryRequest, editableBotInfo, botPreview, additionalConnectedBots ) |> mapToSignal { result, editableBotInfo, botPreview, additionalConnectedBots -> Signal 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(channelData) = chat { let participantsCount = channelData.participantsCount 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 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(chatFullData): let (notifySettings) = (chatFullData.notifySettings) transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) case .channelFull: break } switch fullChat { case let .chatFull(chatFullData): let (chatFullFlags, chatFullAbout, chatFullParticipants, chatFullChatPhoto, chatFullExportedInvite, chatFullBotInfo, chatFullPinnedMsgId, chatFullCall, chatTtlPeriod, chatFullGroupcallDefaultJoinAs, chatFullThemeEmoticon, chatFullRequestsPending, allowedReactions, reactionsLimit) = (chatFullData.flags, chatFullData.about, chatFullData.participants, chatFullData.chatPhoto, chatFullData.exportedInvite, chatFullData.botInfo, chatFullData.pinnedMsgId, chatFullData.call, chatFullData.ttlPeriod, chatFullData.groupcallDefaultJoinAs, chatFullData.themeEmoticon, chatFullData.requestsPending, chatFullData.availableReactions, chatFullData.reactionsLimit) var botInfos: [CachedPeerBotInfo] = [] for botInfo in chatFullBotInfo ?? [] { switch botInfo { case let .botInfo(botInfoData): let (_, userId, _, _, _, _, _, _, _, _) = (botInfoData.flags, botInfoData.userId, botInfoData.description, botInfoData.descriptionPhoto, botInfoData.descriptionDocument, botInfoData.commands, botInfoData.menuButton, botInfoData.privacyPolicyUrl, botInfoData.appSettings, botInfoData.verifierSettings) 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(chatReactionsSomeData): let reactions = chatReactionsSomeData.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 in if error.errorDescription == "CHANNEL_PRIVATE" { return .single(nil) } return .single(nil) } let participantSignal: Signal 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 in return .single(nil) } } return combineLatest(fullChannelSignal, participantSignal) |> mapToSignal { result, participantResult -> Signal in return postbox.transaction { transaction -> Bool in if let result = result { switch result { case let .chatFull(fullChat, chats, users): switch fullChat { case let .channelFull(channelFullData): let notifySettings = channelFullData.notifySettings transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) case .chatFull: break } switch fullChat { case let .channelFull(channelFullData): let (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) = (channelFullData.flags, channelFullData.flags2, channelFullData.about, channelFullData.participantsCount, channelFullData.adminsCount, channelFullData.kickedCount, channelFullData.bannedCount, channelFullData.chatPhoto, channelFullData.exportedInvite, channelFullData.botInfo, channelFullData.migratedFromChatId, channelFullData.migratedFromMaxId, channelFullData.pinnedMsgId, channelFullData.stickerset, channelFullData.availableMinId, channelFullData.linkedChatId, channelFullData.location, channelFullData.slowmodeSeconds, channelFullData.slowmodeNextSendDate, channelFullData.statsDc, channelFullData.call, channelFullData.ttlPeriod, channelFullData.pendingSuggestions, channelFullData.groupcallDefaultJoinAs, channelFullData.themeEmoticon, channelFullData.requestsPending, channelFullData.defaultSendAs, channelFullData.availableReactions, channelFullData.reactionsLimit, channelFullData.wallpaper, channelFullData.boostsApplied, channelFullData.boostsUnrestrict, channelFullData.emojiset, channelFullData.botVerification, channelFullData.stargiftsCount, channelFullData.sendPaidMessagesStars, channelFullData.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(botInfoData): let (_, userId, _, _, _, _, _, _, _, _) = (botInfoData.flags, botInfoData.userId, botInfoData.description, botInfoData.descriptionPhoto, botInfoData.descriptionDocument, botInfoData.commands, botInfoData.menuButton, botInfoData.privacyPolicyUrl, botInfoData.appSettings, botInfoData.verifierSettings) 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(channelParticipantSelfData): let (flags, inviterId, invitedDate) = (channelParticipantSelfData.flags, channelParticipantSelfData.inviterId, channelParticipantSelfData.date) 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(chatReactionsSomeData): let reactions = chatReactionsSomeData.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 { return network.request(Api.functions.bots.getPreviewInfo(bot: inputUser, langCode: language ?? "")) |> map(Optional.init) |> `catch` { _ -> Signal 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(botPreviewMediaData): let (date, media) = (botPreviewMediaData.date, botPreviewMediaData.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 { 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(botPreviewMediaData): let (date, media) = (botPreviewMediaData.date, botPreviewMediaData.media) let value = textMediaAndExpirationTimerFromApiMedia(media, peerId) if let media = value.media { return CachedUserData.BotPreview.Item(media: media, timestamp: date) } else { return nil } } }, alternativeLanguageCodes: [] ) } }