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,41 @@
import Foundation
import Postbox
public final class AdMessageAttribute: MessageAttribute {
public enum MessageType {
case sponsored
case recommended
}
public let opaqueId: Data
public let messageType: MessageType
public let url: String
public let buttonText: String
public let sponsorInfo: String?
public let additionalInfo: String?
public let canReport: Bool
public let hasContentMedia: Bool
public let minDisplayDuration: Int32?
public let maxDisplayDuration: Int32?
public init(opaqueId: Data, messageType: MessageType, url: String, buttonText: String, sponsorInfo: String?, additionalInfo: String?, canReport: Bool, hasContentMedia: Bool, minDisplayDuration: Int32?, maxDisplayDuration: Int32?) {
self.opaqueId = opaqueId
self.messageType = messageType
self.url = url
self.buttonText = buttonText
self.sponsorInfo = sponsorInfo
self.additionalInfo = additionalInfo
self.canReport = canReport
self.hasContentMedia = hasContentMedia
self.minDisplayDuration = minDisplayDuration
self.maxDisplayDuration = maxDisplayDuration
}
public init(decoder: PostboxDecoder) {
preconditionFailure()
}
public func encode(_ encoder: PostboxEncoder) {
preconditionFailure()
}
}
@@ -0,0 +1,344 @@
import Foundation
import Postbox
import TelegramApi
func imageRepresentationsForApiChatPhoto(_ photo: Api.ChatPhoto) -> [TelegramMediaImageRepresentation] {
var representations: [TelegramMediaImageRepresentation] = []
switch photo {
case let .chatPhoto(flags, photoId, strippedThumb, dcId):
let hasVideo = (flags & (1 << 0)) != 0
let smallResource: TelegramMediaResource
let fullSizeResource: TelegramMediaResource
smallResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: photoId, sizeSpec: .small, volumeId: nil, localId: nil)
fullSizeResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: photoId, sizeSpec: .fullSize, volumeId: nil, localId: nil)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: smallResource, progressiveSizes: [], immediateThumbnailData: strippedThumb?.makeData(), hasVideo: hasVideo, isPersonal: false))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: fullSizeResource, progressiveSizes: [], immediateThumbnailData: strippedThumb?.makeData(), hasVideo: hasVideo, isPersonal: false))
case .chatPhotoEmpty:
break
}
return representations
}
func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
switch chat {
case let .chat(flags, id, title, photo, participantsCount, date, version, migratedTo, adminRights, defaultBannedRights):
let left = (flags & ((1 << 1) | (1 << 2))) != 0
var migrationReference: TelegramGroupToChannelMigrationReference?
if let migratedTo = migratedTo {
switch migratedTo {
case let .inputChannel(channelId, accessHash):
migrationReference = TelegramGroupToChannelMigrationReference(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), accessHash: accessHash)
case .inputChannelEmpty:
break
case .inputChannelFromMessage:
break
}
}
var groupFlags = TelegramGroupFlags()
var role: TelegramGroupRole = .member
if (flags & (1 << 0)) != 0 {
role = .creator(rank: nil)
} else if let adminRights = adminRights {
role = .admin(TelegramChatAdminRights(apiAdminRights: adminRights) ?? TelegramChatAdminRights(rights: []), rank: nil)
}
if (flags & (1 << 5)) != 0 {
groupFlags.insert(.deactivated)
}
if (flags & Int32(1 << 23)) != 0 {
groupFlags.insert(.hasVoiceChat)
}
if (flags & Int32(1 << 24)) != 0 {
groupFlags.insert(.hasActiveVoiceChat)
}
if (flags & Int32(1 << 25)) != 0 {
groupFlags.insert(.copyProtectionEnabled)
}
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: imageRepresentationsForApiChatPhoto(photo), participantCount: Int(participantsCount), role: role, membership: left ? .Left : .Member, flags: groupFlags, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init(apiBannedRights:)), migrationReference: migrationReference, creationDate: date, version: Int(version))
case let .chatEmpty(id):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .chatForbidden(id, title):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel, subscriptionUntilDate, verificationIconFileId, sendPaidMessageStars, linkedMonoforumId):
let isMin = (flags & (1 << 12)) != 0
let participationStatus: TelegramChannelParticipationStatus
if (flags & Int32(1 << 1)) != 0 {
participationStatus = .kicked
} else if (flags & Int32(1 << 2)) != 0 {
participationStatus = .left
} else {
participationStatus = .member
}
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
var infoFlags = TelegramChannelGroupFlags()
if (flags & Int32(1 << 22)) != 0 {
infoFlags.insert(.slowModeEnabled)
}
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
} else {
var infoFlags = TelegramChannelBroadcastFlags()
if (flags & Int32(1 << 11)) != 0 {
infoFlags.insert(.messagesShouldHaveSignatures)
}
if (flags2 & Int32(1 << 12)) != 0 {
infoFlags.insert(.messagesShouldHaveProfiles)
}
if (flags & Int32(1 << 20)) != 0 {
infoFlags.insert(.hasDiscussionGroup)
}
if (flags2 & Int32(1 << 16)) != 0 {
infoFlags.insert(.hasMonoforum)
}
info = .broadcast(TelegramChannelBroadcastInfo(flags: infoFlags))
}
var channelFlags = TelegramChannelFlags()
if (flags & Int32(1 << 0)) != 0 {
channelFlags.insert(.isCreator)
}
if (flags & Int32(1 << 7)) != 0 {
channelFlags.insert(.isVerified)
}
if (flags & Int32(1 << 19)) != 0 {
channelFlags.insert(.isScam)
}
if (flags & Int32(1 << 21)) != 0 {
channelFlags.insert(.hasGeo)
}
if (flags & Int32(1 << 23)) != 0 {
channelFlags.insert(.hasVoiceChat)
}
if (flags & Int32(1 << 24)) != 0 {
channelFlags.insert(.hasActiveVoiceChat)
}
if (flags & Int32(1 << 25)) != 0 {
channelFlags.insert(.isFake)
}
if (flags & Int32(1 << 26)) != 0 {
channelFlags.insert(.isGigagroup)
}
if (flags & Int32(1 << 27)) != 0 {
channelFlags.insert(.copyProtectionEnabled)
}
if (flags & Int32(1 << 28)) != 0 {
channelFlags.insert(.joinToSend)
}
if (flags & Int32(1 << 29)) != 0 {
channelFlags.insert(.requestToJoin)
}
if (flags & Int32(1 << 30)) != 0 {
channelFlags.insert(.isForum)
}
if (flags2 & Int32(1 << 15)) != 0 {
channelFlags.insert(.autoTranslateEnabled)
}
if (flags2 & Int32(1 << 17)) != 0 {
channelFlags.insert(.isMonoforum)
}
if (flags2 & Int32(1 << 19)) != 0 {
channelFlags.insert(.displayForumAsTabs)
}
var storiesHidden: Bool?
if flags2 & (1 << 2) == 0 { // stories_hidden_min
if flags2 & (1 << 1) != 0 {
storiesHidden = true
} else if !isMin {
storiesHidden = false
}
}
let restrictionInfo: PeerAccessRestrictionInfo?
if let restrictionReason = restrictionReason {
restrictionInfo = PeerAccessRestrictionInfo(apiReasons: restrictionReason)
} else {
restrictionInfo = nil
}
let accessHashValue = accessHash.flatMap { value -> TelegramPeerAccessHash in
if isMin {
return .genericPublic(value)
} else {
return .personal(value)
}
}
var nameColorIndex: Int32?
var backgroundEmojiId: Int64?
if let color = color {
switch color {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible:
break
case .inputPeerColorCollectible:
break
}
}
var profileColorIndex: Int32?
var profileBackgroundEmojiId: Int64?
if let profileColor = profileColor {
switch profileColor {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
default:
break
}
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), approximateBoostLevel: boostLevel, subscriptionUntilDate: subscriptionUntilDate, verificationIconFileId: verificationIconFileId, sendPaidMessageStars: sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) }, linkedMonoforumId: linkedMonoforumId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) })
case let .channelForbidden(flags, id, accessHash, title, untilDate):
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
info = .group(TelegramChannelGroupInfo(flags: []))
} else {
info = .broadcast(TelegramChannelBroadcastInfo(flags: []))
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)
}
}
func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
switch rhs {
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
return parseTelegramGroupOrChannel(chat: rhs)
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames, _, color, profileColor, emojiStatus, boostLevel, subscriptionUntilDate, verificationIconFileId, sendPaidMessageStars, linkedMonoforumIdValue):
let isMin = (flags & (1 << 12)) != 0
if accessHash != nil && !isMin {
return parseTelegramGroupOrChannel(chat: rhs)
} else if let lhs = lhs as? TelegramChannel {
var channelFlags = lhs.flags
if (flags & Int32(1 << 7)) != 0 {
channelFlags.insert(.isVerified)
} else {
let _ = channelFlags.remove(.isVerified)
}
if (flags & Int32(1 << 23)) != 0 {
channelFlags.insert(.hasVoiceChat)
} else {
let _ = channelFlags.remove(.hasVoiceChat)
}
if (flags & Int32(1 << 24)) != 0 {
channelFlags.insert(.hasActiveVoiceChat)
} else {
let _ = channelFlags.remove(.hasActiveVoiceChat)
}
var info = lhs.info
switch info {
case .broadcast:
break
case .group:
var infoFlags = TelegramChannelGroupFlags()
if (flags & Int32(1 << 22)) != 0 {
infoFlags.insert(.slowModeEnabled)
}
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
}
var storiesHidden: Bool? = lhs.storiesHidden
if flags2 & (1 << 2) == 0 { // stories_hidden_min
if flags2 & (1 << 1) != 0 {
storiesHidden = true
} else if !isMin {
storiesHidden = false
}
}
var nameColorIndex: Int32?
var backgroundEmojiId: Int64?
if let color = color {
switch color {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible:
break
case .inputPeerColorCollectible:
break
}
}
var profileColorIndex: Int32?
var profileBackgroundEmojiId: Int64?
if let profileColor = profileColor {
switch profileColor {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
default:
break
}
}
let parsedEmojiStatus = emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:))
let linkedMonoforumId = linkedMonoforumIdValue.flatMap({ PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }) ?? lhs.linkedMonoforumId
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, emojiStatus: parsedEmojiStatus, approximateBoostLevel: boostLevel, subscriptionUntilDate: subscriptionUntilDate, verificationIconFileId: verificationIconFileId, sendPaidMessageStars: sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) } ?? lhs.sendPaidMessageStars, linkedMonoforumId: linkedMonoforumId)
} else {
return parseTelegramGroupOrChannel(chat: rhs)
}
}
}
func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChannel {
guard let lhs = lhs else {
return rhs
}
if case .personal? = rhs.accessHash {
let storiesHidden: Bool? = rhs.storiesHidden ?? lhs.storiesHidden
return rhs.withUpdatedStoriesHidden(storiesHidden)
}
var channelFlags = lhs.flags
if rhs.flags.contains(.isGigagroup) {
channelFlags.insert(.isGigagroup)
}
if rhs.flags.contains(.isVerified) {
channelFlags.insert(.isVerified)
} else {
let _ = channelFlags.remove(.isVerified)
}
if rhs.flags.contains(.hasVoiceChat) {
channelFlags.insert(.hasVoiceChat)
} else {
let _ = channelFlags.remove(.hasVoiceChat)
}
if rhs.flags.contains(.hasActiveVoiceChat) {
channelFlags.insert(.hasActiveVoiceChat)
} else {
let _ = channelFlags.remove(.hasActiveVoiceChat)
}
var info = lhs.info
switch info {
case .broadcast:
break
case .group:
let infoFlags = TelegramChannelGroupFlags()
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
}
let accessHash: TelegramPeerAccessHash?
if let rhsAccessHashValue = lhs.accessHash, case .personal = rhsAccessHashValue {
accessHash = rhsAccessHashValue
} else {
accessHash = rhs.accessHash ?? lhs.accessHash
}
let storiesHidden: Bool? = rhs.storiesHidden ?? lhs.storiesHidden
let linkedMonoforumId = rhs.linkedMonoforumId ?? lhs.linkedMonoforumId
return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames, storiesHidden: storiesHidden, nameColor: rhs.nameColor, backgroundEmojiId: rhs.backgroundEmojiId, profileColor: rhs.profileColor, profileBackgroundEmojiId: rhs.profileBackgroundEmojiId, emojiStatus: rhs.emojiStatus, approximateBoostLevel: rhs.approximateBoostLevel, subscriptionUntilDate: rhs.subscriptionUntilDate, verificationIconFileId: rhs.verificationIconFileId, sendPaidMessageStars: rhs.sendPaidMessageStars, linkedMonoforumId: linkedMonoforumId)
}
@@ -0,0 +1,111 @@
import Foundation
import Postbox
import TelegramApi
public extension PeerReference {
var id: PeerId {
switch self {
case let .user(id, _):
return PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))
case let .group(id):
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
case let .channel(id, _):
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
}
}
}
extension PeerReference {
var inputPeer: Api.InputPeer {
switch self {
case let .user(id, accessHash):
return .inputPeerUser(userId: id, accessHash: accessHash)
case let .group(id):
return .inputPeerChat(chatId: id)
case let .channel(id, accessHash):
return .inputPeerChannel(channelId: id, accessHash: accessHash)
}
}
var inputUser: Api.InputUser? {
if case let .user(id, accessHash) = self {
return .inputUser(userId: id, accessHash: accessHash)
} else {
return nil
}
}
var inputChannel: Api.InputChannel? {
if case let .channel(id, accessHash) = self {
return .inputChannel(channelId: id, accessHash: accessHash)
} else {
return nil
}
}
}
func forceApiInputPeer(_ peer: Peer) -> Api.InputPeer? {
switch peer {
case let user as TelegramUser:
return Api.InputPeer.inputPeerUser(userId: user.id.id._internalGetInt64Value(), accessHash: user.accessHash?.value ?? 0)
case let group as TelegramGroup:
return Api.InputPeer.inputPeerChat(chatId: group.id.id._internalGetInt64Value())
case let channel as TelegramChannel:
if let accessHash = channel.accessHash {
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id._internalGetInt64Value(), accessHash: accessHash.value)
} else {
return nil
}
default:
return nil
}
}
func apiInputPeer(_ peer: Peer) -> Api.InputPeer? {
switch peer {
case let user as TelegramUser where user.accessHash != nil:
return Api.InputPeer.inputPeerUser(userId: user.id.id._internalGetInt64Value(), accessHash: user.accessHash!.value)
case let group as TelegramGroup:
return Api.InputPeer.inputPeerChat(chatId: group.id.id._internalGetInt64Value())
case let channel as TelegramChannel:
if let accessHash = channel.accessHash {
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id._internalGetInt64Value(), accessHash: accessHash.value)
} else {
return nil
}
default:
return nil
}
}
func apiInputPeerOrSelf(_ peer: Peer, accountPeerId: PeerId) -> Api.InputPeer? {
if peer.id == accountPeerId {
return .inputPeerSelf
}
return apiInputPeer(peer)
}
func apiInputChannel(_ peer: Peer) -> Api.InputChannel? {
if let channel = peer as? TelegramChannel, let accessHash = channel.accessHash {
return Api.InputChannel.inputChannel(channelId: channel.id.id._internalGetInt64Value(), accessHash: accessHash.value)
} else {
return nil
}
}
func apiInputUser(_ peer: Peer) -> Api.InputUser? {
if let user = peer as? TelegramUser, let accessHash = user.accessHash {
return Api.InputUser.inputUser(userId: user.id.id._internalGetInt64Value(), accessHash: accessHash.value)
} else {
return nil
}
}
func apiInputSecretChat(_ peer: Peer) -> Api.InputEncryptedChat? {
if let chat = peer as? TelegramSecretChat {
return Api.InputEncryptedChat.inputEncryptedChat(chatId: Int32(peer.id.id._internalGetInt64Value()), accessHash: chat.accessHash)
} else {
return nil
}
}
@@ -0,0 +1,67 @@
import Foundation
import Postbox
import TelegramApi
extension BotMenuButton {
init(apiBotMenuButton: Api.BotMenuButton) {
switch apiBotMenuButton {
case .botMenuButtonCommands, .botMenuButtonDefault:
self = .commands
case let .botMenuButton(text, url):
self = .webView(text: text, url: url)
}
}
}
extension BotAppSettings {
init(apiBotAppSettings: Api.BotAppSettings) {
switch apiBotAppSettings {
case let .botAppSettings(_, placeholder, backgroundColor, backgroundDarkColor, headerColor, headerDarkColor):
self.init(
placeholderData: placeholder.flatMap { $0.makeData() },
backgroundColor: backgroundColor,
backgroundDarkColor: backgroundDarkColor,
headerColor: headerColor,
headerDarkColor: headerDarkColor
)
}
}
}
extension BotVerifierSettings {
init(apiBotVerifierSettings: Api.BotVerifierSettings) {
switch apiBotVerifierSettings {
case let .botVerifierSettings(flags, iconFileId, companyName, customDescription):
self.init(
iconFileId: iconFileId,
companyName: companyName,
customDescription: customDescription,
canModifyDescription: (flags & (1 << 1)) != 0
)
}
}
}
extension BotInfo {
convenience init(apiBotInfo: Api.BotInfo) {
switch apiBotInfo {
case let .botInfo(_, _, description, descriptionPhoto, descriptionDocument, apiCommands, apiMenuButton, privacyPolicyUrl, appSettings, verifierSettings):
let photo: TelegramMediaImage? = descriptionPhoto.flatMap(telegramMediaImageFromApiPhoto)
let video: TelegramMediaFile? = descriptionDocument.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
var commands: [BotCommand] = []
if let apiCommands = apiCommands {
commands = apiCommands.map { command in
switch command {
case let .botCommand(command, description):
return BotCommand(text: command, description: description)
}
}
}
var menuButton: BotMenuButton = .commands
if let apiMenuButton = apiMenuButton {
menuButton = BotMenuButton(apiBotMenuButton: apiMenuButton)
}
self.init(description: description ?? "", photo: photo, video: video, commands: commands, menuButton: menuButton, privacyPolicyUrl: privacyPolicyUrl, appSettings: appSettings.flatMap { BotAppSettings(apiBotAppSettings: $0) }, verifierSettings: verifierSettings.flatMap { BotVerifierSettings(apiBotVerifierSettings: $0) })
}
}
}
@@ -0,0 +1,230 @@
import Foundation
import Postbox
import TelegramApi
private enum ChannelParticipantValue: Int32 {
case member = 0
case creator = 1
case editor = 2
case moderator = 3
}
public struct ChannelParticipantAdminInfo: PostboxCoding, Equatable {
public let rights: TelegramChatAdminRights
public let promotedBy: PeerId
public let canBeEditedByAccountPeer: Bool
public init(rights: TelegramChatAdminRights, promotedBy: PeerId, canBeEditedByAccountPeer: Bool) {
self.rights = rights
self.promotedBy = promotedBy
self.canBeEditedByAccountPeer = canBeEditedByAccountPeer
}
public init(decoder: PostboxDecoder) {
self.rights = decoder.decodeObjectForKey("r", decoder: { TelegramChatAdminRights(decoder: $0) }) as! TelegramChatAdminRights
self.promotedBy = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
self.canBeEditedByAccountPeer = decoder.decodeInt32ForKey("e", orElse: 0) != 0
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.rights, forKey: "r")
encoder.encodeInt64(self.promotedBy.toInt64(), forKey: "p")
encoder.encodeInt32(self.canBeEditedByAccountPeer ? 1 : 0, forKey: "e")
}
public static func ==(lhs: ChannelParticipantAdminInfo, rhs: ChannelParticipantAdminInfo) -> Bool {
return lhs.rights == rhs.rights && lhs.promotedBy == rhs.promotedBy && lhs.canBeEditedByAccountPeer == rhs.canBeEditedByAccountPeer
}
}
public struct ChannelParticipantBannedInfo: PostboxCoding, Equatable {
public let rights: TelegramChatBannedRights
public let restrictedBy: PeerId
public let timestamp: Int32
public let isMember: Bool
public init(rights: TelegramChatBannedRights, restrictedBy: PeerId, timestamp: Int32, isMember: Bool) {
self.rights = rights
self.restrictedBy = restrictedBy
self.timestamp = timestamp
self.isMember = isMember
}
public init(decoder: PostboxDecoder) {
self.rights = decoder.decodeObjectForKey("r", decoder: { TelegramChatBannedRights(decoder: $0) }) as! TelegramChatBannedRights
self.restrictedBy = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0)
self.isMember = decoder.decodeInt32ForKey("m", orElse: 0) != 0
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.rights, forKey: "r")
encoder.encodeInt64(self.restrictedBy.toInt64(), forKey: "p")
encoder.encodeInt32(self.timestamp, forKey: "t")
encoder.encodeInt32(self.isMember ? 1 : 0, forKey: "m")
}
public static func ==(lhs: ChannelParticipantBannedInfo, rhs: ChannelParticipantBannedInfo) -> Bool {
return lhs.rights == rhs.rights && lhs.restrictedBy == rhs.restrictedBy && lhs.timestamp == rhs.timestamp && lhs.isMember == rhs.isMember
}
}
public enum ChannelParticipant: PostboxCoding, Equatable {
case creator(id: PeerId, adminInfo: ChannelParticipantAdminInfo?, rank: String?)
case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?, rank: String?, subscriptionUntilDate: Int32?)
public var peerId: PeerId {
switch self {
case let .creator(id, _, _):
return id
case let .member(id, _, _, _, _, _):
return id
}
}
public var rank: String? {
switch self {
case let .creator(_, _, rank):
return rank
case let .member(_, _, _, _, rank, _):
return rank
}
}
public static func ==(lhs: ChannelParticipant, rhs: ChannelParticipant) -> Bool {
switch lhs {
case let .member(lhsId, lhsInvitedAt, lhsAdminInfo, lhsBanInfo, lhsRank, lhsSubscriptionUntilDate):
if case let .member(rhsId, rhsInvitedAt, rhsAdminInfo, rhsBanInfo, rhsRank, rhsSubscriptionUntilDate) = rhs {
if lhsId != rhsId {
return false
}
if lhsInvitedAt != rhsInvitedAt {
return false
}
if lhsAdminInfo != rhsAdminInfo {
return false
}
if lhsBanInfo != rhsBanInfo {
return false
}
if lhsRank != rhsRank {
return false
}
if lhsSubscriptionUntilDate != rhsSubscriptionUntilDate {
return false
}
return true
} else {
return false
}
case let .creator(id, adminInfo, rank):
if case .creator(id, adminInfo, rank) = rhs {
return true
} else {
return false
}
}
}
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("r", orElse: 0) {
case ChannelParticipantValue.member.rawValue:
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, banInfo: decoder.decodeObjectForKey("bi", decoder: { ChannelParticipantBannedInfo(decoder: $0) }) as? ChannelParticipantBannedInfo, rank: decoder.decodeOptionalStringForKey("rank"), subscriptionUntilDate: decoder.decodeOptionalInt32ForKey("subscriptionUntilDate"))
case ChannelParticipantValue.creator.rawValue:
self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, rank: decoder.decodeOptionalStringForKey("rank"))
default:
self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil)
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .member(id, invitedAt, adminInfo, banInfo, rank, subscriptionUntilDate):
encoder.encodeInt32(ChannelParticipantValue.member.rawValue, forKey: "r")
encoder.encodeInt64(id.toInt64(), forKey: "i")
encoder.encodeInt32(invitedAt, forKey: "t")
if let adminInfo = adminInfo {
encoder.encodeObject(adminInfo, forKey: "ai")
} else {
encoder.encodeNil(forKey: "ai")
}
if let banInfo = banInfo {
encoder.encodeObject(banInfo, forKey: "bi")
} else {
encoder.encodeNil(forKey: "bi")
}
if let rank = rank {
encoder.encodeString(rank, forKey: "rank")
} else {
encoder.encodeNil(forKey: "rank")
}
if let subscriptionUntilDate = subscriptionUntilDate {
encoder.encodeInt32(subscriptionUntilDate, forKey: "subscriptionUntilDate")
} else {
encoder.encodeNil(forKey: "subscriptionUntilDate")
}
case let .creator(id, adminInfo, rank):
encoder.encodeInt32(ChannelParticipantValue.creator.rawValue, forKey: "r")
encoder.encodeInt64(id.toInt64(), forKey: "i")
if let adminInfo = adminInfo {
encoder.encodeObject(adminInfo, forKey: "ai")
} else {
encoder.encodeNil(forKey: "ai")
}
if let rank = rank {
encoder.encodeString(rank, forKey: "rank")
} else {
encoder.encodeNil(forKey: "rank")
}
}
}
}
public final class CachedChannelParticipants: PostboxCoding, Equatable {
public let participants: [ChannelParticipant]
init(participants: [ChannelParticipant]) {
self.participants = participants
}
public init(decoder: PostboxDecoder) {
self.participants = decoder.decodeObjectArrayWithDecoderForKey("p")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.participants, forKey: "p")
}
public static func ==(lhs: CachedChannelParticipants, rhs: CachedChannelParticipants) -> Bool {
return lhs.participants == rhs.participants
}
}
extension ChannelParticipant {
init(apiParticipant: Api.ChannelParticipant) {
switch apiParticipant {
case let .channelParticipant(_, userId, date, subscriptionUntilDate):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: subscriptionUntilDate)
case let .channelParticipantCreator(_, userId, adminRights, rank):
self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights) ?? TelegramChatAdminRights(rights: []), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), canBeEditedByAccountPeer: true), rank: rank)
case let .channelParticipantBanned(flags, userId, restrictedBy, date, bannedRights):
let hasLeft = (flags & (1 << 0)) != 0
let banInfo = ChannelParticipantBannedInfo(rights: TelegramChatBannedRights(apiBannedRights: bannedRights), restrictedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(restrictedBy)), timestamp: date, isMember: !hasLeft)
self = .member(id: userId.peerId, invitedAt: date, adminInfo: nil, banInfo: banInfo, rank: nil, subscriptionUntilDate: nil)
case let .channelParticipantAdmin(flags, userId, _, promotedBy, date, adminRights, rank: rank):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights) ?? TelegramChatAdminRights(rights: []), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(promotedBy)), canBeEditedByAccountPeer: (flags & (1 << 0)) != 0), banInfo: nil, rank: rank, subscriptionUntilDate: nil)
case let .channelParticipantSelf(_, userId, _, date, subscriptionUntilDate):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: subscriptionUntilDate)
case let .channelParticipantLeft(userId):
self = .member(id: userId.peerId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil)
}
}
}
extension CachedChannelParticipants {
convenience init(apiParticipants: [Api.ChannelParticipant]) {
self.init(participants: apiParticipants.map(ChannelParticipant.init))
}
}
@@ -0,0 +1,28 @@
import Foundation
import Postbox
import TelegramApi
extension GroupParticipant {
init(apiParticipant: Api.ChatParticipant) {
switch apiParticipant {
case let .chatParticipantCreator(userId):
self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)))
case let .chatParticipantAdmin(userId, inviterId, date):
self = .admin(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId)), invitedAt: date)
case let .chatParticipant(userId, inviterId, date):
self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), invitedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId)), invitedAt: date)
}
}
}
extension CachedGroupParticipants {
convenience init?(apiParticipants: Api.ChatParticipants) {
switch apiParticipants {
case let .chatParticipants(_, participants, version):
self.init(participants: participants.map { GroupParticipant(apiParticipant: $0) }, version: version)
case .chatParticipantsForbidden:
return nil
}
}
}
@@ -0,0 +1,699 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum ChatContextResultMessageDecodingError: Error {
case generic
}
public enum ChatContextResultMessage: PostboxCoding, Equatable, Codable {
enum CodingKeys: String, CodingKey {
case data
}
case auto(caption: String, entities: TextEntitiesMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?)
case text(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, previewParameters: WebpagePreviewMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?)
case mapLocation(media: TelegramMediaMap, replyMarkup: ReplyMarkupMessageAttribute?)
case contact(media: TelegramMediaContact, replyMarkup: ReplyMarkupMessageAttribute?)
case invoice(media: TelegramMediaInvoice, replyMarkup: ReplyMarkupMessageAttribute?)
case webpage(text: String, entities: TextEntitiesMessageAttribute?, url: String, previewParameters: WebpagePreviewMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("_v", orElse: 0) {
case 0:
self = .auto(caption: decoder.decodeStringForKey("c", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
case 1:
self = .text(text: decoder.decodeStringForKey("t", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, disableUrlPreview: decoder.decodeInt32ForKey("du", orElse: 0) != 0, previewParameters: decoder.decodeObjectForKey("prp") as? WebpagePreviewMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
case 2:
self = .mapLocation(media: decoder.decodeObjectForKey("l") as! TelegramMediaMap, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
case 3:
self = .contact(media: decoder.decodeObjectForKey("c") as! TelegramMediaContact, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
case 4:
self = .invoice(media: decoder.decodeObjectForKey("i") as! TelegramMediaInvoice, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
case 5:
self = .webpage(text: decoder.decodeStringForKey("t", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, url: decoder.decodeStringForKey("url", orElse: ""), previewParameters: decoder.decodeObjectForKey("prp") as? WebpagePreviewMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute)
default:
self = .auto(caption: "", entities: nil, replyMarkup: nil)
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .auto(caption, entities, replyMarkup):
encoder.encodeInt32(0, forKey: "_v")
encoder.encodeString(caption, forKey: "c")
if let entities = entities {
encoder.encodeObject(entities, forKey: "e")
} else {
encoder.encodeNil(forKey: "e")
}
if let replyMarkup = replyMarkup {
encoder.encodeObject(replyMarkup, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
case let .text(text, entities, disableUrlPreview, previewParameters, replyMarkup):
encoder.encodeInt32(1, forKey: "_v")
encoder.encodeString(text, forKey: "t")
if let entities = entities {
encoder.encodeObject(entities, forKey: "e")
} else {
encoder.encodeNil(forKey: "e")
}
encoder.encodeInt32(disableUrlPreview ? 1 : 0, forKey: "du")
if let previewParameters = previewParameters {
encoder.encodeObject(previewParameters, forKey: "prp")
} else {
encoder.encodeNil(forKey: "prp")
}
if let replyMarkup = replyMarkup {
encoder.encodeObject(replyMarkup, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
case let .mapLocation(media, replyMarkup):
encoder.encodeInt32(2, forKey: "_v")
encoder.encodeObject(media, forKey: "l")
if let replyMarkup = replyMarkup {
encoder.encodeObject(replyMarkup, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
case let .contact(media, replyMarkup):
encoder.encodeInt32(3, forKey: "_v")
encoder.encodeObject(media, forKey: "c")
if let replyMarkup = replyMarkup {
encoder.encodeObject(replyMarkup, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
case let .invoice(media, replyMarkup):
encoder.encodeInt32(4, forKey: "_v")
encoder.encodeObject(media, forKey: "i")
if let replyMarkup = replyMarkup {
encoder.encodeObject(replyMarkup, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
case let .webpage(text, entities, url, previewParameters, replyMarkup):
encoder.encodeInt32(5, forKey: "_v")
encoder.encodeString(text, forKey: "t")
if let entities = entities {
encoder.encodeObject(entities, forKey: "e")
} else {
encoder.encodeNil(forKey: "e")
}
encoder.encodeString(url, forKey: "url")
if let previewParameters = previewParameters {
encoder.encodeObject(previewParameters, forKey: "prp")
} else {
encoder.encodeNil(forKey: "prp")
}
if let replyMarkup = replyMarkup {
encoder.encodeObject(replyMarkup, forKey: "m")
} else {
encoder.encodeNil(forKey: "m")
}
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.decode(Data.self, forKey: .data)
let postboxDecoder = PostboxDecoder(buffer: MemoryBuffer(data: data))
self = ChatContextResultMessage(decoder: postboxDecoder)
}
public func encode(to encoder: Encoder) throws {
let postboxEncoder = PostboxEncoder()
self.encode(postboxEncoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(postboxEncoder.makeData(), forKey: .data)
}
public static func ==(lhs: ChatContextResultMessage, rhs: ChatContextResultMessage) -> Bool {
switch lhs {
case let .auto(lhsCaption, lhsEntities, lhsReplyMarkup):
if case let .auto(rhsCaption, rhsEntities, rhsReplyMarkup) = rhs {
if lhsCaption != rhsCaption {
return false
}
if lhsEntities != rhsEntities {
return false
}
if lhsReplyMarkup != rhsReplyMarkup {
return false
}
return true
} else {
return false
}
case let .text(lhsText, lhsEntities, lhsDisableUrlPreview, lhsPreviewParameters, lhsReplyMarkup):
if case let .text(rhsText, rhsEntities, rhsDisableUrlPreview, rhsPreviewParameters, rhsReplyMarkup) = rhs {
if lhsText != rhsText {
return false
}
if lhsEntities != rhsEntities {
return false
}
if lhsDisableUrlPreview != rhsDisableUrlPreview {
return false
}
if lhsPreviewParameters != rhsPreviewParameters {
return false
}
if lhsReplyMarkup != rhsReplyMarkup {
return false
}
return true
} else {
return false
}
case let .mapLocation(lhsMedia, lhsReplyMarkup):
if case let .mapLocation(rhsMedia, rhsReplyMarkup) = rhs {
if !lhsMedia.isEqual(to: rhsMedia) {
return false
}
if lhsReplyMarkup != rhsReplyMarkup {
return false
}
return true
} else {
return false
}
case let .contact(lhsMedia, lhsReplyMarkup):
if case let .contact(rhsMedia, rhsReplyMarkup) = rhs {
if !lhsMedia.isEqual(to: rhsMedia) {
return false
}
if lhsReplyMarkup != rhsReplyMarkup {
return false
}
return true
} else {
return false
}
case let .invoice(lhsMedia, lhsReplyMarkup):
if case let .invoice(rhsMedia, rhsReplyMarkup) = rhs {
if !lhsMedia.isEqual(to: rhsMedia) {
return false
}
if lhsReplyMarkup != rhsReplyMarkup {
return false
}
return true
} else {
return false
}
case let .webpage(lhsText, lhsEntities, lhsUrl, lhsPreviewParameters, lhsReplyMarkup):
if case let .webpage(rhsText, rhsEntities, rhsUrl, rhsPreviewParameters, rhsReplyMarkup) = rhs {
if lhsText != rhsText {
return false
}
if lhsEntities != rhsEntities {
return false
}
if lhsUrl != rhsUrl {
return false
}
if lhsPreviewParameters != rhsPreviewParameters {
return false
}
if lhsReplyMarkup != rhsReplyMarkup {
return false
}
return true
} else {
return false
}
}
}
}
public enum ChatContextResultDecodingError: Error {
case generic
}
public enum ChatContextResult: Equatable, Codable {
enum CodingKeys: String, CodingKey {
case externalReference
case internalReference
}
public struct ExternalReference: Equatable, Codable {
public let queryId: Int64
public let id: String
public let type: String
public let title: String?
public let description: String?
public let url: String?
public let content: TelegramMediaWebFile?
public let thumbnail: TelegramMediaWebFile?
public let message: ChatContextResultMessage
public init(
queryId: Int64,
id: String,
type: String,
title: String?,
description: String?,
url: String?,
content: TelegramMediaWebFile?,
thumbnail: TelegramMediaWebFile?,
message: ChatContextResultMessage
) {
self.queryId = queryId
self.id = id
self.type = type
self.title = title
self.description = description
self.url = url
self.content = content
self.thumbnail = thumbnail
self.message = message
}
}
public struct InternalReference: Equatable, Codable {
public let queryId: Int64
public let id: String
public let type: String
public let title: String?
public let description: String?
public let image: TelegramMediaImage?
public let file: TelegramMediaFile?
public let message: ChatContextResultMessage
public init(
queryId: Int64,
id: String,
type: String,
title: String?,
description: String?,
image: TelegramMediaImage?,
file: TelegramMediaFile?,
message: ChatContextResultMessage
) {
self.queryId = queryId
self.id = id
self.type = type
self.title = title
self.description = description
self.image = image
self.file = file
self.message = message
}
}
case externalReference(ExternalReference)
case internalReference(InternalReference)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let externalReference = try? container.decode(ExternalReference.self, forKey: .externalReference) {
self = .externalReference(externalReference)
} else if let internalReference = try? container.decode(InternalReference.self, forKey: .internalReference) {
self = .internalReference(internalReference)
} else {
throw ChatContextResultDecodingError.generic
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .internalReference(internalReference):
try container.encode(internalReference, forKey: .internalReference)
case let .externalReference(externalReference):
try container.encode(externalReference, forKey: .externalReference)
}
}
public var queryId: Int64 {
switch self {
case let .externalReference(externalReference):
return externalReference.queryId
case let .internalReference(internalReference):
return internalReference.queryId
}
}
public var id: String {
switch self {
case let .externalReference(externalReference):
return externalReference.id
case let .internalReference(internalReference):
return internalReference.id
}
}
public var type: String {
switch self {
case let .externalReference(externalReference):
return externalReference.type
case let .internalReference(internalReference):
return internalReference.type
}
}
public var title: String? {
switch self {
case let .externalReference(externalReference):
return externalReference.title
case let .internalReference(internalReference):
return internalReference.title
}
}
public var description: String? {
switch self {
case let .externalReference(externalReference):
return externalReference.description
case let .internalReference(internalReference):
return internalReference.description
}
}
public var message: ChatContextResultMessage {
switch self {
case let .externalReference(externalReference):
return externalReference.message
case let .internalReference(internalReference):
return internalReference.message
}
}
}
public enum ChatContextResultCollectionPresentation: Int32, Codable {
case media
case list
}
public struct ChatContextResultSwitchPeer: Equatable, Codable {
public let text: String
public let startParam: String
public static func ==(lhs: ChatContextResultSwitchPeer, rhs: ChatContextResultSwitchPeer) -> Bool {
return lhs.text == rhs.text && lhs.startParam == rhs.startParam
}
}
public struct ChatContextResultWebView: Equatable, Codable {
public let text: String
public let url: String
public static func ==(lhs: ChatContextResultWebView, rhs: ChatContextResultWebView) -> Bool {
return lhs.text == rhs.text && lhs.url == rhs.url
}
}
public final class ChatContextResultCollection: Equatable, Codable {
public struct GeoPoint: Equatable, Codable {
public let latitude: Double
public let longitude: Double
public init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
public let botId: PeerId
public let peerId: PeerId
public let query: String
public let geoPoint: ChatContextResultCollection.GeoPoint?
public let queryId: Int64
public let nextOffset: String?
public let presentation: ChatContextResultCollectionPresentation
public let switchPeer: ChatContextResultSwitchPeer?
public let webView: ChatContextResultWebView?
public let results: [ChatContextResult]
public let cacheTimeout: Int32
public init(botId: PeerId, peerId: PeerId, query: String, geoPoint: ChatContextResultCollection.GeoPoint?, queryId: Int64, nextOffset: String?, presentation: ChatContextResultCollectionPresentation, switchPeer: ChatContextResultSwitchPeer?, webView: ChatContextResultWebView?, results: [ChatContextResult], cacheTimeout: Int32) {
self.botId = botId
self.peerId = peerId
self.query = query
self.geoPoint = geoPoint
self.queryId = queryId
self.nextOffset = nextOffset
self.presentation = presentation
self.switchPeer = switchPeer
self.webView = webView
self.results = results
self.cacheTimeout = cacheTimeout
}
public static func ==(lhs: ChatContextResultCollection, rhs: ChatContextResultCollection) -> Bool {
if lhs.botId != rhs.botId {
return false
}
if lhs.peerId != rhs.peerId {
return false
}
if lhs.queryId != rhs.queryId {
return false
}
if lhs.query != rhs.query {
return false
}
if lhs.geoPoint != rhs.geoPoint {
return false
}
if lhs.nextOffset != rhs.nextOffset {
return false
}
if lhs.presentation != rhs.presentation {
return false
}
if lhs.switchPeer != rhs.switchPeer {
return false
}
if lhs.webView != rhs.webView {
return false
}
if lhs.results != rhs.results {
return false
}
if lhs.cacheTimeout != rhs.cacheTimeout {
return false
}
return true
}
}
extension ChatContextResultMessage {
init(apiMessage: Api.BotInlineMessage) {
switch apiMessage {
case let .botInlineMessageMediaAuto(_, message, entities, replyMarkup):
var parsedEntities: TextEntitiesMessageAttribute?
if let entities = entities, !entities.isEmpty {
parsedEntities = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
}
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
self = .auto(caption: message, entities: parsedEntities, replyMarkup: parsedReplyMarkup)
case let .botInlineMessageText(flags, message, entities, replyMarkup):
var parsedEntities: TextEntitiesMessageAttribute?
if let entities = entities, !entities.isEmpty {
parsedEntities = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
}
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
let leadingPreview = (flags & (1 << 3)) != 0
self = .text(text: message, entities: parsedEntities, disableUrlPreview: (flags & (1 << 0)) != 0, previewParameters: WebpagePreviewMessageAttribute(
leadingPreview: leadingPreview,
forceLargeMedia: nil,
isManuallyAdded: false,
isSafe: false
), replyMarkup: parsedReplyMarkup)
case let .botInlineMessageMediaGeo(_, geo, heading, period, proximityNotificationRadius, replyMarkup):
let media = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, liveProximityNotificationRadius: proximityNotificationRadius, heading: heading)
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup)
case let .botInlineMessageMediaVenue(_, geo, title, address, provider, venueId, venueType, replyMarkup):
let media = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil, heading: nil)
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup)
case let .botInlineMessageMediaContact(_, phoneNumber, firstName, lastName, vcard, replyMarkup):
let media = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: nil, vCardData: vcard.isEmpty ? nil : vcard)
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
self = .contact(media: media, replyMarkup: parsedReplyMarkup)
case let .botInlineMessageMediaInvoice(flags, title, description, photo, currency, totalAmount, replyMarkup):
var parsedFlags = TelegramMediaInvoiceFlags()
if (flags & (1 << 3)) != 0 {
parsedFlags.insert(.isTest)
}
if (flags & (1 << 1)) != 0 {
parsedFlags.insert(.shippingAddressRequested)
}
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
self = .invoice(media: TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: currency, totalAmount: totalAmount, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), replyMarkup: parsedReplyMarkup)
case let .botInlineMessageMediaWebPage(flags, message, entities, url, replyMarkup):
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
if let replyMarkup = replyMarkup {
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
}
let leadingPreview = (flags & (1 << 3)) != 0
var forceLargeMedia: Bool?
if (flags & (1 << 4)) != 0 {
forceLargeMedia = true
} else if (flags & (1 << 5)) != 0 {
forceLargeMedia = false
}
let isManuallyAdded = (flags & (1 << 7)) != 0
let isSafe = (flags & (1 << 8)) != 0
var parsedEntities: TextEntitiesMessageAttribute?
if let entities = entities, !entities.isEmpty {
parsedEntities = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
}
self = .webpage(
text: message,
entities: parsedEntities,
url: url,
previewParameters: WebpagePreviewMessageAttribute(
leadingPreview: leadingPreview,
forceLargeMedia: forceLargeMedia,
isManuallyAdded: isManuallyAdded,
isSafe: isSafe
),
replyMarkup: parsedReplyMarkup
)
}
}
}
extension ChatContextResult {
init(apiResult: Api.BotInlineResult, queryId: Int64) {
switch apiResult {
case let .botInlineResult(_, id, type, title, description, url, thumb, content, sendMessage):
self = .externalReference(ChatContextResult.ExternalReference(queryId: queryId, id: id, type: type, title: title, description: description, url: url, content: content.flatMap(TelegramMediaWebFile.init), thumbnail: thumb.flatMap(TelegramMediaWebFile.init), message: ChatContextResultMessage(apiMessage: sendMessage)))
case let .botInlineMediaResult(_, id, type, photo, document, title, description, sendMessage):
var image: TelegramMediaImage?
var file: TelegramMediaFile?
if let photo = photo, let parsedImage = telegramMediaImageFromApiPhoto(photo) {
image = parsedImage
}
if let document = document, let parsedFile = telegramMediaFileFromApiDocument(document, altDocuments: []) {
file = parsedFile
}
self = .internalReference(ChatContextResult.InternalReference(queryId: queryId, id: id, type: type, title: title, description: description, image: image, file: file, message: ChatContextResultMessage(apiMessage: sendMessage)))
}
}
}
extension ChatContextResultSwitchPeer {
init(apiSwitchPeer: Api.InlineBotSwitchPM) {
switch apiSwitchPeer {
case let .inlineBotSwitchPM(text, startParam):
self.init(text: text, startParam: startParam)
}
}
}
extension ChatContextResultWebView {
init(apiSwitchWebView: Api.InlineBotWebView) {
switch apiSwitchWebView {
case let .inlineBotWebView(text, url):
self.init(text: text, url: url)
}
}
}
extension ChatContextResultCollection {
convenience init(apiResults: Api.messages.BotResults, botId: PeerId, peerId: PeerId, query: String, geoPoint: (Double, Double)?) {
switch apiResults {
case let .botResults(flags, queryId, nextOffset, switchPm, switchWebView, results, cacheTime, _):
var switchPeer: ChatContextResultSwitchPeer?
if let switchPm = switchPm {
switchPeer = ChatContextResultSwitchPeer(apiSwitchPeer: switchPm)
}
var webView: ChatContextResultWebView?
if let switchWebView = switchWebView {
webView = ChatContextResultWebView(apiSwitchWebView: switchWebView)
}
let parsedResults = results.map({ ChatContextResult(apiResult: $0, queryId: queryId) })
/*.filter({ result in
switch result {
case .internalReference:
return false
default:
return true
}
})*/
let mappedGeoPoint = geoPoint.flatMap { geoPoint -> ChatContextResultCollection.GeoPoint in
return ChatContextResultCollection.GeoPoint(latitude: geoPoint.0, longitude: geoPoint.1)
}
self.init(botId: botId, peerId: peerId, query: query, geoPoint: mappedGeoPoint, queryId: queryId, nextOffset: nextOffset, presentation: (flags & (1 << 0) != 0) ? .media : .list, switchPeer: switchPeer, webView: webView, results: parsedResults, cacheTimeout: cacheTime)
}
}
}
public func requestContextResults(engine: TelegramEngine, botId: EnginePeer.Id, query: String, peerId: EnginePeer.Id, offset: String = "", existingResults: ChatContextResultCollection? = nil, incompleteResults: Bool = false, staleCachedResults: Bool = false, limit: Int = 60) -> Signal<RequestChatContextResultsResult?, NoError> {
return engine.messages.requestChatContextResults(botId: botId, peerId: peerId, query: query, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|> `catch` { error -> Signal<RequestChatContextResultsResult?, NoError> in
return .single(nil)
}
|> mapToSignal { resultsStruct -> Signal<RequestChatContextResultsResult?, NoError> in
let results = resultsStruct?.results
var collection = existingResults
var updated: Bool = false
if let existingResults = existingResults, let results = results {
var newResults: [ChatContextResult] = []
var existingIds = Set<String>()
for result in existingResults.results {
newResults.append(result)
existingIds.insert(result.id)
}
for result in results.results {
if !existingIds.contains(result.id) {
newResults.append(result)
existingIds.insert(result.id)
updated = true
}
}
collection = ChatContextResultCollection(botId: existingResults.botId, peerId: existingResults.peerId, query: existingResults.query, geoPoint: existingResults.geoPoint, queryId: results.queryId, nextOffset: results.nextOffset, presentation: existingResults.presentation, switchPeer: existingResults.switchPeer, webView: existingResults.webView, results: newResults, cacheTimeout: existingResults.cacheTimeout)
} else {
collection = results
updated = true
}
if let collection = collection, collection.results.count < limit, let nextOffset = collection.nextOffset, updated {
let nextResults = requestContextResults(engine: engine, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit)
if collection.results.count > 10 {
return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false))
|> then(nextResults)
} else {
return nextResults
}
} else if let collection = collection {
return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false))
} else {
return .single(nil)
}
}
}
@@ -0,0 +1,84 @@
import Foundation
import Postbox
import TelegramApi
protocol TelegramCloudMediaResource: TelegramMediaResource {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation?
}
public func extractMediaResourceDebugInfo(resource: MediaResource) -> String? {
if let resource = resource as? TelegramCloudMediaResource {
guard let inputLocation = resource.apiInputLocation(fileReference: nil) else {
return nil
}
return String(describing: inputLocation)
} else {
return nil
}
}
public protocol TelegramMultipartFetchableResource: TelegramMediaResource {
var datacenterId: Int { get }
}
public protocol TelegramCloudMediaResourceWithFileReference {
var fileReference: Data? { get }
}
extension CloudFileMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
return Api.InputFileLocation.inputFileLocation(volumeId: self.volumeId, localId: self.localId, secret: self.secret, fileReference: Buffer(data: fileReference ?? Data()))
}
}
extension CloudPhotoSizeMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
return Api.InputFileLocation.inputPhotoFileLocation(id: self.photoId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference ?? Data()), thumbSize: self.sizeSpec)
}
}
extension CloudDocumentSizeMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
return Api.InputFileLocation.inputDocumentFileLocation(id: self.documentId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference ?? Data()), thumbSize: self.sizeSpec)
}
}
extension CloudPeerPhotoSizeMediaResource: TelegramMultipartFetchableResource {
func apiInputLocation(peerReference: PeerReference) -> Api.InputFileLocation? {
let flags: Int32
switch self.sizeSpec {
case .small:
flags = 0
case .fullSize:
flags = 1 << 0
}
if let photoId = self.photoId {
return Api.InputFileLocation.inputPeerPhotoFileLocation(flags: flags, peer: peerReference.inputPeer, photoId: photoId)
} else {
return nil
}
}
}
extension CloudStickerPackThumbnailMediaResource: TelegramMultipartFetchableResource {
func apiInputLocation(packReference: StickerPackReference) -> Api.InputFileLocation? {
if let thumbVersion = self.thumbVersion {
return Api.InputFileLocation.inputStickerSetThumb(stickerset: packReference.apiInputStickerSet, thumbVersion: thumbVersion)
} else {
return nil
}
}
}
extension CloudDocumentMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource, TelegramCloudMediaResourceWithFileReference {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference ?? Data()), thumbSize: "")
}
}
extension SecretFileMediaResource: TelegramCloudMediaResource, TelegramMultipartFetchableResource {
func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? {
return .inputEncryptedFileLocation(id: self.fileId, accessHash: self.accessHash)
}
}
@@ -0,0 +1,8 @@
import Foundation
public enum CloudMediaResourceLocation: Equatable {
case photo(id: Int64, accessHash: Int64, fileReference: Data, thumbSize: String)
case file(id: Int64, accessHash: Int64, fileReference: Data, thumbSize: String)
case peerPhoto(peer: PeerReference, fullSize: Bool, volumeId: Int64, localId: Int64)
case stickerPackThumbnail(packReference: StickerPackReference, volumeId: Int64, localId: Int64)
}
@@ -0,0 +1,5 @@
import Foundation
public protocol EncryptedMediaResource {
func decrypt(data: Data, params: Any) -> Data?
}
@@ -0,0 +1,63 @@
import Foundation
import Postbox
import TelegramApi
extension ExportedInvitation {
init(apiExportedInvite: Api.ExportedChatInvite) {
switch apiExportedInvite {
case let .chatInviteExported(flags, link, adminId, date, startDate, expireDate, usageLimit, usage, requested, subscriptionExpired, title, pricing):
let _ = subscriptionExpired
self = .link(link: link, title: title, isPermanent: (flags & (1 << 5)) != 0, requestApproval: (flags & (1 << 6)) != 0, isRevoked: (flags & (1 << 0)) != 0, adminId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), date: date, startDate: startDate, expireDate: expireDate, usageLimit: usageLimit, count: usage, requestedCount: requested, pricing: pricing.flatMap { StarsSubscriptionPricing(apiStarsSubscriptionPricing: $0) })
case .chatInvitePublicJoinRequests:
self = .publicJoinRequest
}
}
}
public extension ExportedInvitation {
var link: String? {
switch self {
case let .link(link, _, _, _, _, _, _, _, _, _, _, _, _):
return link
case .publicJoinRequest:
return nil
}
}
var date: Int32? {
switch self {
case let .link(_, _, _, _, _, _, date, _, _, _, _, _, _):
return date
case .publicJoinRequest:
return nil
}
}
var isPermanent: Bool {
switch self {
case let .link(_, _, isPermanent, _, _, _, _, _, _, _, _, _, _):
return isPermanent
case .publicJoinRequest:
return false
}
}
var isRevoked: Bool {
switch self {
case let .link(_, _, _, _, isRevoked, _, _, _, _, _, _, _, _):
return isRevoked
case .publicJoinRequest:
return false
}
}
var pricing: StarsSubscriptionPricing? {
switch self {
case let .link(_, _, _, _, _, _, _, _, _, _, _, _, pricing):
return pricing
case .publicJoinRequest:
return nil
}
}
}
@@ -0,0 +1,22 @@
import Foundation
public struct ImageRepresentationWithReference: Equatable {
public let representation: TelegramMediaImageRepresentation
public let reference: MediaResourceReference
public init(representation: TelegramMediaImageRepresentation, reference: MediaResourceReference) {
self.representation = representation
self.reference = reference
}
}
public struct VideoRepresentationWithReference: Equatable {
public let representation: TelegramMediaImage.VideoRepresentation
public let reference: MediaResourceReference
public init(representation: TelegramMediaImage.VideoRepresentation, reference: MediaResourceReference) {
self.representation = representation
self.reference = reference
}
}
@@ -0,0 +1,201 @@
import Foundation
import Postbox
import TelegramApi
extension InstantPageCaption {
convenience init(apiCaption: Api.PageCaption) {
switch apiCaption {
case let .pageCaption(text, credit):
self.init(text: RichText(apiText: text), credit: RichText(apiText: credit))
}
}
}
public extension InstantPageListItem {
var num: String? {
switch self {
case let .text(_, num):
return num
case let .blocks(_, num):
return num
default:
return nil
}
}
}
extension InstantPageListItem {
init(apiListItem: Api.PageListItem) {
switch apiListItem {
case let .pageListItemText(text):
self = .text(RichText(apiText: text), nil)
case let .pageListItemBlocks(blocks):
self = .blocks(blocks.map({ InstantPageBlock(apiBlock: $0) }), nil)
}
}
init(apiListOrderedItem: Api.PageListOrderedItem) {
switch apiListOrderedItem {
case let .pageListOrderedItemText(num, text):
self = .text(RichText(apiText: text), num)
case let .pageListOrderedItemBlocks(num, blocks):
self = .blocks(blocks.map({ InstantPageBlock(apiBlock: $0) }), num)
}
}
}
extension InstantPageTableCell {
convenience init(apiTableCell: Api.PageTableCell) {
switch apiTableCell {
case let .pageTableCell(flags, text, colspan, rowspan):
var alignment = TableHorizontalAlignment.left
if (flags & (1 << 3)) != 0 {
alignment = .center
} else if (flags & (1 << 4)) != 0 {
alignment = .right
}
var verticalAlignment = TableVerticalAlignment.top
if (flags & (1 << 5)) != 0 {
verticalAlignment = .middle
} else if (flags & (1 << 6)) != 0 {
verticalAlignment = .bottom
}
self.init(text: text != nil ? RichText(apiText: text!) : nil, header: (flags & (1 << 0)) != 0, alignment: alignment, verticalAlignment: verticalAlignment, colspan: colspan ?? 0, rowspan: rowspan ?? 0)
}
}
}
extension InstantPageTableRow {
convenience init(apiTableRow: Api.PageTableRow) {
switch apiTableRow {
case let .pageTableRow(cells):
self.init(cells: cells.map({ InstantPageTableCell(apiTableCell: $0) }))
}
}
}
extension InstantPageRelatedArticle {
convenience init(apiRelatedArticle: Api.PageRelatedArticle) {
switch apiRelatedArticle {
case let .pageRelatedArticle(_, url, webpageId, title, description, photoId, author, publishedDate):
var posterPhotoId: MediaId?
if let photoId = photoId {
posterPhotoId = MediaId(namespace: Namespaces.Media.CloudImage, id: photoId)
}
self.init(url: url, webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId), title: title, description: description, photoId: posterPhotoId, author: author, date: publishedDate)
}
}
}
extension InstantPageBlock {
init(apiBlock: Api.PageBlock) {
switch apiBlock {
case .pageBlockUnsupported:
self = .unsupported
case let .pageBlockTitle(text):
self = .title(RichText(apiText: text))
case let .pageBlockSubtitle(text):
self = .subtitle(RichText(apiText: text))
case let .pageBlockAuthorDate(author, publishedDate):
self = .authorDate(author: RichText(apiText: author), date: publishedDate)
case let .pageBlockHeader(text):
self = .header(RichText(apiText: text))
case let .pageBlockSubheader(text):
self = .subheader(RichText(apiText: text))
case let .pageBlockParagraph(text):
self = .paragraph(RichText(apiText: text))
case let .pageBlockPreformatted(text, _):
self = .preformatted(RichText(apiText: text))
case let .pageBlockFooter(text):
self = .footer(RichText(apiText: text))
case .pageBlockDivider:
self = .divider
case let .pageBlockAnchor(name):
self = .anchor(name)
case let .pageBlockBlockquote(text, caption):
self = .blockQuote(text: RichText(apiText: text), caption: RichText(apiText: caption))
case let .pageBlockPullquote(text, caption):
self = .pullQuote(text: RichText(apiText: text), caption: RichText(apiText: caption))
case let .pageBlockPhoto(flags, photoId, caption, url, webpageId):
var webpageMediaId: MediaId?
if (flags & (1 << 0)) != 0, let webpageId = webpageId, webpageId != 0 {
webpageMediaId = MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId)
}
self = .image(id: MediaId(namespace: Namespaces.Media.CloudImage, id: photoId), caption: InstantPageCaption(apiCaption: caption), url: url, webpageId: webpageMediaId)
case let .pageBlockVideo(flags, videoId, caption):
self = .video(id: MediaId(namespace: Namespaces.Media.CloudFile, id: videoId), caption: InstantPageCaption(apiCaption: caption), autoplay: (flags & (1 << 0)) != 0, loop: (flags & (1 << 1)) != 0)
case let .pageBlockCover(cover):
self = .cover(InstantPageBlock(apiBlock: cover))
case let .pageBlockEmbed(flags, url, html, posterPhotoId, w, h, caption):
var dimensions: PixelDimensions?
if let w = w, let h = h {
dimensions = PixelDimensions(width: w, height: h)
}
self = .webEmbed(url: url, html: html, dimensions: dimensions, caption: InstantPageCaption(apiCaption: caption), stretchToWidth: (flags & (1 << 0)) != 0, allowScrolling: (flags & (1 << 3)) != 0, coverId: posterPhotoId.flatMap { MediaId(namespace: Namespaces.Media.CloudImage, id: $0) })
case let .pageBlockEmbedPost(url, webpageId, authorPhotoId, author, date, blocks, caption):
self = .postEmbed(url: url, webpageId: webpageId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId), avatarId: authorPhotoId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudImage, id: authorPhotoId), author: author, date: date, blocks: blocks.map({ InstantPageBlock(apiBlock: $0) }), caption: InstantPageCaption(apiCaption: caption))
case let .pageBlockCollage(items, caption):
self = .collage(items: items.map({ InstantPageBlock(apiBlock: $0) }), caption: InstantPageCaption(apiCaption: caption))
case let .pageBlockSlideshow(items, caption):
self = .slideshow(items: items.map({ InstantPageBlock(apiBlock: $0) }), caption: InstantPageCaption(apiCaption: caption))
case let .pageBlockChannel(channel: apiChat):
self = .channelBanner(parseTelegramGroupOrChannel(chat: apiChat) as? TelegramChannel)
case let .pageBlockAudio(audioId, caption):
self = .audio(id: MediaId(namespace: Namespaces.Media.CloudFile, id: audioId), caption: InstantPageCaption(apiCaption: caption))
case let .pageBlockKicker(text):
self = .kicker(RichText(apiText: text))
case let .pageBlockTable(flags, title, rows):
self = .table(title: RichText(apiText: title), rows: rows.map({ InstantPageTableRow(apiTableRow: $0) }), bordered: (flags & (1 << 0)) != 0, striped: (flags & (1 << 1)) != 0)
case let .pageBlockList(items):
self = .list(items: items.map({ InstantPageListItem(apiListItem: $0) }), ordered: false)
case let .pageBlockOrderedList(items):
self = .list(items: items.map({ InstantPageListItem(apiListOrderedItem: $0) }), ordered: true)
case let .pageBlockDetails(flags, blocks, title):
self = .details(title: RichText(apiText: title), blocks: blocks.map({ InstantPageBlock(apiBlock: $0) }), expanded: (flags & (1 << 0)) != 0)
case let .pageBlockRelatedArticles(title, articles):
self = .relatedArticles(title: RichText(apiText: title), articles: articles.map({ InstantPageRelatedArticle(apiRelatedArticle: $0) }))
case let .pageBlockMap(geo, zoom, w, h, caption):
switch geo {
case let .geoPoint(_, long, lat, _, _):
self = .map(latitude: lat, longitude: long, zoom: zoom, dimensions: PixelDimensions(width: w, height: h), caption: InstantPageCaption(apiCaption: caption))
default:
self = .unsupported
}
}
}
}
extension InstantPage {
convenience init(apiPage: Api.Page) {
let blocks: [Api.PageBlock]
let photos: [Api.Photo]
let files: [Api.Document]
let isComplete: Bool
let rtl: Bool
let url: String
let views: Int32?
switch apiPage {
case let .page(flags, pageUrl, pageBlocks, pagePhotos, pageDocuments, pageViews):
url = pageUrl
blocks = pageBlocks
photos = pagePhotos
files = pageDocuments
isComplete = (flags & (1 << 0)) == 0
rtl = (flags & (1 << 1)) != 0
views = pageViews
}
var media: [MediaId: Media] = [:]
for photo in photos {
if let image = telegramMediaImageFromApiPhoto(photo), let id = image.id {
media[id] = image
}
}
for file in files {
if let file = telegramMediaFileFromApiDocument(file, altDocuments: []), let id = file.id {
media[id] = file
}
}
self.init(blocks: blocks.map({ InstantPageBlock(apiBlock: $0) }), media: media, isComplete: isComplete, rtl: rtl, url: url, views: views)
}
}
@@ -0,0 +1,7 @@
import Foundation
extension SecretChatFileReference {
func resource(key: SecretFileEncryptionKey, decryptedSize: Int64) -> SecretFileMediaResource {
return SecretFileMediaResource(fileId: self.id, accessHash: self.accessHash, containerSize: self.size, decryptedSize: decryptedSize, datacenterId: Int(self.datacenterId), key: key)
}
}
@@ -0,0 +1,19 @@
import Foundation
import Postbox
import TelegramApi
extension RestrictionRule {
convenience init(apiReason: Api.RestrictionReason) {
switch apiReason {
case let .restrictionReason(platform, reason, text):
self.init(platform: platform, reason: reason, text: text)
}
}
}
extension PeerAccessRestrictionInfo {
convenience init(apiReasons: [Api.RestrictionReason]) {
self.init(rules: apiReasons.map(RestrictionRule.init(apiReason:)))
}
}
@@ -0,0 +1,19 @@
import Foundation
import Postbox
import TelegramApi
extension PeerGeoLocation {
init?(apiLocation: Api.ChannelLocation) {
switch apiLocation {
case let .channelLocation(geopoint, address):
if case let .geoPoint(_, longitude, latitude, _, _) = geopoint {
self.init(latitude: latitude, longitude: longitude, address: address)
} else {
return nil
}
default:
return nil
}
}
}
@@ -0,0 +1,317 @@
import Foundation
import Postbox
import TelegramApi
extension ReactionsMessageAttribute {
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
switch reactions {
case let .messageReactions(flags, results, recentReactions, topReactors):
let min = (flags & (1 << 0)) != 0
let canViewList = (flags & (1 << 2)) != 0
let isTags = (flags & (1 << 3)) != 0
var reactions = results.compactMap { result -> MessageReaction? in
switch result {
case let .reactionCount(_, chosenOrder, reaction, count):
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
return MessageReaction(value: reaction, count: count, chosenOrder: chosenOrder.flatMap(Int.init))
} else {
return nil
}
}
}
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]
if let recentReactions = recentReactions {
parsedRecentReactions = recentReactions.compactMap { recentReaction -> ReactionsMessageAttribute.RecentPeer? in
switch recentReaction {
case let .messagePeerReaction(flags, peerId, date, reaction):
let isLarge = (flags & (1 << 0)) != 0
let isUnseen = (flags & (1 << 1)) != 0
let isMy = (flags & (1 << 2)) != 0
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, isMy: isMy, peerId: peerId.peerId, timestamp: date)
} else {
return nil
}
}
}
} else {
parsedRecentReactions = []
}
if min {
var currentSelectedReactions: [MessageReaction.Reaction: Int] = [:]
for reaction in self.reactions {
if let chosenOrder = reaction.chosenOrder {
currentSelectedReactions[reaction.value] = chosenOrder
break
}
}
if !currentSelectedReactions.isEmpty {
for i in 0 ..< reactions.count {
if let chosenOrder = currentSelectedReactions[reactions[i].value] {
reactions[i].chosenOrder = chosenOrder
}
}
}
}
var topPeers: [ReactionsMessageAttribute.TopPeer] = []
if let topReactors {
for item in topReactors {
switch item {
case let .messageReactor(flags, peerId, count):
topPeers.append(ReactionsMessageAttribute.TopPeer(
peerId: peerId?.peerId,
count: count,
isTop: (flags & (1 << 0)) != 0,
isMy: (flags & (1 << 1)) != 0,
isAnonymous: (flags & (1 << 2)) != 0
))
}
}
}
return ReactionsMessageAttribute(canViewList: canViewList, isTags: isTags, reactions: reactions, recentPeers: parsedRecentReactions, topPeers: topPeers)
}
}
}
public func mergedMessageReactionsAndPeers(accountPeerId: EnginePeer.Id, accountPeer: EnginePeer?, message: Message) -> (reactions: [MessageReaction], peers: [(MessageReaction.Reaction, EnginePeer)]) {
guard let attribute = mergedMessageReactions(attributes: message.attributes, isTags: message.areReactionsTags(accountPeerId: accountPeerId)) else {
return ([], [])
}
var recentPeers: [(MessageReaction.Reaction, EnginePeer)] = []
if message.id.peerId.namespace == Namespaces.Peer.CloudUser {
for reaction in attribute.reactions {
var selfCount: Int32 = 0
if reaction.isSelected {
selfCount += 1
if let accountPeer = accountPeer {
recentPeers.append((reaction.value, accountPeer))
}
}
if reaction.count >= selfCount + 1 {
if let peer = message.peers[message.id.peerId] {
recentPeers.append((reaction.value, EnginePeer(peer)))
}
}
}
} else {
recentPeers = attribute.recentPeers.compactMap { recentPeer -> (MessageReaction.Reaction, EnginePeer)? in
if let peer = message.peers[recentPeer.peerId] {
return (recentPeer.value, EnginePeer(peer))
} else {
return nil
}
}
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
recentPeers.removeAll()
}
}
let reactions = attribute.reactions
return (reactions, recentPeers)
}
private func mergeReactions(reactions: [MessageReaction], recentPeers: [ReactionsMessageAttribute.RecentPeer], pending: [PendingReactionsMessageAttribute.PendingReaction], accountPeerId: PeerId) -> ([MessageReaction], [ReactionsMessageAttribute.RecentPeer]) {
var result = reactions
var recentPeers = recentPeers
var pendingIndex: Int = Int(Int32.max - 100)
for pendingReaction in pending {
if let index = result.firstIndex(where: { $0.value == pendingReaction.value }) {
var merged = result[index]
if merged.chosenOrder == nil {
merged.chosenOrder = pendingIndex
pendingIndex += 1
merged.count += 1
}
result[index] = merged
} else {
result.append(MessageReaction(value: pendingReaction.value, count: 1, chosenOrder: pendingIndex))
pendingIndex += 1
}
let pendingReactionSendAsPeerId = pendingReaction.sendAsPeerId ?? accountPeerId
if let index = recentPeers.firstIndex(where: {
$0.value == pendingReaction.value && ($0.peerId == pendingReactionSendAsPeerId || $0.isMy)
}) {
recentPeers.remove(at: index)
}
recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: pendingReaction.value, isLarge: false, isUnseen: false, isMy: true, peerId: pendingReactionSendAsPeerId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)))
}
for i in (0 ..< result.count).reversed() {
if result[i].chosenOrder != nil {
if !pending.contains(where: { $0.value == result[i].value }), result[i].value != .stars {
if let index = recentPeers.firstIndex(where: { $0.value == result[i].value && ($0.peerId == accountPeerId || $0.isMy) }) {
recentPeers.remove(at: index)
}
if result[i].count <= 1 {
result.remove(at: i)
} else {
result[i].count -= 1
result[i].chosenOrder = nil
}
}
}
}
if recentPeers.count > 3 {
recentPeers.removeFirst(recentPeers.count - 3)
}
return (result, recentPeers)
}
public func mergedMessageReactions(attributes: [MessageAttribute], isTags: Bool) -> ReactionsMessageAttribute? {
var current: ReactionsMessageAttribute?
var pending: PendingReactionsMessageAttribute?
var pendingStars: PendingStarsReactionsMessageAttribute?
for attribute in attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
current = attribute
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
pending = attribute
} else if let attribute = attribute as? PendingStarsReactionsMessageAttribute {
pendingStars = attribute
}
}
let result: ReactionsMessageAttribute?
if let pending = pending, let accountPeerId = pending.accountPeerId {
var reactions = current?.reactions ?? []
var recentPeers = current?.recentPeers ?? []
let (updatedReactions, updatedRecentPeers) = mergeReactions(reactions: reactions, recentPeers: recentPeers, pending: pending.reactions, accountPeerId: accountPeerId)
reactions = updatedReactions
recentPeers = updatedRecentPeers
if !reactions.isEmpty {
result = ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: recentPeers, topPeers: current?.topPeers ?? [])
} else {
result = nil
}
} else if let current {
result = current
} else {
result = nil
}
if let pendingStars {
if let result {
var reactions = result.reactions
var updatedCount: Int32 = pendingStars.count
if let index = reactions.firstIndex(where: { $0.value == .stars }) {
updatedCount += reactions[index].count
reactions.remove(at: index)
}
var topPeers = result.topPeers
if let index = topPeers.firstIndex(where: { $0.isMy }) {
topPeers[index].count += pendingStars.count
} else {
let isAnonymous: Bool
let topPeerId: PeerId?
switch pendingStars.privacy {
case .anonymous:
isAnonymous = true
topPeerId = pendingStars.accountPeerId
case .default:
isAnonymous = false
topPeerId = pendingStars.accountPeerId
case let .peer(peerId):
isAnonymous = false
topPeerId = peerId
}
topPeers.append(ReactionsMessageAttribute.TopPeer(peerId: topPeerId, count: pendingStars.count, isTop: false, isMy: true, isAnonymous: isAnonymous))
}
reactions.insert(MessageReaction(value: .stars, count: updatedCount, chosenOrder: -1), at: 0)
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: result.recentPeers, topPeers: topPeers)
} else {
let isAnonymous: Bool
let topPeerId: PeerId?
switch pendingStars.privacy {
case .anonymous:
isAnonymous = true
topPeerId = pendingStars.accountPeerId
case .default:
isAnonymous = false
topPeerId = pendingStars.accountPeerId
case let .peer(peerId):
isAnonymous = false
topPeerId = peerId
}
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: [MessageReaction(value: .stars, count: pendingStars.count, chosenOrder: -1)], recentPeers: [], topPeers: [ReactionsMessageAttribute.TopPeer(peerId: topPeerId, count: pendingStars.count, isTop: false, isMy: true, isAnonymous: isAnonymous)])
}
} else {
return result
}
}
extension ReactionsMessageAttribute {
convenience init(apiReactions: Api.MessageReactions) {
switch apiReactions {
case let .messageReactions(flags, results, recentReactions, topReactors):
let canViewList = (flags & (1 << 2)) != 0
let isTags = (flags & (1 << 3)) != 0
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]
if let recentReactions = recentReactions {
parsedRecentReactions = recentReactions.compactMap { recentReaction -> ReactionsMessageAttribute.RecentPeer? in
switch recentReaction {
case let .messagePeerReaction(flags, peerId, date, reaction):
let isLarge = (flags & (1 << 0)) != 0
let isUnseen = (flags & (1 << 1)) != 0
let isMy = (flags & (1 << 2)) != 0
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, isMy: isMy, peerId: peerId.peerId, timestamp: date)
} else {
return nil
}
}
}
} else {
parsedRecentReactions = []
}
var topPeers: [ReactionsMessageAttribute.TopPeer] = []
if let topReactors {
for item in topReactors {
switch item {
case let .messageReactor(flags, peerId, count):
topPeers.append(ReactionsMessageAttribute.TopPeer(
peerId: peerId?.peerId,
count: count,
isTop: (flags & (1 << 0)) != 0,
isMy: (flags & (1 << 1)) != 0,
isAnonymous: (flags & (1 << 2)) != 0
))
}
}
}
self.init(
canViewList: canViewList,
isTags: isTags,
reactions: results.compactMap { result -> MessageReaction? in
switch result {
case let .reactionCount(_, chosenOrder, reaction, count):
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
return MessageReaction(value: reaction, count: count, chosenOrder: chosenOrder.flatMap(Int.init))
} else {
return nil
}
}
},
recentPeers: parsedRecentReactions,
topPeers: topPeers
)
}
}
}
@@ -0,0 +1,27 @@
import Foundation
import Postbox
import SwiftSignalKit
func currentWebDocumentsHostDatacenterId(postbox: Postbox, isTestingEnvironment: Bool) -> Signal<Int32, NoError> {
return postbox.transaction { transaction -> Int32 in
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.remoteStorageConfiguration)?.get(RemoteStorageConfiguration.self) {
return entry.webDocumentsHostDatacenterId
} else {
if isTestingEnvironment {
return 2
} else {
return 4
}
}
}
}
func updateRemoteStorageConfiguration(transaction: Transaction, configuration: RemoteStorageConfiguration) {
let current = transaction.getPreferencesEntry(key: PreferencesKeys.remoteStorageConfiguration)?.get(RemoteStorageConfiguration.self)
if let current = current, current == configuration {
return
}
transaction.setPreferencesEntry(key: PreferencesKeys.remoteStorageConfiguration, value: PreferencesEntry(configuration))
}
@@ -0,0 +1,167 @@
import Foundation
import Postbox
import TelegramApi
extension ReplyMarkupButtonAction.PeerTypes {
init(apiType: [Api.InlineQueryPeerType]) {
var rawValue: Int32 = 0
for type in apiType {
switch type {
case .inlineQueryPeerTypePM:
rawValue |= ReplyMarkupButtonAction.PeerTypes.users.rawValue
case .inlineQueryPeerTypeBotPM:
rawValue |= ReplyMarkupButtonAction.PeerTypes.bots.rawValue
case .inlineQueryPeerTypeBroadcast:
rawValue |= ReplyMarkupButtonAction.PeerTypes.channels.rawValue
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
rawValue |= ReplyMarkupButtonAction.PeerTypes.groups.rawValue
case .inlineQueryPeerTypeSameBotPM:
break
}
}
self.init(rawValue: rawValue)
}
}
extension ReplyMarkupButton {
init(apiButton: Api.KeyboardButton) {
switch apiButton {
case let .keyboardButton(text):
self.init(title: text, titleWhenForwarded: nil, action: .text)
case let .keyboardButtonCallback(flags, text, data):
let memory = malloc(data.size)!
memcpy(memory, data.data, data.size)
let dataBuffer = MemoryBuffer(memory: memory, capacity: data.size, length: data.size, freeWhenDone: true)
self.init(title: text, titleWhenForwarded: nil, action: .callback(requiresPassword: (flags & (1 << 0)) != 0, data: dataBuffer))
case let .keyboardButtonRequestGeoLocation(text):
self.init(title: text, titleWhenForwarded: nil, action: .requestMap)
case let .keyboardButtonRequestPhone(text):
self.init(title: text, titleWhenForwarded: nil, action: .requestPhone)
case let .keyboardButtonSwitchInline(flags, text, query, types):
var peerTypes = ReplyMarkupButtonAction.PeerTypes()
if let types = types {
for type in types {
switch type {
case .inlineQueryPeerTypePM:
peerTypes.insert(.users)
case .inlineQueryPeerTypeBotPM:
peerTypes.insert(.bots)
case .inlineQueryPeerTypeBroadcast:
peerTypes.insert(.channels)
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
peerTypes.insert(.groups)
case .inlineQueryPeerTypeSameBotPM:
break
}
}
}
self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query, peerTypes: peerTypes))
case let .keyboardButtonUrl(text, url):
self.init(title: text, titleWhenForwarded: nil, action: .url(url))
case let .keyboardButtonGame(text):
self.init(title: text, titleWhenForwarded: nil, action: .openWebApp)
case let .keyboardButtonBuy(text):
self.init(title: text, titleWhenForwarded: nil, action: .payment)
case let .keyboardButtonUrlAuth(_, text, fwdText, url, buttonId):
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: buttonId))
case let .inputKeyboardButtonUrlAuth(_, text, fwdText, url, _):
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: 0))
case let .keyboardButtonRequestPoll(_, quiz, text):
let isQuiz: Bool? = quiz.flatMap { quiz in
if case .boolTrue = quiz {
return true
} else {
return false
}
}
self.init(title: text, titleWhenForwarded: nil, action: .setupPoll(isQuiz: isQuiz))
case let .keyboardButtonUserProfile(text, userId):
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))))
case let .inputKeyboardButtonUserProfile(text, _):
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))))
case let .keyboardButtonWebView(text, url):
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: false))
case let .keyboardButtonSimpleWebView(text, url):
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: true))
case let .keyboardButtonRequestPeer(text, buttonId, peerType, maxQuantity), let .inputKeyboardButtonRequestPeer(_, text, buttonId, peerType, maxQuantity):
let mappedPeerType: ReplyMarkupButtonRequestPeerType
switch peerType {
case let .requestPeerTypeUser(_, bot, premium):
mappedPeerType = .user(ReplyMarkupButtonRequestPeerType.User(
isBot: bot.flatMap({ $0 == .boolTrue }),
isPremium: premium.flatMap({ $0 == .boolTrue })
))
case let .requestPeerTypeChat(flags, hasUsername, forum, userAdminRights, botAdminRights):
mappedPeerType = .group(ReplyMarkupButtonRequestPeerType.Group(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
isForum: forum.flatMap({ $0 == .boolTrue }),
botParticipant: (flags & (1 << 5)) != 0,
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
case let .requestPeerTypeBroadcast(flags, hasUsername, userAdminRights, botAdminRights):
mappedPeerType = .channel(ReplyMarkupButtonRequestPeerType.Channel(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
}
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId, maxQuantity: maxQuantity))
case let .keyboardButtonCopy(text, payload):
self.init(title: text, titleWhenForwarded: nil, action: .copyText(payload: payload))
}
}
}
extension ReplyMarkupRow {
init(apiRow: Api.KeyboardButtonRow) {
switch apiRow {
case let .keyboardButtonRow(buttons):
self.init(buttons: buttons.map { ReplyMarkupButton(apiButton: $0) })
}
}
}
extension ReplyMarkupMessageAttribute {
convenience init(apiMarkup: Api.ReplyMarkup) {
var rows: [ReplyMarkupRow] = []
var flags = ReplyMarkupMessageFlags()
var placeholder: String?
switch apiMarkup {
case let .replyKeyboardMarkup(markupFlags, apiRows, apiPlaceholder):
rows = apiRows.map { ReplyMarkupRow(apiRow: $0) }
if (markupFlags & (1 << 0)) != 0 {
flags.insert(.fit)
}
if (markupFlags & (1 << 1)) != 0 {
flags.insert(.once)
}
if (markupFlags & (1 << 2)) != 0 {
flags.insert(.personal)
}
if (markupFlags & (1 << 4)) != 0 {
flags.insert(.persistent)
}
placeholder = apiPlaceholder
case let .replyInlineMarkup(apiRows):
rows = apiRows.map { ReplyMarkupRow(apiRow: $0) }
flags.insert(.inline)
case let .replyKeyboardForceReply(forceReplyFlags, apiPlaceholder):
if (forceReplyFlags & (1 << 1)) != 0 {
flags.insert(.once)
}
if (forceReplyFlags & (1 << 2)) != 0 {
flags.insert(.personal)
}
flags.insert(.setupReply)
placeholder = apiPlaceholder
case let .replyKeyboardHide(hideFlags):
if (hideFlags & (1 << 2)) != 0 {
flags.insert(.personal)
}
}
self.init(rows: rows, flags: flags, placeholder: placeholder)
}
}
@@ -0,0 +1,43 @@
import Foundation
import Postbox
import TelegramApi
extension RichText {
init(apiText: Api.RichText) {
switch apiText {
case .textEmpty:
self = .empty
case let .textPlain(text):
self = .plain(text)
case let .textBold(text):
self = .bold(RichText(apiText: text))
case let .textItalic(text):
self = .italic(RichText(apiText: text))
case let .textUnderline(text):
self = .underline(RichText(apiText: text))
case let .textStrike(text):
self = .strikethrough(RichText(apiText: text))
case let .textFixed(text):
self = .fixed(RichText(apiText: text))
case let .textUrl(text, url, webpageId):
self = .url(text: RichText(apiText: text), url: url, webpageId: webpageId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId))
case let .textEmail(text, email):
self = .email(text: RichText(apiText: text), email: email)
case let .textConcat(texts):
self = .concat(texts.map({ RichText(apiText: $0) }))
case let .textSubscript(text):
self = .subscript(RichText(apiText: text))
case let .textSuperscript(text):
self = .superscript(RichText(apiText: text))
case let .textMarked(text):
self = .marked(RichText(apiText: text))
case let .textPhone(text, phone):
self = .phone(text: RichText(apiText: text), phone: phone)
case let .textImage(documentId, w, h):
self = .image(id: MediaId(namespace: Namespaces.Media.CloudFile, id: documentId), dimensions: PixelDimensions(width: w, height: h))
case let .textAnchor(text, name):
self = .anchor(text: RichText(apiText: text), name: name)
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,296 @@
import Foundation
import Postbox
public enum TelegramChannelPermission {
case sendText
case sendPhoto
case sendVideo
case sendSomething
case pinMessages
case manageTopics
case createTopics
case inviteMembers
case editAllMessages
case deleteAllMessages
case banMembers
case addAdmins
case changeInfo
case canBeAnonymous
case manageCalls
case postStories
case editStories
case deleteStories
case manageDirect
}
public extension TelegramChannel {
func hasPermission(_ permission: TelegramChannelPermission, ignoreDefault: Bool = false) -> Bool {
if self.flags.contains(.isCreator) {
if case .canBeAnonymous = permission {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canBeAnonymous)
} else {
return false
}
}
return true
}
switch permission {
case .sendText:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canPostMessages)
} else {
return false
}
} else {
if let _ = self.adminRights {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendText) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendText) && !ignoreDefault {
return false
}
return true
}
case .sendPhoto:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canPostMessages)
} else {
return false
}
} else {
if let _ = self.adminRights {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendPhotos) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendPhotos) && !ignoreDefault {
return false
}
return true
}
case .sendVideo:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canPostMessages)
} else {
return false
}
} else {
if let _ = self.adminRights {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendVideos) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendVideos) && !ignoreDefault {
return false
}
return true
}
case .sendSomething:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canPostMessages)
} else {
return false
}
} else {
if let _ = self.adminRights {
return true
}
let flags: TelegramChatBannedRightsFlags = [
.banSendText,
.banSendInstantVideos,
.banSendVoice,
.banSendPhotos,
.banSendVideos,
.banSendStickers,
.banSendPolls,
.banSendFiles,
.banSendInline,
.banSendMusic
]
if let bannedRights = self.bannedRights, bannedRights.flags.intersection(flags) == flags {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags && !ignoreDefault {
return false
}
return true
}
case .pinMessages:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canPinMessages) || adminRights.rights.contains(.canEditMessages)
} else {
return false
}
} else {
if let adminRights = self.adminRights, adminRights.rights.contains(.canPinMessages) {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banPinMessages) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banPinMessages) {
return false
}
return true
}
case .manageTopics:
if self.flags.contains(.isCreator) {
return true
}
if self.adminRights == nil {
return false
}
if let adminRights = self.adminRights, adminRights.rights.contains(.canManageTopics) {
return true
}
return false
case .createTopics:
if self.flags.contains(.isCreator) {
return true
}
if let adminRights = self.adminRights, adminRights.rights.contains(.canManageTopics) {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banManageTopics) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banManageTopics) {
return false
}
return true
case .inviteMembers:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canInviteUsers)
} else {
return false
}
} else {
if let adminRights = self.adminRights, adminRights.rights.contains(.canInviteUsers) {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banAddMembers) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banAddMembers) {
return false
}
return true
}
case .editAllMessages:
if let adminRights = self.adminRights, adminRights.rights.contains(.canEditMessages) {
return true
}
return false
case .deleteAllMessages:
if let adminRights = self.adminRights, adminRights.rights.contains(.canDeleteMessages) {
return true
}
return false
case .banMembers:
if let adminRights = self.adminRights, adminRights.rights.contains(.canBanUsers) {
return true
}
return false
case .changeInfo:
if case .broadcast = self.info {
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canChangeInfo)
} else {
return false
}
} else {
if let adminRights = self.adminRights, adminRights.rights.contains(.canChangeInfo) {
return true
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banChangeInfo) {
return false
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banChangeInfo) {
return false
}
return true
}
case .addAdmins:
if let adminRights = self.adminRights, adminRights.rights.contains(.canAddAdmins) {
return true
}
return false
case .manageDirect:
if let adminRights = self.adminRights, adminRights.rights.contains(.canManageDirect) {
return true
}
return false
case .manageCalls:
if let adminRights = self.adminRights, adminRights.rights.contains(.canManageCalls) {
return true
}
return false
case .canBeAnonymous:
if let adminRights = self.adminRights, adminRights.rights.contains(.canBeAnonymous) {
return true
}
return false
case .postStories:
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canPostStories)
} else {
return false
}
case .editStories:
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canEditStories)
} else {
return false
}
case .deleteStories:
if let adminRights = self.adminRights {
return adminRights.rights.contains(.canDeleteStories)
} else {
return false
}
}
}
func hasBannedPermission(_ rights: TelegramChatBannedRightsFlags, ignoreDefault: Bool = false) -> (Int32, Bool)? {
if self.flags.contains(.isCreator) {
return nil
}
if let _ = self.adminRights {
return nil
}
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(rights) && !ignoreDefault {
return (Int32.max, false)
}
if let bannedRights = self.bannedRights, bannedRights.flags.contains(rights) {
return (bannedRights.untilDate, true)
}
return nil
}
var isRestrictedBySlowmode: Bool {
if self.flags.contains(.isCreator) {
return false
}
if let _ = self.adminRights {
return false
}
if case let .group(group) = self.info {
return group.flags.contains(.slowModeEnabled)
} else {
return false
}
}
}
@@ -0,0 +1,23 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramChatAdminRights {
init?(apiAdminRights: Api.ChatAdminRights) {
switch apiAdminRights {
case let .chatAdminRights(flags):
if flags == 0 {
return nil
}
let filteredFlags = flags & (~(1 << 12))
self.init(rights: TelegramChatAdminRightsFlags(rawValue: filteredFlags))
}
}
var apiAdminRights: Api.ChatAdminRights {
var filteredFlags = self.rights.rawValue
filteredFlags |= 1 << 12
return .chatAdminRights(flags: filteredFlags)
}
}
@@ -0,0 +1,23 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramChatBannedRights {
init(apiBannedRights: Api.ChatBannedRights) {
switch apiBannedRights {
case let .chatBannedRights(flags, untilDate):
var effectiveFlags = TelegramChatBannedRightsFlags(rawValue: flags)
effectiveFlags.remove(.banSendMedia)
effectiveFlags.remove(TelegramChatBannedRightsFlags(rawValue: 1 << 1))
self.init(flags: effectiveFlags, untilDate: untilDate)
}
}
var apiBannedRights: Api.ChatBannedRights {
var effectiveFlags = self.flags
effectiveFlags.remove(.banSendMedia)
effectiveFlags.remove(TelegramChatBannedRightsFlags(rawValue: 1 << 1))
return .chatBannedRights(flags: effectiveFlags.rawValue, untilDate: self.untilDate)
}
}
@@ -0,0 +1,26 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramExtendedMedia {
init?(apiExtendedMedia: Api.MessageExtendedMedia, peerId: PeerId) {
switch apiExtendedMedia {
case let .messageExtendedMediaPreview(_, width, height, thumb, videoDuration):
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
var immediateThumbnailData: Data?
if let thumb = thumb, case let .photoStrippedSize(_, bytes) = thumb {
immediateThumbnailData = bytes.makeData()
}
self = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case let .messageExtendedMedia(apiMedia):
if let media = textMediaAndExpirationTimerFromApiMedia(apiMedia, peerId).media {
self = .full(media: media)
} else {
return nil
}
}
}
}
@@ -0,0 +1,55 @@
import Foundation
import Postbox
// Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker
private final class LinkHelperClass: NSObject {
}
public extension TelegramGroup {
enum Permission {
case sendSomething
}
func hasPermission(_ permission: Permission) -> Bool {
switch permission {
case .sendSomething:
switch self.role {
case .creator, .admin:
return true
default:
break
}
let flags: TelegramChatBannedRightsFlags = [
.banSendText,
.banSendInstantVideos,
.banSendVoice,
.banSendPhotos,
.banSendVideos,
.banSendStickers,
.banSendPolls,
.banSendFiles,
.banSendInline,
.banSendMusic
]
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags {
return false
}
return true
}
}
func hasBannedPermission(_ rights: TelegramChatBannedRightsFlags) -> Bool {
switch self.role {
case .creator, .admin:
return false
default:
if let bannedRights = self.defaultBannedRights {
return bannedRights.flags.contains(rights)
} else {
return false
}
}
}
}
@@ -0,0 +1,23 @@
import Foundation
import Postbox
import TelegramApi
public class InvertMediaMessageAttribute: MessageAttribute, Equatable {
public let associatedPeerIds: [PeerId] = []
public let associatedMediaIds: [MediaId] = []
public init() {
}
required public init(decoder: PostboxDecoder) {
}
public func encode(_ encoder: PostboxEncoder) {
}
public static func ==(lhs: InvertMediaMessageAttribute, rhs: InvertMediaMessageAttribute) -> Bool {
return true
}
}
@@ -0,0 +1,339 @@
import Foundation
import Postbox
import TelegramApi
func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMediaAction? {
switch action {
case let .messageActionChannelCreate(title):
return TelegramMediaAction(action: .groupCreated(title: title))
case let .messageActionChannelMigrateFrom(title, chatId):
return TelegramMediaAction(action: .channelMigratedFromGroup(title: title, groupId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))))
case let .messageActionChatAddUser(users):
return TelegramMediaAction(action: .addedMembers(peerIds: users.map({ PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) })))
case let .messageActionChatCreate(title, _):
return TelegramMediaAction(action: .groupCreated(title: title))
case .messageActionChatDeletePhoto:
return TelegramMediaAction(action: .photoUpdated(image: nil))
case let .messageActionChatDeleteUser(userId):
return TelegramMediaAction(action: .removedMembers(peerIds: [PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))]))
case let .messageActionChatEditPhoto(photo):
return TelegramMediaAction(action: .photoUpdated(image: telegramMediaImageFromApiPhoto(photo)))
case let .messageActionChatEditTitle(title):
return TelegramMediaAction(action: .titleUpdated(title: title))
case let .messageActionChatJoinedByLink(inviterId):
return TelegramMediaAction(action: .joinedByLink(inviter: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId))))
case let .messageActionChatMigrateTo(channelId):
return TelegramMediaAction(action: .groupMigratedToChannel(channelId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))))
case .messageActionHistoryClear:
return TelegramMediaAction(action: .historyCleared)
case .messageActionPinMessage:
return TelegramMediaAction(action: .pinnedMessageUpdated)
case let .messageActionGameScore(gameId, score):
return TelegramMediaAction(action: .gameScore(gameId: gameId, score: score))
case let .messageActionPhoneCall(flags, callId, reason, duration):
var discardReason: PhoneCallDiscardReason?
if let reason = reason {
discardReason = PhoneCallDiscardReason(apiReason: reason)
}
let isVideo = (flags & (1 << 2)) != 0
return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration, isVideo: isVideo))
case .messageActionEmpty:
return nil
case let .messageActionPaymentSent(flags, currency, totalAmount, invoiceSlug, subscriptionUntilDate):
let _ = subscriptionUntilDate
let isRecurringInit = (flags & (1 << 2)) != 0
let isRecurringUsed = (flags & (1 << 3)) != 0
return TelegramMediaAction(action: .paymentSent(currency: currency, totalAmount: totalAmount, invoiceSlug: invoiceSlug, isRecurringInit: isRecurringInit, isRecurringUsed: isRecurringUsed))
case .messageActionPaymentSentMe:
return nil
case .messageActionScreenshotTaken:
return TelegramMediaAction(action: .historyScreenshot)
case let .messageActionCustomAction(message):
return TelegramMediaAction(action: .customText(text: message, entities: [], additionalAttributes: nil))
case let .messageActionBotAllowed(flags, domain, app):
if let domain = domain {
return TelegramMediaAction(action: .botDomainAccessGranted(domain: domain))
} else {
var appName: String?
if case let .botApp(_, _, _, _, appNameValue, _, _, _, _) = app {
appName = appNameValue
}
var type: BotSendMessageAccessGrantedType?
if (flags & (1 << 1)) != 0 {
type = .attachMenu
}
if (flags & (1 << 3)) != 0 {
type = .request
}
return TelegramMediaAction(action: .botAppAccessGranted(appName: appName, type: type))
}
case .messageActionSecureValuesSentMe:
return nil
case let .messageActionSecureValuesSent(types):
return TelegramMediaAction(action: .botSentSecureValues(types: types.map(SentSecureValueType.init)))
case .messageActionContactSignUp:
return TelegramMediaAction(action: .peerJoined)
case let .messageActionGeoProximityReached(fromId, toId, distance):
return TelegramMediaAction(action: .geoProximityReached(from: fromId.peerId, to: toId.peerId, distance: distance))
case let .messageActionGroupCall(_, call, duration):
switch call {
case let .inputGroupCall(id, accessHash):
return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: nil, duration: duration))
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
}
case let .messageActionInviteToGroupCall(call, userIds):
switch call {
case let .inputGroupCall(id, accessHash):
return TelegramMediaAction(action: .inviteToGroupPhoneCall(callId: id, accessHash: accessHash, peerIds: userIds.map { userId in
PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
}))
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
}
case let .messageActionSetMessagesTTL(_, period, autoSettingFrom):
return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: period, autoSettingSource: autoSettingFrom.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }))
case let .messageActionGroupCallScheduled(call, scheduleDate):
switch call {
case let .inputGroupCall(id, accessHash):
return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil))
case .inputGroupCallSlug, .inputGroupCallInviteMessage:
return nil
}
case let .messageActionSetChatTheme(chatTheme):
if let chatTheme = ChatTheme(apiChatTheme: chatTheme) {
return TelegramMediaAction(action: .setChatTheme(chatTheme: chatTheme))
} else {
return nil
}
case .messageActionChatJoinedByRequest:
return TelegramMediaAction(action: .joinedByRequest)
case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text):
return TelegramMediaAction(action: .webViewData(text))
case let .messageActionGiftPremium(_, currency, amount, days, cryptoCurrency, cryptoAmount, message):
let text: String?
let entities: [MessageTextEntity]?
switch message {
case let .textWithEntities(textValue, entitiesValue):
text = textValue
entities = messageTextEntitiesFromApiEntities(entitiesValue)
default:
text = nil
entities = nil
}
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, days: days, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
case let .messageActionGiftStars(_, currency, amount, stars, cryptoCurrency, cryptoAmount, transactionId):
return TelegramMediaAction(action: .giftStars(currency: currency, amount: amount, count: stars, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId))
case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId):
return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId))
case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, hidden):
var components: [TelegramMediaActionType.ForumTopicEditComponent] = []
if let title = title {
components.append(.title(title))
}
if (flags & (1 << 1)) != 0 {
components.append(.iconFileId(iconEmojiId == 0 ? nil : iconEmojiId))
}
if let closed = closed {
components.append(.isClosed(closed == .boolTrue))
}
if let hidden = hidden {
components.append(.isHidden(hidden == .boolTrue))
}
return TelegramMediaAction(action: .topicEdited(components: components))
case let .messageActionSuggestProfilePhoto(photo):
return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo)))
case let .messageActionRequestedPeer(buttonId, peers):
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerIds: peers.map { $0.peerId }))
case let .messageActionRequestedPeerSentMe(buttonId, _):
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerIds: []))
case let .messageActionSetChatWallPaper(flags, wallpaper):
if (flags & (1 << 0)) != 0 {
return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper)))
} else {
return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper), forBoth: (flags & (1 << 1)) != 0))
}
case let .messageActionGiftCode(flags, boostPeer, months, slug, currency, amount, cryptoCurrency, cryptoAmount, message):
let text: String?
let entities: [MessageTextEntity]?
switch message {
case let .textWithEntities(textValue, entitiesValue):
text = textValue
entities = messageTextEntitiesFromApiEntities(entitiesValue)
default:
text = nil
entities = nil
}
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 5)) != 0, boostPeerId: boostPeer?.peerId, months: months, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
case let .messageActionGiveawayLaunch(_, stars):
return TelegramMediaAction(action: .giveawayLaunched(stars: stars))
case let .messageActionGiveawayResults(flags, winners, unclaimed):
return TelegramMediaAction(action: .giveawayResults(winners: winners, unclaimed: unclaimed, stars: (flags & (1 << 0)) != 0))
case let .messageActionBoostApply(boosts):
return TelegramMediaAction(action: .boostsApplied(boosts: boosts))
case let .messageActionPaymentRefunded(_, peer, currency, totalAmount, payload, charge):
let transactionId: String
switch charge {
case let .paymentCharge(id, _):
transactionId = id
}
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeMessageId, upgradeStars, fromId, peer, savedId, prepaidUpgradeHash, giftMessageId, toId, number):
let text: String?
let entities: [MessageTextEntity]?
switch message {
case let .textWithEntities(textValue, entitiesValue):
text = textValue
entities = messageTextEntitiesFromApiEntities(entitiesValue)
default:
text = nil
entities = nil
}
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId, upgradeSeparate: (flags & (1 << 16)) != 0, isAuctionAcquired: (flags & (1 << 17)) != 0, toPeerId: toId?.peerId, number: number))
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars):
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0))
case let .messageActionPaidMessagesRefunded(count, stars):
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
case let .messageActionPaidMessagesPrice(flags, stars):
let broadcastMessagesAllowed = (flags & (1 << 0)) != 0
return TelegramMediaAction(action: .paidMessagesPriceEdited(stars: stars, broadcastMessagesAllowed: broadcastMessagesAllowed))
case let .messageActionConferenceCall(flags, callId, duration, otherParticipants):
let isMissed = (flags & (1 << 0)) != 0
let isActive = (flags & (1 << 1)) != 0
let isVideo = (flags & (1 << 4)) != 0
var mappedFlags = TelegramMediaActionType.ConferenceCall.Flags()
if isMissed {
mappedFlags.insert(.isMissed)
}
if isActive {
mappedFlags.insert(.isActive)
}
if isVideo {
mappedFlags.insert(.isVideo)
}
return TelegramMediaAction(action: .conferenceCall(TelegramMediaActionType.ConferenceCall(
callId: callId,
duration: duration,
flags: mappedFlags,
otherParticipants: otherParticipants.flatMap({ return $0.map(\.peerId) }) ?? []
)))
case let .messageActionTodoCompletions(completed, incompleted):
return TelegramMediaAction(action: .todoCompletions(completed: completed, incompleted: incompleted))
case let .messageActionTodoAppendTasks(list):
return TelegramMediaAction(action: .todoAppendTasks(list.map { TelegramMediaTodo.Item(apiItem: $0) }))
case let .messageActionSuggestedPostApproval(flags, rejectComment, scheduleDate, starsAmount):
let status: TelegramMediaActionType.SuggestedPostApprovalStatus
if (flags & (1 << 0)) != 0 {
let reason: TelegramMediaActionType.SuggestedPostApprovalStatus.RejectionReason
if (flags & (1 << 1)) != 0 {
let balanceNeeded: CurrencyAmount
switch starsAmount {
case .none:
balanceNeeded = CurrencyAmount(amount: .zero, currency: .stars)
case let .starsAmount(amount, nanos):
balanceNeeded = CurrencyAmount(amount: StarsAmount(value: amount, nanos: nanos), currency: .stars)
case let .starsTonAmount(amount):
balanceNeeded = CurrencyAmount(amount: StarsAmount(value: amount, nanos: 0), currency: .ton)
}
reason = .lowBalance(balanceNeeded: balanceNeeded)
} else {
reason = .generic
}
status = .rejected(reason: reason, comment: rejectComment)
} else if (flags & (1 << 1)) != 0 {
let amountValue: CurrencyAmount
switch starsAmount {
case .none:
amountValue = CurrencyAmount(amount: .zero, currency: .stars)
case let .starsAmount(amount, nanos):
amountValue = CurrencyAmount(amount: StarsAmount(value: amount, nanos: nanos), currency: .stars)
case let .starsTonAmount(amount):
amountValue = CurrencyAmount(amount: StarsAmount(value: amount, nanos: 0), currency: .ton)
}
status = .rejected(reason: .lowBalance(balanceNeeded: amountValue), comment: nil)
} else {
status = .approved(timestamp: scheduleDate, amount: starsAmount.flatMap(CurrencyAmount.init(apiAmount:)))
}
return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status))
case let .messageActionGiftTon(_, currency, amount, cryptoCurrency, cryptoAmount, transactionId):
return TelegramMediaAction(action: .giftTon(currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId))
case let .messageActionSuggestedPostSuccess(price):
return TelegramMediaAction(action: .suggestedPostSuccess(amount: CurrencyAmount(apiAmount: price)))
case let .messageActionSuggestedPostRefund(flags):
return TelegramMediaAction(action: .suggestedPostRefund(TelegramMediaActionType.SuggestedPostRefund(isUserInitiated: (flags & (1 << 0)) != 0)))
case let .messageActionSuggestBirthday(birthday):
return TelegramMediaAction(action: .suggestedBirthday(TelegramBirthday(apiBirthday: birthday)))
case let .messageActionStarGiftPurchaseOffer(flags, apiGift, price, expiresAt):
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return TelegramMediaAction(action: .starGiftPurchaseOffer(gift: gift, amount: CurrencyAmount(apiAmount: price), expireDate: expiresAt, isAccepted: (flags & (1 << 0)) != 0, isDeclined: (flags & (1 << 1)) != 0))
case let .messageActionStarGiftPurchaseOfferDeclined(flags, apiGift, price):
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return TelegramMediaAction(action: .starGiftPurchaseOfferDeclined(gift: gift, amount: CurrencyAmount(apiAmount: price), hasExpired: (flags & (1 << 0)) != 0))
}
}
extension PhoneCallDiscardReason {
init(apiReason: Api.PhoneCallDiscardReason) {
switch apiReason {
case .phoneCallDiscardReasonBusy:
self = .busy
case .phoneCallDiscardReasonDisconnect:
self = .disconnect
case .phoneCallDiscardReasonHangup:
self = .hangup
case .phoneCallDiscardReasonMissed:
self = .missed
case .phoneCallDiscardReasonMigrateConferenceCall:
self = .hangup
}
}
}
extension SentSecureValueType {
init(apiType: Api.SecureValueType) {
switch apiType {
case .secureValueTypePersonalDetails:
self = .personalDetails
case .secureValueTypePassport:
self = .passport
case .secureValueTypeDriverLicense:
self = .driversLicense
case .secureValueTypeIdentityCard:
self = .idCard
case .secureValueTypeAddress:
self = .address
case .secureValueTypeBankStatement:
self = .bankStatement
case .secureValueTypeUtilityBill:
self = .utilityBill
case .secureValueTypeRentalAgreement:
self = .rentalAgreement
case .secureValueTypePhone:
self = .phone
case .secureValueTypeEmail:
self = .email
case .secureValueTypeInternalPassport:
self = .internalPassport
case .secureValueTypePassportRegistration:
self = .passportRegistration
case .secureValueTypeTemporaryRegistration:
self = .temporaryRegistration
}
}
}
@@ -0,0 +1,228 @@
import Foundation
import Postbox
import TelegramApi
func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> PixelDimensions? {
for attribute in attributes {
switch attribute {
case let .Video(_, size, _, _, _, _):
return size
case let .ImageSize(size):
return size
default:
break
}
}
return nil
}
func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> Double? {
for attribute in attributes {
switch attribute {
case let .Video(duration, _, _, _, _, _):
return duration
case let .Audio(_, duration, _, _, _):
return Double(duration)
default:
break
}
}
return nil
}
public extension TelegramMediaFile {
var dimensions: PixelDimensions? {
if let value = dimensionsForFileAttributes(self.attributes) {
return value
} else if self.isAnimatedSticker {
return PixelDimensions(width: 512, height: 512)
} else {
return nil
}
}
var duration: Double? {
return durationForFileAttributes(self.attributes)
}
}
public extension TelegramMediaFile {
func isValidForDisplay(chatPeerId: PeerId) -> Bool {
if chatPeerId.namespace == Namespaces.Peer.SecretChat {
if self.isAnimatedSticker {
if !self.attributes.contains(where: { attribute in
if case .hintIsValidated = attribute {
return true
}
return false
}) {
return false
}
}
}
return true
}
}
extension StickerPackReference {
init?(apiInputSet: Api.InputStickerSet) {
switch apiInputSet {
case .inputStickerSetEmpty:
return nil
case let .inputStickerSetID(id, accessHash):
self = .id(id: id, accessHash: accessHash)
case let .inputStickerSetShortName(shortName):
self = .name(shortName)
case .inputStickerSetAnimatedEmoji:
self = .animatedEmoji
case let .inputStickerSetDice(emoticon):
self = .dice(emoticon)
case .inputStickerSetAnimatedEmojiAnimations:
self = .animatedEmojiAnimations
case .inputStickerSetPremiumGifts:
self = .premiumGifts
case .inputStickerSetEmojiGenericAnimations:
self = .emojiGenericAnimations
case .inputStickerSetEmojiDefaultStatuses:
self = .iconStatusEmoji
case .inputStickerSetEmojiChannelDefaultStatuses:
self = .iconChannelStatusEmoji
case .inputStickerSetEmojiDefaultTopicIcons:
self = .iconTopicEmoji
case .inputStickerSetTonGifts:
self = .tonGifts
}
}
}
extension StickerMaskCoords {
init(apiMaskCoords: Api.MaskCoords) {
switch apiMaskCoords {
case let .maskCoords(n, x, y, zoom):
self.init(n: n, x: x, y: y, zoom: zoom)
}
}
}
func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAttribute]) -> [TelegramMediaFileAttribute] {
var result: [TelegramMediaFileAttribute] = []
for attribute in attributes {
switch attribute {
case let .documentAttributeFilename(fileName):
result.append(.FileName(fileName: fileName))
case let .documentAttributeSticker(_, alt, stickerSet, maskCoords):
result.append(.Sticker(displayText: alt, packReference: StickerPackReference(apiInputSet: stickerSet), maskData: maskCoords.flatMap(StickerMaskCoords.init)))
case .documentAttributeHasStickers:
result.append(.HasLinkedStickers)
case let .documentAttributeImageSize(w, h):
result.append(.ImageSize(size: PixelDimensions(width: w, height: h)))
case .documentAttributeAnimated:
result.append(.Animated)
case let .documentAttributeVideo(flags, duration, w, h, preloadSize, videoStart, videoCodec):
var videoFlags = TelegramMediaVideoFlags()
if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo)
}
if (flags & (1 << 1)) != 0 {
videoFlags.insert(.supportsStreaming)
}
if (flags & (1 << 3)) != 0 {
videoFlags.insert(.isSilent)
}
result.append(.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize, coverTime: videoStart, videoCodec: videoCodec))
case let .documentAttributeAudio(flags, duration, title, performer, waveform):
let isVoice = (flags & (1 << 10)) != 0
let waveformBuffer: Data? = waveform?.makeData()
result.append(.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: performer, waveform: waveformBuffer))
case let .documentAttributeCustomEmoji(flags, alt, stickerSet):
let isFree = (flags & (1 << 0)) != 0
let isSingleColor = (flags & (1 << 1)) != 0
result.append(.CustomEmoji(isPremium: !isFree, isSingleColor: isSingleColor, alt: alt, packReference: StickerPackReference(apiInputSet: stickerSet)))
}
}
return result
}
public func fileNameFromFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> String? {
for attribute in attributes {
if case let .FileName(value) = attribute {
return value
}
}
return nil
}
func telegramMediaFileThumbnailRepresentationsFromApiSizes(datacenterId: Int32, documentId: Int64, accessHash: Int64, fileReference: Data?, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [TelegramMediaImageRepresentation]) {
var immediateThumbnailData: Data?
var representations: [TelegramMediaImageRepresentation] = []
for size in sizes {
switch size {
case let .photoCachedSize(type, w, h, _):
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
case let .photoSize(type, w, h, _):
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
case let .photoSizeProgressive(type, w, h, sizes):
let resource = CloudDocumentSizeMediaResource(datacenterId: datacenterId, documentId: documentId, accessHash: accessHash, sizeSpec: type, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes, immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
case let .photoPathSize(_, data):
immediateThumbnailData = data.makeData()
case let .photoStrippedSize(_, data):
immediateThumbnailData = data.makeData()
case .photoSizeEmpty:
break
}
}
return (immediateThumbnailData, representations)
}
func telegramMediaFileFromApiDocument(_ document: Api.Document, altDocuments: [Api.Document]?, videoCover: Api.Photo? = nil) -> TelegramMediaFile? {
switch document {
case let .document(_, id, accessHash, fileReference, _, mimeType, size, thumbs, videoThumbs, dcId, attributes):
var parsedAttributes = telegramMediaFileAttributesFromApiAttributes(attributes)
var isSticker = false
var isAnimated = false
for attribute in parsedAttributes {
switch attribute {
case .Sticker:
isSticker = true
case .Animated:
isAnimated = true
default:
break
}
}
if isSticker && isAnimated {
parsedAttributes.append(.hintIsValidated)
}
let (immediateThumbnail, previewRepresentations) = telegramMediaFileThumbnailRepresentationsFromApiSizes(datacenterId: dcId, documentId: id, accessHash: accessHash, fileReference: fileReference.makeData(), sizes: thumbs ?? [])
var videoThumbnails: [TelegramMediaFile.VideoThumbnail] = []
if let videoThumbs = videoThumbs {
for thumb in videoThumbs {
switch thumb {
case let .videoSize(_, type, w, h, _, _):
let resource: TelegramMediaResource
resource = CloudDocumentSizeMediaResource(datacenterId: dcId, documentId: id, accessHash: accessHash, sizeSpec: type, fileReference: fileReference.makeData())
videoThumbnails.append(TelegramMediaFile.VideoThumbnail(
dimensions: PixelDimensions(width: w, height: h),
resource: resource))
case .videoSizeEmojiMarkup, .videoSizeStickerMarkup:
break
}
}
}
var alternativeRepresentations: [TelegramMediaFile] = []
if let altDocuments {
alternativeRepresentations = altDocuments.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
}
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: size, fileReference: fileReference.makeData(), fileName: fileNameFromFileAttributes(parsedAttributes)), previewRepresentations: previewRepresentations, videoThumbnails: videoThumbnails, videoCover: videoCover.flatMap(telegramMediaImageFromApiPhoto), immediateThumbnailData: immediateThumbnail, mimeType: mimeType, size: size, attributes: parsedAttributes, alternativeRepresentations: alternativeRepresentations)
case .documentEmpty:
return nil
}
}
@@ -0,0 +1,17 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramMediaGame {
convenience init(apiGame: Api.Game) {
switch apiGame {
case let .game(_, id, accessHash, shortName, title, description, photo, document):
var file: TelegramMediaFile?
if let document = document {
file = telegramMediaFileFromApiDocument(document, altDocuments: [])
}
self.init(gameId: id, accessHash: accessHash, name: shortName, title: title, description: description, image: telegramMediaImageFromApiPhoto(photo), file: file)
}
}
}
@@ -0,0 +1,67 @@
import Foundation
import Postbox
import TelegramApi
func telegramMediaImageRepresentationsFromApiSizes(datacenterId: Int32, photoId: Int64, accessHash: Int64, fileReference: Data?, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [TelegramMediaImageRepresentation]) {
var immediateThumbnailData: Data?
var representations: [TelegramMediaImageRepresentation] = []
for size in sizes {
switch size {
case let .photoCachedSize(type, w, h, _):
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, size: nil, fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
case let .photoSize(type, w, h, size):
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, size: Int64(size), fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
case let .photoSizeProgressive(type, w, h, sizes):
if !sizes.isEmpty {
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, size: Int64(sizes[sizes.count - 1]), fileReference: fileReference)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes, immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
}
case let .photoStrippedSize(_, data):
immediateThumbnailData = data.makeData()
case .photoPathSize:
break
case .photoSizeEmpty:
break
}
}
return (immediateThumbnailData, representations)
}
func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? {
switch photo {
case let .photo(flags, id, accessHash, fileReference, _, sizes, videoSizes, dcId):
let (immediateThumbnailData, representations) = telegramMediaImageRepresentationsFromApiSizes(datacenterId: dcId, photoId: id, accessHash: accessHash, fileReference: fileReference.makeData(), sizes: sizes)
var imageFlags: TelegramMediaImageFlags = []
let hasStickers = (flags & (1 << 0)) != 0
if hasStickers {
imageFlags.insert(.hasStickers)
}
var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = []
var emojiMarkup: TelegramMediaImage.EmojiMarkup?
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 let .videoSizeEmojiMarkup(fileId, backgroundColors):
emojiMarkup = TelegramMediaImage.EmojiMarkup(content: .emoji(fileId: fileId), backgroundColors: backgroundColors)
case let .videoSizeStickerMarkup(stickerSet, fileId, backgroundColors):
if let packReference = StickerPackReference(apiInputSet: stickerSet) {
emojiMarkup = TelegramMediaImage.EmojiMarkup(content: .sticker(packReference: packReference, fileId: fileId), backgroundColors: backgroundColors)
}
}
}
}
return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: representations, videoRepresentations: videoRepresentations, immediateThumbnailData: immediateThumbnailData, emojiMarkup: emojiMarkup, reference: .cloud(imageId: id, accessHash: accessHash, fileReference: fileReference.makeData()), partialReference: nil, flags: imageFlags)
case .photoEmpty:
return nil
}
}
@@ -0,0 +1,25 @@
import Foundation
import Postbox
import TelegramApi
func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, address: String?, provider: String?, venueId: String?, venueType: String?, liveBroadcastingTimeout: Int32?, liveProximityNotificationRadius: Int32?, heading: Int32?) -> TelegramMediaMap {
var venue: MapVenue?
if let title = title {
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
}
switch geo {
case let .geoPoint(_, long, lat, _, accuracyRadius):
return TelegramMediaMap(latitude: lat, longitude: long, heading: heading, accuracyRadius: accuracyRadius.flatMap { Double($0) }, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout, liveProximityNotificationRadius: liveProximityNotificationRadius)
case .geoPointEmpty:
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout, liveProximityNotificationRadius: liveProximityNotificationRadius)
}
}
func mapGeoAddressFromApiGeoPointAddress(_ geo: Api.GeoPointAddress) -> MapGeoAddress {
switch geo {
case let .geoPointAddress(_, countryIso2, state, city, street):
return MapGeoAddress(country: countryIso2, state: state, city: city, street: street)
}
}
@@ -0,0 +1,50 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramMediaPollOption {
init(apiOption: Api.PollAnswer) {
switch apiOption {
case let .pollAnswer(text, option):
let answerText: String
let answerEntities: [MessageTextEntity]
switch text {
case let .textWithEntities(text, entities):
answerText = text
answerEntities = messageTextEntitiesFromApiEntities(entities)
}
self.init(text: answerText, entities: answerEntities, opaqueIdentifier: option.makeData())
}
}
var apiOption: Api.PollAnswer {
return .pollAnswer(text: .textWithEntities(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary())), option: Buffer(data: self.opaqueIdentifier))
}
}
extension TelegramMediaPollOptionVoters {
init(apiVoters: Api.PollAnswerVoters) {
switch apiVoters {
case let .pollAnswerVoters(flags, option, voters):
self.init(selected: (flags & (1 << 0)) != 0, opaqueIdentifier: option.makeData(), count: voters, isCorrect: (flags & (1 << 1)) != 0)
}
}
}
extension TelegramMediaPollResults {
init(apiResults: Api.PollResults) {
switch apiResults {
case let .pollResults(_, results, totalVoters, recentVoters, solution, solutionEntities):
var parsedSolution: TelegramMediaPollResults.Solution?
if let solution = solution, let solutionEntities = solutionEntities, !solution.isEmpty {
parsedSolution = TelegramMediaPollResults.Solution(text: solution, entities: messageTextEntitiesFromApiEntities(solutionEntities))
}
self.init(voters: results.flatMap({ $0.map(TelegramMediaPollOptionVoters.init(apiVoters:)) }), totalVoters: totalVoters, recentVoters: recentVoters.flatMap { recentVoters in
return recentVoters.map { $0.peerId }
} ?? [], solution: parsedSolution)
}
}
}
@@ -0,0 +1,33 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramMediaTodo.Item {
init(apiItem: Api.TodoItem) {
switch apiItem {
case let .todoItem(id, title):
let itemText: String
let itemEntities: [MessageTextEntity]
switch title {
case let .textWithEntities(text, entities):
itemText = text
itemEntities = messageTextEntitiesFromApiEntities(entities)
}
self.init(text: itemText, entities: itemEntities, id: id)
}
}
var apiItem: Api.TodoItem {
return .todoItem(id: self.id, title: .textWithEntities(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary())))
}
}
extension TelegramMediaTodo.Completion {
init(apiCompletion: Api.TodoCompletion) {
switch apiCompletion {
case let .todoCompletion(id, completedBy, date):
self.init(id: id, date: date, completedBy: completedBy.peerId)
}
}
}
@@ -0,0 +1,15 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramMediaWebFile {
convenience init(_ document: Api.WebDocument) {
switch document {
case let .webDocument(url, accessHash, size, mimeType, attributes):
self.init(resource: WebFileReferenceMediaResource(url: url, size: Int64(size), accessHash: accessHash), mimeType: mimeType, size: size, attributes: telegramMediaFileAttributesFromApiAttributes(attributes))
case let .webDocumentNoProxy(url, size, mimeType, attributes):
self.init(resource: HttpReferenceMediaResource(url: url, size: Int64(size)), mimeType: mimeType, size: size, attributes: telegramMediaFileAttributesFromApiAttributes(attributes))
}
}
}
@@ -0,0 +1,143 @@
import Foundation
import Postbox
import TelegramApi
func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPageAttribute) -> TelegramMediaWebpageAttribute? {
switch attribute {
case let .webPageAttributeTheme(_, documents, settings):
var files: [TelegramMediaFile] = []
if let documents = documents {
files = documents.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
}
return .theme(TelegraMediaWebpageThemeAttribute(files: files, settings: settings.flatMap { TelegramThemeSettings(apiThemeSettings: $0) }))
case let .webPageAttributeStickerSet(apiFlags, stickers):
var flags = TelegramMediaWebpageStickerPackAttribute.Flags()
if (apiFlags & (1 << 0)) != 0 {
flags.insert(.isEmoji)
}
if (apiFlags & (1 << 1)) != 0 {
flags.insert(.isTemplate)
}
var files: [TelegramMediaFile] = []
files = stickers.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
return .stickerPack(TelegramMediaWebpageStickerPackAttribute(flags: flags, files: files))
case let .webPageAttributeUniqueStarGift(gift):
if let starGift = StarGift(apiStarGift: gift) {
return .starGift(TelegramMediaWebpageStarGiftAttribute(gift: starGift))
}
return nil
case let .webPageAttributeStarGiftCollection(icons):
var files: [TelegramMediaFile] = []
files = icons.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
return .giftCollection(TelegramMediaWebpageGiftCollectionAttribute(files: files))
case let .webPageAttributeStarGiftAuction(apiGift, endDate):
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return .giftAuction(TelegramMediaWebpageGiftAuctionAttribute(gift: gift, endDate: endDate))
case .webPageAttributeStory:
return nil
}
}
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? {
switch webpage {
case .webPageNotModified:
return nil
case let .webPagePending(flags, id, url, date):
let _ = flags
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url))
case let .webPage(flags, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage, attributes):
var embedSize: PixelDimensions?
if let embedWidth = embedWidth, let embedHeight = embedHeight {
embedSize = PixelDimensions(width: embedWidth, height: embedHeight)
}
var webpageDuration: Int?
if let duration = duration {
webpageDuration = Int(duration)
}
var image: TelegramMediaImage?
if let photo = photo {
image = telegramMediaImageFromApiPhoto(photo)
}
var file: TelegramMediaFile?
if let document = document {
file = telegramMediaFileFromApiDocument(document, altDocuments: [])
}
var story: TelegramMediaStory?
var webpageAttributes: [TelegramMediaWebpageAttribute] = []
if let attributes = attributes {
webpageAttributes = attributes.compactMap(telegramMediaWebpageAttributeFromApiWebpageAttribute)
for attribute in attributes {
if case let .webPageAttributeStory(_, peerId, id, _) = attribute {
story = TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: false)
}
}
}
var instantPage: InstantPage?
if let cachedPage = cachedPage {
instantPage = InstantPage(apiPage: cachedPage)
}
let isMediaLargeByDefault = (flags & (1 << 13)) != 0
let imageIsVideoCover = (flags & (1 << 14)) != 0
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, isMediaLargeByDefault: isMediaLargeByDefault, imageIsVideoCover: imageIsVideoCover, image: image, file: file, story: story, attributes: webpageAttributes, instantPage: instantPage)))
case .webPageEmpty:
return nil
}
}
public class WebpagePreviewMessageAttribute: MessageAttribute, Equatable {
public let associatedPeerIds: [PeerId] = []
public let associatedMediaIds: [MediaId] = []
public let leadingPreview: Bool
public let forceLargeMedia: Bool?
public let isManuallyAdded: Bool
public let isSafe: Bool
public init(leadingPreview: Bool, forceLargeMedia: Bool?, isManuallyAdded: Bool, isSafe: Bool) {
self.leadingPreview = leadingPreview
self.forceLargeMedia = forceLargeMedia
self.isManuallyAdded = isManuallyAdded
self.isSafe = isSafe
}
required public init(decoder: PostboxDecoder) {
self.leadingPreview = decoder.decodeBoolForKey("lp", orElse: false)
self.forceLargeMedia = decoder.decodeOptionalBoolForKey("lm")
self.isManuallyAdded = decoder.decodeBoolForKey("ma", orElse: false)
self.isSafe = decoder.decodeBoolForKey("sf", orElse: false)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeBool(self.leadingPreview, forKey: "lp")
if let forceLargeMedia = self.forceLargeMedia {
encoder.encodeBool(forceLargeMedia, forKey: "lm")
} else {
encoder.encodeNil(forKey: "lm")
}
encoder.encodeBool(self.isManuallyAdded, forKey: "ma")
encoder.encodeBool(self.isSafe, forKey: "sf")
}
public static func ==(lhs: WebpagePreviewMessageAttribute, rhs: WebpagePreviewMessageAttribute) -> Bool {
if lhs.leadingPreview != rhs.leadingPreview {
return false
}
if lhs.forceLargeMedia != rhs.forceLargeMedia {
return false
}
if lhs.isManuallyAdded != rhs.isManuallyAdded {
return false
}
if lhs.isSafe != rhs.isSafe {
return false
}
return true
}
}
@@ -0,0 +1,118 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramPeerNotificationSettings {
convenience init(apiSettings: Api.PeerNotifySettings) {
switch apiSettings {
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, storiesHideSender, storiesIosSound, _, storiesDesktopSound):
let sound: Api.NotificationSound?
let storiesSound: Api.NotificationSound?
#if os(iOS)
sound = iosSound
storiesSound = storiesIosSound
#elseif os(macOS)
sound = desktopSound
storiesSound = storiesDesktopSound
#endif
let muteState: PeerMuteState
if let muteUntil = muteUntil {
if muteUntil == 0 {
muteState = .unmuted
} else {
muteState = .muted(until: muteUntil)
}
} else {
muteState = .default
}
let displayPreviews: PeerNotificationDisplayPreviews
if let showPreviews = showPreviews {
if case .boolTrue = showPreviews {
displayPreviews = .show
} else {
displayPreviews = .hide
}
} else {
displayPreviews = .default
}
let storiesMutedValue: PeerStoryNotificationSettings.Mute
if let storiesMuted = storiesMuted {
storiesMutedValue = storiesMuted == .boolTrue ? .muted : .unmuted
} else {
storiesMutedValue = .default
}
var storiesHideSenderValue: PeerStoryNotificationSettings.HideSender
if let storiesHideSender = storiesHideSender {
storiesHideSenderValue = storiesHideSender == .boolTrue ? .hide : .show
} else {
storiesHideSenderValue = .default
}
self.init(muteState: muteState, messageSound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), displayPreviews: displayPreviews, storySettings: PeerStoryNotificationSettings(
mute: storiesMutedValue,
hideSender: storiesHideSenderValue,
sound: PeerMessageSound(apiSound: storiesSound ?? .notificationSoundDefault)
))
}
}
}
extension PeerMessageSound {
init(apiSound: Api.NotificationSound) {
switch apiSound {
case .notificationSoundDefault:
self = .default
case .notificationSoundNone:
self = .none
case let .notificationSoundLocal(_, data):
var rawApiSound = data
if let index = rawApiSound.firstIndex(of: ".") {
rawApiSound = String(rawApiSound[..<index])
}
let parsedSound: PeerMessageSound
if rawApiSound == "default" {
parsedSound = .default
} else if rawApiSound == "" || rawApiSound == "0" {
parsedSound = .none
} else {
let soundId: Int32
if let id = Int32(rawApiSound) {
soundId = id
} else {
soundId = 100
}
if soundId >= 100 && soundId <= 111 {
parsedSound = .bundledModern(id: soundId - 100)
} else if soundId >= 2 && soundId <= 9 {
parsedSound = .bundledClassic(id: soundId - 2)
} else {
parsedSound = defaultCloudPeerNotificationSound
}
}
self = parsedSound
case let .notificationSoundRingtone(id):
self = .cloud(fileId: id)
}
}
var apiSound: Api.NotificationSound {
switch self {
case .none:
return .notificationSoundNone
case .default:
return .notificationSoundDefault
case let .bundledModern(id):
let string = "\(id + 100)"
return .notificationSoundLocal(title: string, data: string)
case let .bundledClassic(id):
let string = "\(id + 2)"
return .notificationSoundLocal(title: string, data: string)
case let .cloud(fileId):
return .notificationSoundRingtone(id: fileId)
}
}
}
@@ -0,0 +1,356 @@
import Foundation
import Postbox
import TelegramApi
func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMediaImageRepresentation] {
var representations: [TelegramMediaImageRepresentation] = []
switch photo {
case let .userProfilePhoto(flags, id, strippedThumb, dcId):
let hasVideo = (flags & (1 << 0)) != 0
let isPersonal = (flags & (1 << 2)) != 0
let smallResource: TelegramMediaResource
let fullSizeResource: TelegramMediaResource
smallResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: id, sizeSpec: .small, volumeId: nil, localId: nil)
fullSizeResource = CloudPeerPhotoSizeMediaResource(datacenterId: dcId, photoId: id, sizeSpec: .fullSize, volumeId: nil, localId: nil)
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: smallResource, progressiveSizes: [], immediateThumbnailData: strippedThumb?.makeData(), hasVideo: hasVideo, isPersonal: isPersonal))
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: fullSizeResource, progressiveSizes: [], immediateThumbnailData: strippedThumb?.makeData(), hasVideo: hasVideo, isPersonal: isPersonal))
case .userProfilePhotoEmpty:
break
}
return representations
}
extension TelegramPeerUsername {
init(apiUsername: Api.Username) {
switch apiUsername {
case let .username(flags, username):
self.init(flags: Flags(rawValue: flags), username: username)
}
}
}
extension PeerVerification {
init(apiBotVerification: Api.BotVerification) {
switch apiBotVerification {
case let .botVerification(botId, iconFileId, description):
self.init(
botId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)),
iconFileId: iconFileId,
description: description
)
}
}
}
extension TelegramUser {
convenience init(user: Api.User) {
switch user {
case let .user(flags, flags2, id, accessHash, firstName, lastName, username, phone, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, usernames, _, color, profileColor, subscriberCount, verificationIconFileId, _):
let representations: [TelegramMediaImageRepresentation] = photo.flatMap(parsedTelegramProfilePhoto) ?? []
let isMin = (flags & (1 << 20)) != 0
let accessHashValue = accessHash.flatMap { value -> TelegramPeerAccessHash in
if isMin {
return .genericPublic(value)
} else {
return .personal(value)
}
}
var userFlags: UserInfoFlags = []
if (flags & (1 << 12)) != 0 {
userFlags.insert(.mutualContact)
}
if (flags & (1 << 17)) != 0 {
userFlags.insert(.isVerified)
}
if (flags & (1 << 23)) != 0 {
userFlags.insert(.isSupport)
}
if (flags & (1 << 24)) != 0 {
userFlags.insert(.isScam)
}
if (flags & (1 << 26)) != 0 {
userFlags.insert(.isFake)
}
if (flags & (1 << 28)) != 0 {
userFlags.insert(.isPremium)
}
if (flags2 & (1 << 2)) != 0 {
userFlags.insert(.isCloseFriend)
}
if (flags2 & (1 << 10)) != 0 {
userFlags.insert(.requirePremium)
}
if (flags2 & (1 << 15)) != 0 {
userFlags.insert(.requireStars)
}
var storiesHidden: Bool?
if !isMin {
storiesHidden = (flags2 & (1 << 3)) != 0
}
var botInfo: BotUserInfo?
if (flags & (1 << 14)) != 0 {
var botFlags = BotUserInfoFlags()
if (flags & (1 << 15)) != 0 {
botFlags.insert(.hasAccessToChatHistory)
}
if (flags & (1 << 16)) == 0 {
botFlags.insert(.worksWithGroups)
}
if (flags & (1 << 21)) != 0 {
botFlags.insert(.requiresGeolocationForInlineRequests)
}
if (flags & (1 << 27)) != 0 {
botFlags.insert(.canBeAddedToAttachMenu)
}
if (flags2 & (1 << 1)) != 0 {
botFlags.insert(.canEdit)
}
if (flags2 & (1 << 11)) != 0 {
botFlags.insert(.isBusiness)
}
if (flags2 & (1 << 13)) != 0 {
botFlags.insert(.hasWebApp)
}
if (flags2 & (1 << 16)) != 0 {
botFlags.insert(.hasForum)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}
let restrictionInfo: PeerAccessRestrictionInfo? = restrictionReason.flatMap(PeerAccessRestrictionInfo.init(apiReasons:))
var nameColor: PeerColor?
var backgroundEmojiId: Int64?
if let color = color {
switch color {
case let .peerColor(_, color, backgroundEmojiIdValue):
if let color {
nameColor = .preset(PeerNameColor(rawValue: color))
}
backgroundEmojiId = backgroundEmojiIdValue
case let .peerColorCollectible(_, collectibleId, giftEmojiId, backgroundEmojiIdValue, accentColor, colors, darkAccentColor, darkColors):
nameColor = .collectible(PeerCollectibleColor(
collectibleId: collectibleId,
giftEmojiFileId: giftEmojiId,
backgroundEmojiId: backgroundEmojiIdValue,
accentColor: UInt32(bitPattern: accentColor),
colors: colors.map { UInt32(bitPattern: $0) },
darkAccentColor: darkAccentColor.flatMap { UInt32(bitPattern: $0) },
darkColors: darkColors.flatMap { $0.map { UInt32(bitPattern: $0) } }
))
backgroundEmojiId = backgroundEmojiIdValue
case .inputPeerColorCollectible:
break
}
}
var profileColorIndex: Int32?
var profileBackgroundEmojiId: Int64?
if let profileColor = profileColor {
switch profileColor {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
default:
break
}
}
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: representations, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, subscriberCount: subscriberCount, verificationIconFileId: verificationIconFileId)
case let .userEmpty(id):
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)
}
}
static func merge(_ lhs: TelegramUser?, rhs: Api.User) -> TelegramUser? {
switch rhs {
case let .user(flags, _, _, rhsAccessHash, _, _, _, _, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, _, _, color, profileColor, subscriberCount, _, _):
let isMin = (flags & (1 << 20)) != 0
if !isMin {
return TelegramUser(user: rhs)
} else {
let applyMinPhoto = (flags & (1 << 25)) != 0
let telegramPhoto: [TelegramMediaImageRepresentation]
if let photo = photo, applyMinPhoto {
telegramPhoto = parsedTelegramProfilePhoto(photo)
} else if let currentPhoto = lhs?.photo {
telegramPhoto = currentPhoto
} else {
telegramPhoto = []
}
if let lhs = lhs {
var userFlags: UserInfoFlags = []
if (flags & (1 << 12)) != 0 {
userFlags.insert(.mutualContact)
}
if (flags & (1 << 17)) != 0 {
userFlags.insert(.isVerified)
}
if (flags & (1 << 23)) != 0 {
userFlags.insert(.isSupport)
}
if (flags & (1 << 24)) != 0 {
userFlags.insert(.isScam)
}
if (flags & Int32(1 << 26)) != 0 {
userFlags.insert(.isFake)
}
if (flags & (1 << 28)) != 0 {
userFlags.insert(.isPremium)
}
if lhs.flags.contains(.isCloseFriend) {
userFlags.insert(.isCloseFriend)
}
if lhs.flags.contains(.requirePremium) {
userFlags.insert(.requirePremium)
}
var botInfo: BotUserInfo?
if (flags & (1 << 14)) != 0 {
var botFlags = BotUserInfoFlags()
if (flags & (1 << 15)) != 0 {
botFlags.insert(.hasAccessToChatHistory)
}
if (flags & (1 << 16)) == 0 {
botFlags.insert(.worksWithGroups)
}
if (flags & (1 << 21)) != 0 {
botFlags.insert(.requiresGeolocationForInlineRequests)
}
if (flags & (1 << 27)) != 0 {
botFlags.insert(.canBeAddedToAttachMenu)
}
if let botInfo = lhs.botInfo, botInfo.flags.contains(.canEdit) {
botFlags.insert(.canEdit)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}
let restrictionInfo: PeerAccessRestrictionInfo? = restrictionReason.flatMap(PeerAccessRestrictionInfo.init)
let rhsAccessHashValue = rhsAccessHash.flatMap { value -> TelegramPeerAccessHash in
if isMin {
return .genericPublic(value)
} else {
return .personal(value)
}
}
let accessHash: TelegramPeerAccessHash?
if let rhsAccessHashValue = rhsAccessHashValue, case .personal = rhsAccessHashValue {
accessHash = rhsAccessHashValue
} else {
accessHash = lhs.accessHash ?? rhsAccessHashValue
}
var nameColor: PeerColor?
var backgroundEmojiId: Int64?
if let color {
switch color {
case let .peerColor(_, color, backgroundEmojiIdValue):
if let color {
nameColor = .preset(PeerNameColor(rawValue: color))
}
backgroundEmojiId = backgroundEmojiIdValue
case let .peerColorCollectible(_, collectibleId, giftEmojiId, backgroundEmojiIdValue, accentColor, colors, darkAccentColor, darkColors):
nameColor = .collectible(PeerCollectibleColor(
collectibleId: collectibleId,
giftEmojiFileId: giftEmojiId,
backgroundEmojiId: backgroundEmojiIdValue,
accentColor: UInt32(bitPattern: accentColor),
colors: colors.map { UInt32(bitPattern: $0) },
darkAccentColor: darkAccentColor.flatMap { UInt32(bitPattern: $0) },
darkColors: darkColors.flatMap { $0.map { UInt32(bitPattern: $0) } }
))
backgroundEmojiId = backgroundEmojiIdValue
case .inputPeerColorCollectible:
break
}
}
var profileColorIndex: Int32?
var profileBackgroundEmojiId: Int64?
if let profileColor = profileColor {
switch profileColor {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
default:
break
}
}
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: lhs.username, phone: lhs.phone, photo: telegramPhoto, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus.flatMap(PeerEmojiStatus.init(apiStatus:)), usernames: lhs.usernames, storiesHidden: lhs.storiesHidden, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: profileColorIndex.flatMap { PeerNameColor(rawValue: $0) }, profileBackgroundEmojiId: profileBackgroundEmojiId, subscriberCount: subscriberCount, verificationIconFileId: lhs.verificationIconFileId)
} else {
return TelegramUser(user: rhs)
}
}
case .userEmpty:
return TelegramUser(user: rhs)
}
}
static func merge(lhs: TelegramUser?, rhs: TelegramUser) -> TelegramUser {
guard let lhs = lhs else {
return rhs
}
if let rhsAccessHash = rhs.accessHash, case .personal = rhsAccessHash {
return rhs
} else {
var userFlags: UserInfoFlags = []
if rhs.flags.contains(.isVerified) {
userFlags.insert(.isVerified)
}
if rhs.flags.contains(.isSupport) {
userFlags.insert(.isSupport)
}
if rhs.flags.contains(.isScam) {
userFlags.insert(.isScam)
}
if rhs.flags.contains(.isFake) {
userFlags.insert(.isFake)
}
if rhs.flags.contains(.isPremium) {
userFlags.insert(.isPremium)
}
let botInfo: BotUserInfo? = rhs.botInfo
let emojiStatus = rhs.emojiStatus
let restrictionInfo: PeerAccessRestrictionInfo? = rhs.restrictionInfo
let accessHash: TelegramPeerAccessHash?
if let rhsAccessHashValue = rhs.accessHash, case .personal = rhsAccessHashValue {
accessHash = rhsAccessHashValue
} else {
accessHash = lhs.accessHash ?? rhs.accessHash
}
let photo: [TelegramMediaImageRepresentation]
if case .genericPublic = rhs.accessHash {
photo = lhs.photo
} else {
photo = rhs.photo
}
var storiesHidden: Bool?
if let value = rhs.storiesHidden {
storiesHidden = value
} else {
storiesHidden = lhs.storiesHidden
}
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: lhs.username, phone: lhs.phone, photo: photo, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags, emojiStatus: emojiStatus, usernames: lhs.usernames, storiesHidden: storiesHidden, nameColor: rhs.nameColor, backgroundEmojiId: rhs.backgroundEmojiId, profileColor: rhs.profileColor, profileBackgroundEmojiId: rhs.profileBackgroundEmojiId, subscriberCount: rhs.subscriberCount, verificationIconFileId: rhs.verificationIconFileId)
}
}
}
@@ -0,0 +1,39 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramUserPresence {
convenience init(apiStatus: Api.UserStatus) {
switch apiStatus {
case .userStatusEmpty:
self.init(status: .none, lastActivity: 0)
case let .userStatusOnline(expires):
self.init(status: .present(until: expires), lastActivity: 0)
case let .userStatusOffline(wasOnline):
self.init(status: .present(until: wasOnline), lastActivity: 0)
case let .userStatusRecently(flags):
let isHidden = (flags & (1 << 0)) != 0
self.init(status: .recently(isHidden: isHidden), lastActivity: 0)
case let .userStatusLastWeek(flags):
let isHidden = (flags & (1 << 0)) != 0
self.init(status: .lastWeek(isHidden: isHidden), lastActivity: 0)
case let .userStatusLastMonth(flags):
let isHidden = (flags & (1 << 0)) != 0
self.init(status: .lastMonth(isHidden: isHidden), lastActivity: 0)
}
}
convenience init?(apiUser: Api.User) {
switch apiUser {
case let .user(_, _, _, _, _, _, _, _, _, status, _, _, _, _, _, _, _, _, _, _, _, _):
if let status = status {
self.init(apiStatus: status)
} else {
self.init(status: .none, lastActivity: 0)
}
case .userEmpty:
return nil
}
}
}
@@ -0,0 +1,67 @@
import Foundation
import Postbox
import TelegramApi
func apiEntitiesFromMessageTextEntities(_ entities: [MessageTextEntity], associatedPeers: SimpleDictionary<PeerId, Peer>) -> [Api.MessageEntity] {
var apiEntities: [Api.MessageEntity] = []
for entity in entities {
let offset: Int32 = Int32(entity.range.lowerBound)
let length: Int32 = Int32(entity.range.upperBound - entity.range.lowerBound)
switch entity.type {
case .Unknown:
break
case .Mention:
apiEntities.append(.messageEntityMention(offset: offset, length: length))
case .Hashtag:
apiEntities.append(.messageEntityHashtag(offset: offset, length: length))
case .BotCommand:
apiEntities.append(.messageEntityBotCommand(offset: offset, length: length))
case .Url:
apiEntities.append(.messageEntityUrl(offset: offset, length: length))
case .Email:
apiEntities.append(.messageEntityEmail(offset: offset, length: length))
case .Bold:
apiEntities.append(.messageEntityBold(offset: offset, length: length))
case .Italic:
apiEntities.append(.messageEntityItalic(offset: offset, length: length))
case .Code:
apiEntities.append(.messageEntityCode(offset: offset, length: length))
case let .Pre(language):
apiEntities.append(.messageEntityPre(offset: offset, length: length, language: language ?? ""))
case let .TextUrl(url):
apiEntities.append(.messageEntityTextUrl(offset: offset, length: length, url: url))
case let .TextMention(peerId):
if let peer = associatedPeers[peerId], let inputUser = apiInputUser(peer) {
apiEntities.append(.inputMessageEntityMentionName(offset: offset, length: length, userId: inputUser))
}
case .PhoneNumber:
break
case .Strikethrough:
apiEntities.append(.messageEntityStrike(offset: offset, length: length))
case let .BlockQuote(isCollapsed):
var flags: Int32 = 0
if isCollapsed {
flags |= 1 << 0
}
apiEntities.append(.messageEntityBlockquote(flags: flags, offset: offset, length: length))
case .Underline:
apiEntities.append(.messageEntityUnderline(offset: offset, length: length))
case .BankCard:
apiEntities.append(.messageEntityBankCard(offset: offset, length: length))
case .Spoiler:
apiEntities.append(.messageEntitySpoiler(offset: offset, length: length))
case let .CustomEmoji(_, fileId):
apiEntities.append(.messageEntityCustomEmoji(offset: offset, length: length, documentId: fileId))
case .Custom:
break
}
}
return apiEntities
}
func apiTextAttributeEntities(_ attribute: TextEntitiesMessageAttribute, associatedPeers: SimpleDictionary<PeerId, Peer>) -> [Api.MessageEntity] {
return apiEntitiesFromMessageTextEntities(attribute.entities, associatedPeers: associatedPeers)
}
@@ -0,0 +1,78 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
extension TelegramTheme {
convenience init(apiTheme: Api.Theme) {
switch apiTheme {
case let .theme(flags, id, accessHash, slug, title, document, settings, emoticon, installCount):
self.init(id: id, accessHash: accessHash, slug: slug, emoticon: emoticon, title: title, file: document.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }, settings: settings?.compactMap(TelegramThemeSettings.init(apiThemeSettings:)), isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, installCount: installCount)
}
}
}
extension TelegramBaseTheme {
init(apiBaseTheme: Api.BaseTheme) {
switch apiBaseTheme {
case .baseThemeClassic:
self = .classic
case .baseThemeDay:
self = .day
case .baseThemeNight:
self = .night
case .baseThemeTinted:
self = .tinted
case .baseThemeArctic:
self = .day
}
}
var apiBaseTheme: Api.BaseTheme {
switch self {
case .classic:
return .baseThemeClassic
case .day:
return .baseThemeDay
case .night:
return .baseThemeNight
case .tinted:
return .baseThemeTinted
}
}
}
extension TelegramThemeSettings {
convenience init?(apiThemeSettings: Api.ThemeSettings) {
switch apiThemeSettings {
case let .themeSettings(flags, baseTheme, accentColor, outboxAccentColor, messageColors, wallpaper):
self.init(baseTheme: TelegramBaseTheme(apiBaseTheme: baseTheme), accentColor: UInt32(bitPattern: accentColor), outgoingAccentColor: outboxAccentColor.flatMap { UInt32(bitPattern: $0) }, messageColors: messageColors?.map(UInt32.init(bitPattern:)) ?? [], animateMessageColors: (flags & 1 << 2) != 0, wallpaper: wallpaper.flatMap(TelegramWallpaper.init(apiWallpaper:)))
}
}
var apiInputThemeSettings: Api.InputThemeSettings {
var flags: Int32 = 0
if !self.messageColors.isEmpty {
flags |= 1 << 0
}
if self.animateMessageColors {
flags |= 1 << 2
}
if let _ = self.outgoingAccentColor {
flags |= 1 << 3
}
var inputWallpaper: Api.InputWallPaper?
var inputWallpaperSettings: Api.WallPaperSettings?
if let wallpaper = self.wallpaper, let inputWallpaperAndSettings = wallpaper.apiInputWallpaperAndSettings {
inputWallpaper = inputWallpaperAndSettings.0
inputWallpaperSettings = inputWallpaperAndSettings.1
flags |= 1 << 1
}
return .inputThemeSettings(flags: flags, baseTheme: self.baseTheme.apiBaseTheme, accentColor: Int32(bitPattern: self.accentColor), outboxAccentColor: self.outgoingAccentColor.flatMap { Int32(bitPattern: $0) }, messageColors: self.messageColors.isEmpty ? nil : self.messageColors.map(Int32.init(bitPattern:)), wallpaper: inputWallpaper, wallpaperSettings: inputWallpaperSettings)
}
}
@@ -0,0 +1,15 @@
import Foundation
import Postbox
public final class TypingDraftMessageAttribute: MessageAttribute {
public init() {
}
public init(decoder: PostboxDecoder) {
preconditionFailure()
}
public func encode(_ encoder: PostboxEncoder) {
preconditionFailure()
}
}
@@ -0,0 +1,121 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
extension WallpaperSettings {
init(apiWallpaperSettings: Api.WallPaperSettings) {
switch apiWallpaperSettings {
case let .wallPaperSettings(flags, backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor, intensity, rotation, emoticon):
var colors: [UInt32] = []
if let backgroundColor = backgroundColor {
colors.append(UInt32(bitPattern: backgroundColor))
}
if let secondBackgroundColor = secondBackgroundColor {
colors.append(UInt32(bitPattern: secondBackgroundColor))
}
if let thirdBackgroundColor = thirdBackgroundColor {
colors.append(UInt32(bitPattern: thirdBackgroundColor))
}
if let fourthBackgroundColor = fourthBackgroundColor {
colors.append(UInt32(bitPattern: fourthBackgroundColor))
}
self = WallpaperSettings(blur: (flags & 1 << 1) != 0, motion: (flags & 1 << 2) != 0, colors: colors, intensity: intensity, rotation: rotation, emoticon: emoticon)
}
}
}
func apiWallpaperSettings(_ wallpaperSettings: WallpaperSettings) -> Api.WallPaperSettings {
var flags: Int32 = 0
var backgroundColor: Int32?
if wallpaperSettings.colors.count >= 1 {
flags |= (1 << 0)
backgroundColor = Int32(bitPattern: wallpaperSettings.colors[0])
}
if wallpaperSettings.blur {
flags |= (1 << 1)
}
if wallpaperSettings.motion {
flags |= (1 << 2)
}
if let _ = wallpaperSettings.intensity {
flags |= (1 << 3)
}
if let _ = wallpaperSettings.emoticon {
flags |= (1 << 7)
}
var secondBackgroundColor: Int32?
if wallpaperSettings.colors.count >= 2 {
flags |= (1 << 4)
secondBackgroundColor = Int32(bitPattern: wallpaperSettings.colors[1])
}
var thirdBackgroundColor: Int32?
if wallpaperSettings.colors.count >= 3 {
flags |= (1 << 5)
thirdBackgroundColor = Int32(bitPattern: wallpaperSettings.colors[2])
}
var fourthBackgroundColor: Int32?
if wallpaperSettings.colors.count >= 4 {
flags |= (1 << 6)
fourthBackgroundColor = Int32(bitPattern: wallpaperSettings.colors[3])
}
return .wallPaperSettings(flags: flags, backgroundColor: backgroundColor, secondBackgroundColor: secondBackgroundColor, thirdBackgroundColor: thirdBackgroundColor, fourthBackgroundColor: fourthBackgroundColor, intensity: wallpaperSettings.intensity, rotation: wallpaperSettings.rotation ?? 0, emoticon: wallpaperSettings.emoticon)
}
extension TelegramWallpaper {
init(apiWallpaper: Api.WallPaper) {
switch apiWallpaper {
case let .wallPaper(id, flags, accessHash, slug, document, settings):
if let file = telegramMediaFileFromApiDocument(document, altDocuments: []) {
let wallpaperSettings: WallpaperSettings
if let settings = settings {
wallpaperSettings = WallpaperSettings(apiWallpaperSettings: settings)
} else {
wallpaperSettings = WallpaperSettings()
}
self = .file(TelegramWallpaper.File(id: id, accessHash: accessHash, isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, isPattern: (flags & 1 << 3) != 0, isDark: (flags & 1 << 4) != 0, slug: slug, file: file, settings: wallpaperSettings))
} else {
//assertionFailure()
self = .color(0xffffff)
}
case let .wallPaperNoFile(id, _, settings):
if let settings = settings, case let .wallPaperSettings(_, backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor, _, rotation, emoticon) = settings {
if id == 0, let emoticon = emoticon {
self = .emoticon(emoticon)
return
}
let colors: [UInt32] = ([backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor] as [Int32?]).compactMap({ color -> UInt32? in
return color.flatMap(UInt32.init(bitPattern:))
})
if colors.count > 1 {
self = .gradient(TelegramWallpaper.Gradient(id: id, colors: colors, settings: WallpaperSettings(rotation: rotation)))
} else if colors.count == 1 {
self = .color(UInt32(bitPattern: colors[0]))
} else {
self = .color(0xffffff)
}
} else {
self = .color(0xffffff)
}
}
}
var apiInputWallpaperAndSettings: (Api.InputWallPaper, Api.WallPaperSettings)? {
switch self {
case .builtin:
return nil
case let .file(file):
return (.inputWallPaperSlug(slug: file.slug), apiWallpaperSettings(file.settings))
case let .color(color):
return (.inputWallPaperNoFile(id: 0), apiWallpaperSettings(WallpaperSettings(colors: [color])))
case let .gradient(gradient):
return (.inputWallPaperNoFile(id: gradient.id ?? 0), apiWallpaperSettings(WallpaperSettings(colors: gradient.colors, rotation: gradient.settings.rotation)))
case let .emoticon(emoticon):
return (.inputWallPaperNoFile(id: 0), apiWallpaperSettings(WallpaperSettings(emoticon: emoticon)))
default:
return nil
}
}
}