Telegram-iOS/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift
isaac d1aa0db537 Postbox -> TelegramEngine waves 46-93 (squashed)
Squash of 63 commits spanning waves 46-93 (plus interspersed docs commits)
of the gradual Postbox->TelegramEngine consumer-side migration.

Scope: 139 files changed, 2123 insertions(+), 452 deletions(-).

## Themes by wave-block

**Waves 46-58 — Peer field migrations + facade additions**
Foundational EnginePeer convenience init additions (PeerReference, RenderedPeer,
SelectivePrivacyPeer). Multiple `peer: Peer` field migrations across PeerInfo,
ChatList, and SettingsUI components.

**Waves 59-73 — peer field cascade + EnginePeer wrap drops**
Series of single- to two-file peer-field migrations; consumer-side wrap
removal (`EnginePeer(peer)` -> direct EnginePeer use); `as? TelegramUser`
cast conversion to `case let .user(...)` enum match. Wave 64: RenderedPeer
convenience init. Wave 68: SelectivePrivacyPeer convenience init.

**Waves 74-83 — controller-Node bridge cleanup + small migrations**
Wave-71 shadow-pattern cleanup at controller->Node bridges. Migrations of
ChatRecentActionsController.peer (74), PeerInfoMember (75), MentionChatInputPanelItem
(76), PassportUI SecureIdAuthController (77), AccountWithInfo + ShareController
(78), peerInputActivitiesPromise (79), InactiveChannel (80), BlockedPeers (81),
openHashtag resolveSignal (82), NotificationExceptionsList (83).

**Waves 84-90 — TelegramEngine.Resources facade migrations**
Per-method Shape-A/B sweeps converting `<ctx>.account.postbox.mediaBox.X(...)`
to `<ctx>.engine.resources.X(...)`. Wave 90 was a single-commit big sweep:
40 fetchedMediaResource sites in 25 files migrated to engine.resources.fetch
facade in one atomic pass with first-pass-clean build.

Methods covered: storeResourceData, completedResourcePath, cancelInteractiveResourceFetch,
resourceRangesStatus, resourceStatus, fetch (fetchedMediaResource).

**Waves 91-92 — additional type migrations**
Wave 91: ItemListWebsiteItem.peer + RecentSessionsController enum-case payload
+ openWebSession callback Peer? -> EnginePeer?.
Wave 92: ChatListController StateHolder.EntryContext status type
MediaResourceStatus -> EngineMediaResource.FetchStatus.

**Wave 93 — speculative `import Postbox` drop sweep**
Drop import from 7 wave-touched files where it became unused; restore in 5
files where bare PeerId/Message/MediaId/StoryId references escaped the
pre-flight regex. Includes one MediaId(...) -> EngineMedia.Id(...) swap in
InAppPurchaseManager to unlock its import drop.

## Build state

Final state at squash: clean Telegram/Telegram build at debug_sim_arm64.

## Persistent-state notes

- Pre-existing WIP unchanged across the squashed range:
  - build-system/bazel-rules/sourcekit-bazel-bsp submodule marker
  - Untracked: build-system/tulsi/, submodules/TgVoip/, third-party/libx264/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:48:15 +04:00

455 lines
20 KiB
Swift

import Foundation
import Postbox
import TelegramApi
public final class SelectivePrivacyPeer: Equatable {
public let peer: Peer
public let participantCount: Int32?
public init(peer: Peer, participantCount: Int32?) {
self.peer = peer
self.participantCount = participantCount
}
public convenience init(peer: EnginePeer, participantCount: Int32?) {
self.init(peer: peer._asPeer(), participantCount: participantCount)
}
public static func ==(lhs: SelectivePrivacyPeer, rhs: SelectivePrivacyPeer) -> Bool {
if !lhs.peer.isEqual(rhs.peer) {
return false
}
if lhs.participantCount != rhs.participantCount {
return false
}
return true
}
public var userCount: Int {
if let participantCount = self.participantCount {
return Int(participantCount)
} else if let group = self.peer as? TelegramGroup {
return group.participantCount
} else {
return 1
}
}
}
public enum SelectivePrivacySettings: Equatable {
case enableEveryone(disableFor: [PeerId: SelectivePrivacyPeer])
case enableContacts(enableFor: [PeerId: SelectivePrivacyPeer], disableFor: [PeerId: SelectivePrivacyPeer], enableForPremium: Bool, enableForBots: Bool)
case disableEveryone(enableFor: [PeerId: SelectivePrivacyPeer], enableForCloseFriends: Bool, enableForPremium: Bool, enableForBots: Bool)
public static func ==(lhs: SelectivePrivacySettings, rhs: SelectivePrivacySettings) -> Bool {
switch lhs {
case let .enableEveryone(disableFor):
if case .enableEveryone(disableFor) = rhs {
return true
} else {
return false
}
case let .enableContacts(enableFor, disableFor, enableForPremium, enableForBots):
if case .enableContacts(enableFor, disableFor, enableForPremium, enableForBots) = rhs {
return true
} else {
return false
}
case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium, enableForBots):
if case .disableEveryone(enableFor, enableForCloseFriends, enableForPremium, enableForBots) = rhs {
return true
} else {
return false
}
}
}
func withEnabledPeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettings {
switch self {
case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium, enableForBots):
return .disableEveryone(enableFor: enableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium, enableForBots: enableForBots)
case let .enableContacts(enableFor, disableFor, enableForPremium, enableForBots):
return .enableContacts(enableFor: enableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), disableFor: disableFor, enableForPremium: enableForPremium, enableForBots: enableForBots)
case .enableEveryone:
return self
}
}
func withDisabledPeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettings {
switch self {
case .disableEveryone:
return self
case let .enableContacts(enableFor, disableFor, enableForPremium, enableForBots):
return .enableContacts(enableFor: enableFor, disableFor: disableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), enableForPremium: enableForPremium, enableForBots: enableForBots)
case let .enableEveryone(disableFor):
return .enableEveryone(disableFor: disableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }))
}
}
func withEnableForPremium(_ enableForPremium: Bool) -> SelectivePrivacySettings {
switch self {
case let .disableEveryone(enableFor, enableForCloseFriends, _, enableForBots):
return .disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium, enableForBots: enableForBots)
case let .enableContacts(enableFor, disableFor, _, enableForBots):
return .enableContacts(enableFor: enableFor, disableFor: disableFor, enableForPremium: enableForPremium, enableForBots: enableForBots)
case .enableEveryone:
return self
}
}
func withEnableForCloseFriends(_ enableForCloseFriends: Bool) -> SelectivePrivacySettings {
switch self {
case let .disableEveryone(enableFor, _, enableForPremium, enableForBots):
return .disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium, enableForBots: enableForBots)
case .enableContacts:
return self
case .enableEveryone:
return self
}
}
func withEnableForBots(_ enableForBots: Bool?) -> SelectivePrivacySettings {
switch self {
case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium, _):
return .disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium, enableForBots: enableForBots == true)
case let .enableContacts(enableFor, disableFor, enableForPremium, _):
return .enableContacts(enableFor: enableFor, disableFor: disableFor, enableForPremium: enableForPremium, enableForBots: enableForBots == true)
case let .enableEveryone(disableFor):
return .enableEveryone(disableFor: disableFor)
}
}
}
public struct AccountPrivacySettings: Equatable {
public var presence: SelectivePrivacySettings
public var groupInvitations: SelectivePrivacySettings
public var voiceCalls: SelectivePrivacySettings
public var voiceCallsP2P: SelectivePrivacySettings
public var profilePhoto: SelectivePrivacySettings
public var forwards: SelectivePrivacySettings
public var phoneNumber: SelectivePrivacySettings
public var phoneDiscoveryEnabled: Bool
public var voiceMessages: SelectivePrivacySettings
public var bio: SelectivePrivacySettings
public var birthday: SelectivePrivacySettings
public var giftsAutoSave: SelectivePrivacySettings
public var noPaidMessages: SelectivePrivacySettings
public var savedMusic: SelectivePrivacySettings
public var globalSettings: GlobalPrivacySettings
public var accountRemovalTimeout: Int32
public var messageAutoremoveTimeout: Int32?
public init(presence: SelectivePrivacySettings, groupInvitations: SelectivePrivacySettings, voiceCalls: SelectivePrivacySettings, voiceCallsP2P: SelectivePrivacySettings, profilePhoto: SelectivePrivacySettings, forwards: SelectivePrivacySettings, phoneNumber: SelectivePrivacySettings, phoneDiscoveryEnabled: Bool, voiceMessages: SelectivePrivacySettings, bio: SelectivePrivacySettings, birthday: SelectivePrivacySettings, giftsAutoSave: SelectivePrivacySettings, noPaidMessages: SelectivePrivacySettings, savedMusic: SelectivePrivacySettings, globalSettings: GlobalPrivacySettings, accountRemovalTimeout: Int32, messageAutoremoveTimeout: Int32?) {
self.presence = presence
self.groupInvitations = groupInvitations
self.voiceCalls = voiceCalls
self.voiceCallsP2P = voiceCallsP2P
self.profilePhoto = profilePhoto
self.forwards = forwards
self.phoneNumber = phoneNumber
self.phoneDiscoveryEnabled = phoneDiscoveryEnabled
self.voiceMessages = voiceMessages
self.bio = bio
self.birthday = birthday
self.giftsAutoSave = giftsAutoSave
self.noPaidMessages = noPaidMessages
self.savedMusic = savedMusic
self.globalSettings = globalSettings
self.accountRemovalTimeout = accountRemovalTimeout
self.messageAutoremoveTimeout = messageAutoremoveTimeout
}
public static func ==(lhs: AccountPrivacySettings, rhs: AccountPrivacySettings) -> Bool {
if lhs.presence != rhs.presence {
return false
}
if lhs.groupInvitations != rhs.groupInvitations {
return false
}
if lhs.voiceCalls != rhs.voiceCalls {
return false
}
if lhs.voiceCallsP2P != rhs.voiceCallsP2P {
return false
}
if lhs.profilePhoto != rhs.profilePhoto {
return false
}
if lhs.forwards != rhs.forwards {
return false
}
if lhs.phoneNumber != rhs.phoneNumber {
return false
}
if lhs.phoneDiscoveryEnabled != rhs.phoneDiscoveryEnabled {
return false
}
if lhs.voiceMessages != rhs.voiceMessages {
return false
}
if lhs.bio != rhs.bio {
return false
}
if lhs.birthday != rhs.birthday {
return false
}
if lhs.giftsAutoSave != rhs.giftsAutoSave {
return false
}
if lhs.noPaidMessages != rhs.noPaidMessages {
return false
}
if lhs.savedMusic != rhs.savedMusic {
return false
}
if lhs.globalSettings != rhs.globalSettings {
return false
}
if lhs.accountRemovalTimeout != rhs.accountRemovalTimeout {
return false
}
if lhs.messageAutoremoveTimeout != rhs.messageAutoremoveTimeout {
return false
}
return true
}
}
extension SelectivePrivacySettings {
init(apiRules: [Api.PrivacyRule], peers: [PeerId: SelectivePrivacyPeer]) {
var current: SelectivePrivacySettings = .disableEveryone(enableFor: [:], enableForCloseFriends: false, enableForPremium: false, enableForBots: false)
var disableFor: [PeerId: SelectivePrivacyPeer] = [:]
var enableFor: [PeerId: SelectivePrivacyPeer] = [:]
var enableForCloseFriends: Bool = false
var enableForPremium: Bool = false
var enableForBots: Bool?
for rule in apiRules {
switch rule {
case .privacyValueAllowAll:
current = .enableEveryone(disableFor: [:])
case .privacyValueAllowContacts:
current = .enableContacts(enableFor: [:], disableFor: [:], enableForPremium: false, enableForBots: false)
case let .privacyValueAllowUsers(privacyValueAllowUsersData):
let users = privacyValueAllowUsersData.users
for id in users {
if let peer = peers[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))] {
enableFor[peer.peer.id] = peer
}
}
case .privacyValueDisallowAll:
break
case .privacyValueDisallowContacts:
break
case let .privacyValueDisallowUsers(privacyValueDisallowUsersData):
let users = privacyValueDisallowUsersData.users
for id in users {
if let peer = peers[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))] {
disableFor[peer.peer.id] = peer
}
}
case let .privacyValueAllowChatParticipants(privacyValueAllowChatParticipantsData):
let chats = privacyValueAllowChatParticipantsData.chats
for id in chats {
for possibleId in [PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))] {
if let peer = peers[possibleId] {
enableFor[peer.peer.id] = peer
}
}
}
case let .privacyValueDisallowChatParticipants(privacyValueDisallowChatParticipantsData):
let chats = privacyValueDisallowChatParticipantsData.chats
for id in chats {
for possibleId in [PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))] {
if let peer = peers[possibleId] {
disableFor[peer.peer.id] = peer
}
}
}
case .privacyValueAllowCloseFriends:
enableForCloseFriends = true
case .privacyValueAllowPremium:
enableForPremium = true
case .privacyValueAllowBots:
enableForBots = true
case .privacyValueDisallowBots:
break
}
}
self = current.withEnabledPeers(enableFor).withDisabledPeers(disableFor).withEnableForCloseFriends(enableForCloseFriends).withEnableForPremium(enableForPremium).withEnableForBots(enableForBots)
}
}
public struct GlobalMessageAutoremoveTimeoutSettings: Equatable, Codable {
public static var `default` = GlobalMessageAutoremoveTimeoutSettings(
messageAutoremoveTimeout: nil
)
public var messageAutoremoveTimeout: Int32?
public init(messageAutoremoveTimeout: Int32?) {
self.messageAutoremoveTimeout = messageAutoremoveTimeout
}
}
func updateGlobalMessageAutoremoveTimeoutSettings(transaction: Transaction, _ f: (GlobalMessageAutoremoveTimeoutSettings) -> GlobalMessageAutoremoveTimeoutSettings) {
transaction.updatePreferencesEntry(key: PreferencesKeys.globalMessageAutoremoveTimeoutSettings, { current in
let previous = current?.get(GlobalMessageAutoremoveTimeoutSettings.self) ?? GlobalMessageAutoremoveTimeoutSettings.default
let updated = f(previous)
return PreferencesEntry(updated)
})
}
public struct TelegramDisallowedGifts: OptionSet, Codable {
public var rawValue: Int32
public init() {
self.rawValue = 0
}
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let unlimited = TelegramDisallowedGifts(rawValue: 1 << 0)
public static let limited = TelegramDisallowedGifts(rawValue: 1 << 1)
public static let unique = TelegramDisallowedGifts(rawValue: 1 << 2)
public static let premium = TelegramDisallowedGifts(rawValue: 1 << 3)
public static let channel = TelegramDisallowedGifts(rawValue: 1 << 4)
public static let All: TelegramDisallowedGifts = [
.unlimited,
.limited,
.unique,
.premium
]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
let value = try? container.decode(Int32.self, forKey: "v")
self = TelegramDisallowedGifts(rawValue: value ?? 0)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.rawValue, forKey: "v")
}
}
extension TelegramDisallowedGifts {
init(apiDisallowedGifts: Api.DisallowedGiftsSettings?) {
var disallowedGifts: TelegramDisallowedGifts = []
switch apiDisallowedGifts {
case let .disallowedGiftsSettings(disallowedGiftsSettingsData):
let giftFlags = disallowedGiftsSettingsData.flags
if (giftFlags & (1 << 0)) != 0 {
disallowedGifts.insert(.unlimited)
}
if (giftFlags & (1 << 1)) != 0 {
disallowedGifts.insert(.limited)
}
if (giftFlags & (1 << 2)) != 0 {
disallowedGifts.insert(.unique)
}
if (giftFlags & (1 << 3)) != 0 {
disallowedGifts.insert(.premium)
}
if (giftFlags & (1 << 4)) != 0 {
disallowedGifts.insert(.channel)
}
default:
break
}
self = disallowedGifts
}
}
public struct GlobalPrivacySettings: Equatable, Codable {
public enum NonContactChatsPrivacy: Equatable, Codable {
case everybody
case requirePremium
case paidMessages(StarsAmount)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
switch (try? container.decode(Int32.self, forKey: "t")) ?? 0 {
case 0:
self = .everybody
case 1:
self = .requirePremium
case 2:
self = .paidMessages(StarsAmount(value: try container.decodeIfPresent(Int64.self, forKey: "stars") ?? 0, nanos: 0))
default:
assertionFailure()
self = .everybody
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
switch self {
case .everybody:
try container.encode(0 as Int32, forKey: "t")
case .requirePremium:
try container.encode(1 as Int32, forKey: "t")
case let .paidMessages(amount):
try container.encode(2 as Int32, forKey: "t")
try container.encode(amount.value, forKey: "stars")
}
}
}
public static var `default` = GlobalPrivacySettings(
automaticallyArchiveAndMuteNonContacts: false,
keepArchivedUnmuted: true,
keepArchivedFolders: true,
hideReadTime: false,
nonContactChatsPrivacy: .everybody,
disallowedGifts: [],
displayGiftButton: false
)
public var automaticallyArchiveAndMuteNonContacts: Bool
public var keepArchivedUnmuted: Bool
public var keepArchivedFolders: Bool
public var hideReadTime: Bool
public var nonContactChatsPrivacy: NonContactChatsPrivacy
public var disallowedGifts: TelegramDisallowedGifts
public var displayGiftButton: Bool
public init(
automaticallyArchiveAndMuteNonContacts: Bool,
keepArchivedUnmuted: Bool,
keepArchivedFolders: Bool,
hideReadTime: Bool,
nonContactChatsPrivacy: NonContactChatsPrivacy,
disallowedGifts: TelegramDisallowedGifts,
displayGiftButton: Bool
) {
self.automaticallyArchiveAndMuteNonContacts = automaticallyArchiveAndMuteNonContacts
self.keepArchivedUnmuted = keepArchivedUnmuted
self.keepArchivedFolders = keepArchivedFolders
self.hideReadTime = hideReadTime
self.nonContactChatsPrivacy = nonContactChatsPrivacy
self.disallowedGifts = disallowedGifts
self.displayGiftButton = displayGiftButton
}
}
func fetchGlobalPrivacySettings(transaction: Transaction) -> GlobalPrivacySettings {
return transaction.getPreferencesEntry(key: PreferencesKeys.globalPrivacySettings)?.get(GlobalPrivacySettings.self) ?? GlobalPrivacySettings.default
}
func updateGlobalPrivacySettings(transaction: Transaction, _ f: (GlobalPrivacySettings) -> GlobalPrivacySettings) {
transaction.updatePreferencesEntry(key: PreferencesKeys.globalPrivacySettings, { current in
let previous = current?.get(GlobalPrivacySettings.self) ?? GlobalPrivacySettings.default
let updated = f(previous)
return PreferencesEntry(updated)
})
}