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,225 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum AssignAppStoreTransactionError {
case generic
case timeout
case serverProvided
}
public enum AppStoreTransactionPurpose {
case subscription
case upgrade
case restore
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64, text: String?, entities: [MessageTextEntity]?)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
case stars(count: Int64, currency: String, amount: Int64, peerId: EnginePeer.Id?)
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64)
}
private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
switch purpose {
case .subscription, .upgrade, .restore:
var flags: Int32 = 0
switch purpose {
case .upgrade:
flags |= (1 << 1)
case .restore:
flags |= (1 << 0)
default:
break
}
return .single(.inputStorePaymentPremiumSubscription(flags: flags))
case let .gift(peerId, currency, amount):
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
}
return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount))
}
case let .giftCode(peerIds, boostPeerId, currency, amount, text, entities):
return postbox.transaction { transaction -> Api.InputStorePaymentPurpose in
var flags: Int32 = 0
var apiBoostPeer: Api.InputPeer?
var apiInputUsers: [Api.InputUser] = []
for peerId in peerIds {
if let user = transaction.getPeer(peerId), let apiUser = apiInputUser(user) {
apiInputUsers.append(apiUser)
}
}
if let boostPeerId = boostPeerId, let boostPeer = transaction.getPeer(boostPeerId), let apiPeer = apiInputPeer(boostPeer) {
apiBoostPeer = apiPeer
flags |= (1 << 0)
}
var message: Api.TextWithEntities?
if let text, !text.isEmpty {
flags |= (1 << 1)
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
}
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount, message: message)
}
case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount):
return postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete()
}
var flags: Int32 = 0
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
for peerId in additionalPeerIds {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
additionalPeers.append(inputPeer)
}
}
}
if !countries.isEmpty {
flags |= (1 << 2)
}
if let _ = prizeDescription {
flags |= (1 << 4)
}
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
}
|> switchToLatest
case let .stars(count, currency, amount, peerId):
let peerSignal: Signal<Api.InputPeer?, NoError>
if let peerId {
peerSignal = postbox.loadedPeerWithId(peerId)
|> map { peer in
return apiInputPeer(peer)
}
} else {
peerSignal = .single(nil)
}
return peerSignal
|> map { spendPurposePeer in
var flags: Int32 = 0
if let _ = spendPurposePeer {
flags |= (1 << 0)
}
return .inputStorePaymentStarsTopup(flags: flags, stars: count, currency: currency, amount: amount, spendPurposePeer: spendPurposePeer)
}
case let .starsGift(peerId, count, currency, amount):
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
}
return .single(.inputStorePaymentStarsGift(userId: inputUser, stars: count, currency: currency, amount: amount))
}
case let .starsGiveaway(stars, boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, users):
return postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete()
}
var flags: Int32 = 0
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
for peerId in additionalPeerIds {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
additionalPeers.append(inputPeer)
}
}
}
if !countries.isEmpty {
flags |= (1 << 2)
}
if let _ = prizeDescription {
flags |= (1 << 4)
}
return .single(.inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users))
}
|> switchToLatest
case let .authCode(restore, phoneNumber, phoneCodeHash, currency, amount):
var flags: Int32 = 0
if restore {
flags |= (1 << 0)
}
return .single(.inputStorePaymentAuthCode(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount))
}
}
func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: AccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
|> mapError { error -> AssignAppStoreTransactionError in
if error.errorCode == 406 {
return .serverProvided
} else {
return .generic
}
}
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
stateManager.addUpdates(updates)
return .complete()
}
}
}
func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: UnauthorizedAccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
|> mapError { error -> AssignAppStoreTransactionError in
if error.errorCode == 406 {
return .serverProvided
} else {
return .generic
}
}
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
stateManager.addUpdates(updates)
return .complete()
}
}
}
public enum RestoreAppStoreReceiptError {
case generic
}
func _internal_canPurchasePremium(postbox: Postbox, network: Network, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> mapToSignal { purpose -> Signal<Bool, NoError> in
return network.request(Api.functions.payments.canPurchaseStore(purpose: purpose))
|> map { result -> Bool in
switch result {
case .boolTrue:
return true
case .boolFalse:
return false
}
}
|> `catch` { _ -> Signal<Bool, NoError> in
return .single(false)
}
}
}
@@ -0,0 +1,58 @@
import Foundation
import Postbox
import TelegramApi
import MtProtoKit
import SwiftSignalKit
func _internal_getBankCardInfo(account: Account, cardNumber: String) -> Signal<BankCardInfo?, NoError> {
return currentWebDocumentsHostDatacenterId(postbox: account.postbox, isTestingEnvironment: false)
|> mapToSignal { datacenterId in
let signal: Signal<Api.payments.BankCardData, MTRpcError>
if account.network.datacenterId != datacenterId {
signal = account.network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|> castError(MTRpcError.self)
|> mapToSignal { worker in
return worker.request(Api.functions.payments.getBankCardData(number: cardNumber))
}
} else {
signal = account.network.request(Api.functions.payments.getBankCardData(number: cardNumber))
}
return signal
|> map { result -> BankCardInfo? in
return BankCardInfo(apiBankCardData: result)
}
|> `catch` { _ -> Signal<BankCardInfo?, NoError> in
return .single(nil)
}
}
}
public struct BankCardUrl {
public let title: String
public let url: String
}
public struct BankCardInfo {
public let title: String
public let urls: [BankCardUrl]
}
extension BankCardUrl {
init(apiBankCardOpenUrl: Api.BankCardOpenUrl) {
switch apiBankCardOpenUrl {
case let .bankCardOpenUrl(url, name):
self.title = name
self.url = url
}
}
}
extension BankCardInfo {
init(apiBankCardData: Api.payments.BankCardData) {
switch apiBankCardData {
case let .bankCardData(title, urls):
self.title = title
self.urls = urls.map { BankCardUrl(apiBankCardOpenUrl: $0) }
}
}
}
@@ -0,0 +1,967 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum BotPaymentInvoiceSource {
case message(MessageId)
case slug(String)
case premiumGiveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, option: PremiumGiftCodeOption)
case giftCode(users: [PeerId], currency: String, amount: Int64, option: PremiumGiftCodeOption, text: String?, entities: [MessageTextEntity]?)
case stars(option: StarsTopUpOption, peerId: EnginePeer.Id?)
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
case starsChatSubscription(hash: String)
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
case starGift(hideName: Bool, includeUpgrade: Bool, peerId: EnginePeer.Id, giftId: Int64, text: String?, entities: [MessageTextEntity]?)
case starGiftUpgrade(keepOriginalInfo: Bool, reference: StarGiftReference)
case starGiftTransfer(reference: StarGiftReference, toPeerId: EnginePeer.Id)
case premiumGift(peerId: EnginePeer.Id, option: CachedPremiumGiftOption, text: String?, entities: [MessageTextEntity]?)
case starGiftResale(slug: String, toPeerId: EnginePeer.Id, ton: Bool)
case starGiftPrepaidUpgrade(peerId: EnginePeer.Id, hash: String)
case starGiftDropOriginalDetails(reference: StarGiftReference)
case starGiftAuctionBid(update: Bool, hideName: Bool, peerId: EnginePeer.Id?, giftId: Int64, bidAmount: Int64, text: String?, entities: [MessageTextEntity]?)
}
public struct BotPaymentInvoiceFields: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let name = BotPaymentInvoiceFields(rawValue: 1 << 0)
public static let phone = BotPaymentInvoiceFields(rawValue: 1 << 1)
public static let email = BotPaymentInvoiceFields(rawValue: 1 << 2)
public static let shippingAddress = BotPaymentInvoiceFields(rawValue: 1 << 3)
public static let flexibleShipping = BotPaymentInvoiceFields(rawValue: 1 << 4)
public static let phoneAvailableToProvider = BotPaymentInvoiceFields(rawValue: 1 << 5)
public static let emailAvailableToProvider = BotPaymentInvoiceFields(rawValue: 1 << 6)
}
public struct BotPaymentPrice : Equatable {
public let label: String
public let amount: Int64
public init(label: String, amount: Int64) {
self.label = label
self.amount = amount
}
}
public struct BotPaymentInvoice : Equatable {
public struct Tip: Equatable {
public var max: Int64
public var suggested: [Int64]
}
public struct RecurrentInfo: Equatable {
public var termsUrl: String
public var isRecurrent: Bool
}
public let isTest: Bool
public let requestedFields: BotPaymentInvoiceFields
public let currency: String
public let prices: [BotPaymentPrice]
public let tip: Tip?
public let termsInfo: RecurrentInfo?
public let subscriptionPeriod: Int32?
public init(isTest: Bool, requestedFields: BotPaymentInvoiceFields, currency: String, prices: [BotPaymentPrice], tip: Tip?, termsInfo: RecurrentInfo?, subscriptionPeriod: Int32?) {
self.isTest = isTest
self.requestedFields = requestedFields
self.currency = currency
self.prices = prices
self.tip = tip
self.termsInfo = termsInfo
self.subscriptionPeriod = subscriptionPeriod
}
}
public struct BotPaymentNativeProvider : Equatable {
public let name: String
public let params: String
}
public struct BotPaymentShippingAddress: Equatable {
public let streetLine1: String
public let streetLine2: String
public let city: String
public let state: String
public let countryIso2: String
public let postCode: String
public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) {
self.streetLine1 = streetLine1
self.streetLine2 = streetLine2
self.city = city
self.state = state
self.countryIso2 = countryIso2
self.postCode = postCode
}
}
public struct BotPaymentRequestedInfo: Equatable {
public var name: String?
public var phone: String?
public var email: String?
public var shippingAddress: BotPaymentShippingAddress?
public init(name: String?, phone: String?, email: String?, shippingAddress: BotPaymentShippingAddress?) {
self.name = name
self.phone = phone
self.email = email
self.shippingAddress = shippingAddress
}
}
public enum BotPaymentSavedCredentials: Equatable {
case card(id: String, title: String)
public static func ==(lhs: BotPaymentSavedCredentials, rhs: BotPaymentSavedCredentials) -> Bool {
switch lhs {
case let .card(id, title):
if case .card(id, title) = rhs {
return true
} else {
return false
}
}
}
}
public struct BotPaymentForm : Equatable {
public let id: Int64
public let canSaveCredentials: Bool
public let passwordMissing: Bool
public let invoice: BotPaymentInvoice
public let paymentBotId: PeerId?
public let providerId: PeerId?
public let url: String?
public let nativeProvider: BotPaymentNativeProvider?
public let savedInfo: BotPaymentRequestedInfo?
public let savedCredentials: [BotPaymentSavedCredentials]
public let additionalPaymentMethods: [BotPaymentMethod]
public init(id: Int64, canSaveCredentials: Bool, passwordMissing: Bool, invoice: BotPaymentInvoice, paymentBotId: PeerId?, providerId: PeerId?, url: String?, nativeProvider: BotPaymentNativeProvider?, savedInfo: BotPaymentRequestedInfo?, savedCredentials: [BotPaymentSavedCredentials], additionalPaymentMethods: [BotPaymentMethod]) {
self.id = id
self.canSaveCredentials = canSaveCredentials
self.passwordMissing = passwordMissing
self.invoice = invoice
self.paymentBotId = paymentBotId
self.providerId = providerId
self.url = url
self.nativeProvider = nativeProvider
self.savedInfo = savedInfo
self.savedCredentials = savedCredentials
self.additionalPaymentMethods = additionalPaymentMethods
}
}
public struct BotPaymentMethod: Equatable {
public let url: String
public let title: String
}
extension BotPaymentMethod {
init(apiPaymentFormMethod: Api.PaymentFormMethod) {
switch apiPaymentFormMethod {
case let .paymentFormMethod(url, title):
self.init(url: url, title: title)
}
}
}
public enum BotPaymentFormRequestError {
case generic
case alreadyActive
case noPaymentNeeded
case disallowedStarGift
case starGiftResellTooEarly(Int32)
case starGiftUserLimit
}
extension BotPaymentInvoice {
init(apiInvoice: Api.Invoice) {
switch apiInvoice {
case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, termsUrl, subscriptionPeriod):
var fields = BotPaymentInvoiceFields()
if (flags & (1 << 1)) != 0 {
fields.insert(.name)
}
if (flags & (1 << 2)) != 0 {
fields.insert(.phone)
}
if (flags & (1 << 3)) != 0 {
fields.insert(.email)
}
if (flags & (1 << 4)) != 0 {
fields.insert(.shippingAddress)
}
if (flags & (1 << 5)) != 0 {
fields.insert(.flexibleShipping)
}
if (flags & (1 << 6)) != 0 {
fields.insert(.phoneAvailableToProvider)
}
if (flags & (1 << 7)) != 0 {
fields.insert(.emailAvailableToProvider)
}
let isRecurrent = (flags & (1 << 9)) != 0
var termsInfo: BotPaymentInvoice.RecurrentInfo?
if let termsUrl = termsUrl {
termsInfo = BotPaymentInvoice.RecurrentInfo(termsUrl: termsUrl, isRecurrent: isRecurrent)
}
var parsedTip: BotPaymentInvoice.Tip?
if let maxTipAmount = maxTipAmount, let suggestedTipAmounts = suggestedTipAmounts {
parsedTip = BotPaymentInvoice.Tip(max: maxTipAmount, suggested: suggestedTipAmounts)
}
self.init(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map {
switch $0 {
case let .labeledPrice(label, amount):
return BotPaymentPrice(label: label, amount: amount)
}
}, tip: parsedTip, termsInfo: termsInfo, subscriptionPeriod: subscriptionPeriod)
}
}
}
extension BotPaymentRequestedInfo {
init(apiInfo: Api.PaymentRequestedInfo) {
switch apiInfo {
case let .paymentRequestedInfo(_, name, phone, email, shippingAddress):
var parsedShippingAddress: BotPaymentShippingAddress?
if let shippingAddress = shippingAddress {
switch shippingAddress {
case let .postAddress(streetLine1, streetLine2, city, state, countryIso2, postCode):
parsedShippingAddress = BotPaymentShippingAddress(streetLine1: streetLine1, streetLine2: streetLine2, city: city, state: state, countryIso2: countryIso2, postCode: postCode)
}
}
self.init(name: name, phone: phone, email: email, shippingAddress: parsedShippingAddress)
}
}
}
func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInvoiceSource) -> Api.InputInvoice? {
switch source {
case let .message(messageId):
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
return nil
}
return .inputInvoiceMessage(peer: inputPeer, msgId: messageId.id)
case let .slug(slug):
return .inputInvoiceSlug(slug: slug)
case let .premiumGiveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, option):
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return nil
}
var flags: Int32 = 0
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
for peerId in additionalPeerIds {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
additionalPeers.append(inputPeer)
}
}
}
if !countries.isEmpty {
flags |= (1 << 2)
}
if let _ = prizeDescription {
flags |= (1 << 4)
}
let inputPurpose: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
flags = 0
if let _ = option.storeProductId {
flags |= (1 << 0)
}
if option.storeQuantity > 0 {
flags |= (1 << 1)
}
let option: Api.PremiumGiftCodeOption = .premiumGiftCodeOption(flags: flags, users: option.users, months: option.months, storeProduct: option.storeProductId, storeQuantity: option.storeQuantity, currency: option.currency, amount: option.amount)
return .inputInvoicePremiumGiftCode(purpose: inputPurpose, option: option)
case let .giftCode(users, currency, amount, option, text, entities):
var inputUsers: [Api.InputUser] = []
if !users.isEmpty {
for peerId in users {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputUser(peer) {
inputUsers.append(inputPeer)
}
}
}
var inputPurposeFlags: Int32 = 0
var message: Api.TextWithEntities?
if let text, !text.isEmpty {
inputPurposeFlags |= (1 << 1)
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
}
let inputPurpose: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiftCode(flags: inputPurposeFlags, users: inputUsers, boostPeer: nil, currency: currency, amount: amount, message: message)
var flags: Int32 = 0
if let _ = option.storeProductId {
flags |= (1 << 0)
}
if option.storeQuantity > 0 {
flags |= (1 << 1)
}
let option: Api.PremiumGiftCodeOption = .premiumGiftCodeOption(flags: flags, users: option.users, months: option.months, storeProduct: option.storeProductId, storeQuantity: option.storeQuantity, currency: option.currency, amount: option.amount)
return .inputInvoicePremiumGiftCode(purpose: inputPurpose, option: option)
case let .stars(option, peerId):
var flags: Int32 = 0
var spendPurposePeer: Api.InputPeer?
if let peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
flags |= (1 << 0)
spendPurposePeer = inputPeer
}
return .inputInvoiceStars(purpose: .inputStorePaymentStarsTopup(flags: flags, stars: option.count, currency: option.currency, amount: option.amount, spendPurposePeer: spendPurposePeer))
case let .starsGift(peerId, count, currency, amount):
guard let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) else {
return nil
}
return .inputInvoiceStars(purpose: .inputStorePaymentStarsGift(userId: inputUser, stars: count, currency: currency, amount: amount))
case let .starsChatSubscription(hash):
return .inputInvoiceChatInviteSubscription(hash: hash)
case let .starsGiveaway(stars, boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, users):
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return nil
}
var flags: Int32 = 0
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
for peerId in additionalPeerIds {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
additionalPeers.append(inputPeer)
}
}
}
if !countries.isEmpty {
flags |= (1 << 2)
}
if let _ = prizeDescription {
flags |= (1 << 4)
}
return .inputInvoiceStars(purpose: .inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users))
case let .starGift(hideName, includeUpgrade, peerId, giftId, text, entities):
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
return nil
}
var flags: Int32 = 0
if hideName {
flags |= (1 << 0)
}
if includeUpgrade {
flags |= (1 << 2)
}
var message: Api.TextWithEntities?
if let text, !text.isEmpty {
flags |= (1 << 1)
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
}
return .inputInvoiceStarGift(flags: flags, peer: inputPeer, giftId: giftId, message: message)
case let .starGiftUpgrade(keepOriginalInfo, reference):
var flags: Int32 = 0
if keepOriginalInfo {
flags |= (1 << 0)
}
return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftUpgrade(flags: flags, stargift: $0) }
case let .starGiftTransfer(reference, toPeerId):
guard let peer = transaction.getPeer(toPeerId), let inputPeer = apiInputPeer(peer) else {
return nil
}
return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftTransfer(stargift: $0, toId: inputPeer) }
case let .premiumGift(peerId, option, text, entities):
guard let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) else {
return nil
}
var flags: Int32 = 0
var message: Api.TextWithEntities?
if let text, !text.isEmpty {
flags |= (1 << 0)
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
}
return .inputInvoicePremiumGiftStars(flags: flags, userId: inputUser, months: option.months, message: message)
case let .starGiftResale(slug, toPeerId, ton):
guard let peer = transaction.getPeer(toPeerId), let inputPeer = apiInputPeer(peer) else {
return nil
}
var flags: Int32 = 0
if ton {
flags |= 1 << 0
}
return .inputInvoiceStarGiftResale(flags: flags, slug: slug, toId: inputPeer)
case let .starGiftPrepaidUpgrade(peerId, hash):
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
return nil
}
return .inputInvoiceStarGiftPrepaidUpgrade(peer: inputPeer, hash: hash)
case let .starGiftDropOriginalDetails(reference):
return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftDropOriginalDetails(stargift: $0) }
case let .starGiftAuctionBid(update, hideName, peerId, giftId, bidAmount, text, entities):
var flags: Int32 = 0
var inputPeer: Api.InputPeer?
var message: Api.TextWithEntities?
if update {
flags |= (1 << 2)
}
if let peerId {
guard let peer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
flags |= (1 << 3)
inputPeer = peer
if hideName {
flags |= (1 << 0)
}
if let text, !text.isEmpty {
flags |= (1 << 1)
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
}
}
return .inputInvoiceStarGiftAuctionBid(flags: flags, peer: inputPeer, giftId: giftId, bidAmount: bidAmount, message: message)
}
}
func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source: BotPaymentInvoiceSource) -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> {
return postbox.transaction { transaction -> Api.InputInvoice? in
return _internal_parseInputInvoice(transaction: transaction, source: source)
}
|> castError(BotPaymentFormRequestError.self)
|> mapToSignal { invoice -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> in
guard let invoice = invoice else {
return .fail(.generic)
}
let flags: Int32 = 0
return network.request(Api.functions.payments.getPaymentForm(flags: flags, invoice: invoice, themeParams: nil))
|> `catch` { error -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
if error.errorDescription == "SUBSCRIPTION_ALREADY_ACTIVE" {
return .fail(.alreadyActive)
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> in
return postbox.transaction { transaction -> TelegramMediaInvoice in
switch result {
case let .paymentForm(_, _, _, title, description, photo, invoice, _, _, _, _, _, _, _, _):
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
var parsedFlags = TelegramMediaInvoiceFlags()
if parsedInvoice.isTest {
parsedFlags.insert(.isTest)
}
if parsedInvoice.requestedFields.contains(.shippingAddress) {
parsedFlags.insert(.shippingAddressRequested)
}
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, subscriptionPeriod: parsedInvoice.subscriptionPeriod, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion)
case let .paymentFormStars(_, _, _, title, description, photo, invoice, _):
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, subscriptionPeriod: parsedInvoice.subscriptionPeriod, flags: [], version: TelegramMediaInvoice.lastVersion)
case let .paymentFormStarGift(_, invoice):
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
return TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, subscriptionPeriod: parsedInvoice.subscriptionPeriod, flags: [], version: TelegramMediaInvoice.lastVersion)
}
}
|> mapError { _ -> BotPaymentFormRequestError in }
}
}
}
func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, network: Network, source: BotPaymentInvoiceSource, themeParams: [String: Any]?) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
return postbox.transaction { transaction -> Api.InputInvoice? in
return _internal_parseInputInvoice(transaction: transaction, source: source)
}
|> castError(BotPaymentFormRequestError.self)
|> mapToSignal { invoice -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
guard let invoice = invoice else {
return .fail(.generic)
}
var flags: Int32 = 0
var serializedThemeParams: Api.DataJSON?
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
serializedThemeParams = Api.DataJSON.dataJSON(data: dataString)
}
if serializedThemeParams != nil {
flags |= 1 << 0
}
return network.request(Api.functions.payments.getPaymentForm(flags: flags, invoice: invoice, themeParams: serializedThemeParams))
|> `catch` { error -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
if error.errorDescription == "NO_PAYMENT_NEEDED" {
return .fail(.noPaymentNeeded)
} else if error.errorDescription == "USER_DISALLOWED_STARGIFTS" {
return .fail(.disallowedStarGift)
} else if error.errorDescription.hasPrefix("STARGIFT_RESELL_TOO_EARLY_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "STARGIFT_RESELL_TOO_EARLY_".count)...])
if let value = Int32(timeout) {
return .fail(.starGiftResellTooEarly(value))
}
} else if error.errorDescription == "STARGIFT_USER_USAGE_LIMITED" {
return .fail(.starGiftUserLimit)
}
return .fail(.generic)
}
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
return postbox.transaction { transaction -> BotPaymentForm in
switch result {
case let .paymentForm(flags, id, botId, title, description, photo, invoice, providerId, url, nativeProvider, nativeParams, additionalMethods, savedInfo, savedCredentials, apiUsers):
let _ = title
let _ = description
let _ = photo
let parsedPeers = AccumulatedPeers(users: apiUsers)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
var parsedNativeProvider: BotPaymentNativeProvider?
if let nativeProvider = nativeProvider, let nativeParams = nativeParams {
switch nativeParams {
case let .dataJSON(data):
parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data)
}
}
let parsedSavedInfo = savedInfo.flatMap(BotPaymentRequestedInfo.init)
let parsedSavedCredentials = savedCredentials?.map({ savedCredentials -> BotPaymentSavedCredentials in
switch savedCredentials {
case let .paymentSavedCredentialsCard(id, title):
return .card(id: id, title: title)
}
}) ?? []
let additionalPaymentMethods = additionalMethods?.map({ BotPaymentMethod(apiPaymentFormMethod: $0) }) ?? []
return BotPaymentForm(id: id, canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, paymentBotId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(providerId)), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials, additionalPaymentMethods: additionalPaymentMethods)
case let .paymentFormStars(flags, id, botId, title, description, photo, invoice, apiUsers):
let _ = flags
let _ = title
let _ = description
let _ = photo
let parsedPeers = AccumulatedPeers(users: apiUsers)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
return BotPaymentForm(id: id, canSaveCredentials: false, passwordMissing: false, invoice: parsedInvoice, paymentBotId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: [])
case let .paymentFormStarGift(id, invoice):
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
return BotPaymentForm(id: id, canSaveCredentials: false, passwordMissing: false, invoice: parsedInvoice, paymentBotId: nil, providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: [])
}
}
|> mapError { _ -> BotPaymentFormRequestError in }
}
}
}
public enum ValidateBotPaymentFormError {
case generic
case shippingNotAvailable
case addressStateInvalid
case addressPostcodeInvalid
case addressCityInvalid
case nameInvalid
case emailInvalid
case phoneInvalid
}
public struct BotPaymentShippingOption : Equatable {
public let id: String
public let title: String
public let prices: [BotPaymentPrice]
}
public struct BotPaymentValidatedFormInfo : Equatable {
public let id: String?
public let shippingOptions: [BotPaymentShippingOption]?
}
extension BotPaymentShippingOption {
init(apiOption: Api.ShippingOption) {
switch apiOption {
case let .shippingOption(id, title, prices):
self.init(id: id, title: title, prices: prices.map {
switch $0 {
case let .labeledPrice(label, amount):
return BotPaymentPrice(label: label, amount: amount)
}
})
}
}
}
func _internal_validateBotPaymentForm(account: Account, saveInfo: Bool, source: BotPaymentInvoiceSource, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
return account.postbox.transaction { transaction -> Api.InputInvoice? in
return _internal_parseInputInvoice(transaction: transaction, source: source)
}
|> castError(ValidateBotPaymentFormError.self)
|> mapToSignal { invoice -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> in
guard let invoice = invoice else {
return .fail(.generic)
}
var flags: Int32 = 0
if saveInfo {
flags |= (1 << 0)
}
var infoFlags: Int32 = 0
if let _ = formInfo.name {
infoFlags |= (1 << 0)
}
if let _ = formInfo.phone {
infoFlags |= (1 << 1)
}
if let _ = formInfo.email {
infoFlags |= (1 << 2)
}
var apiShippingAddress: Api.PostAddress?
if let address = formInfo.shippingAddress {
infoFlags |= (1 << 3)
apiShippingAddress = .postAddress(streetLine1: address.streetLine1, streetLine2: address.streetLine2, city: address.city, state: address.state, countryIso2: address.countryIso2, postCode: address.postCode)
}
return account.network.request(Api.functions.payments.validateRequestedInfo(flags: flags, invoice: invoice, info: .paymentRequestedInfo(flags: infoFlags, name: formInfo.name, phone: formInfo.phone, email: formInfo.email, shippingAddress: apiShippingAddress)))
|> mapError { error -> ValidateBotPaymentFormError in
if error.errorDescription == "SHIPPING_NOT_AVAILABLE" {
return .shippingNotAvailable
} else if error.errorDescription == "ADDRESS_STATE_INVALID" {
return .addressStateInvalid
} else if error.errorDescription == "ADDRESS_POSTCODE_INVALID" {
return .addressPostcodeInvalid
} else if error.errorDescription == "ADDRESS_CITY_INVALID" {
return .addressCityInvalid
} else if error.errorDescription == "REQ_INFO_NAME_INVALID" {
return .nameInvalid
} else if error.errorDescription == "REQ_INFO_EMAIL_INVALID" {
return .emailInvalid
} else if error.errorDescription == "REQ_INFO_PHONE_INVALID" {
return .phoneInvalid
} else {
return .generic
}
}
|> map { result -> BotPaymentValidatedFormInfo in
switch result {
case let .validatedRequestedInfo(_, id, shippingOptions):
return BotPaymentValidatedFormInfo(id: id, shippingOptions: shippingOptions.flatMap {
return $0.map(BotPaymentShippingOption.init)
})
}
}
}
}
public enum BotPaymentCredentials {
case generic(data: String, saveOnServer: Bool)
case saved(id: String, tempPassword: Data)
case applePay(data: String)
}
public enum SendBotPaymentFormError {
case generic
case precheckoutFailed
case paymentFailed
case alreadyPaid
case starGiftOutOfStock
case disallowedStarGift
case starGiftUserLimit
case serverProvided(String)
}
public enum SendBotPaymentResult {
case done(receiptMessageId: MessageId?, subscriptionPeerId: PeerId?, uniqueStarGift: ProfileGiftsContext.State.StarGift?)
case externalVerificationRequired(url: String)
}
func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPaymentInvoiceSource, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
return account.postbox.transaction { transaction -> Api.InputInvoice? in
return _internal_parseInputInvoice(transaction: transaction, source: source)
}
|> castError(SendBotPaymentFormError.self)
|> mapToSignal { invoice -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
guard let invoice = invoice else {
return .fail(.generic)
}
let apiCredentials: Api.InputPaymentCredentials
switch credentials {
case let .generic(data, saveOnServer):
var credentialsFlags: Int32 = 0
if saveOnServer {
credentialsFlags |= (1 << 0)
}
apiCredentials = .inputPaymentCredentials(flags: credentialsFlags, data: .dataJSON(data: data))
case let .saved(id, tempPassword):
apiCredentials = .inputPaymentCredentialsSaved(id: id, tmpPassword: Buffer(data: tempPassword))
case let .applePay(data):
apiCredentials = .inputPaymentCredentialsApplePay(paymentData: .dataJSON(data: data))
}
var flags: Int32 = 0
if validatedInfoId != nil {
flags |= (1 << 0)
}
if shippingOptionId != nil {
flags |= (1 << 1)
}
if tipAmount != nil {
flags |= (1 << 2)
}
return account.network.request(Api.functions.payments.sendPaymentForm(flags: flags, formId: formId, invoice: invoice, requestedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, credentials: apiCredentials, tipAmount: tipAmount))
|> map { result -> SendBotPaymentResult in
switch result {
case let .paymentResult(updates):
account.stateManager.addUpdates(updates)
var receiptMessageId: MessageId?
switch source {
case .starsChatSubscription:
let chats = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) }
if let first = chats.first {
return .done(receiptMessageId: nil, subscriptionPeerId: first.id, uniqueStarGift: nil)
}
default:
break
}
for apiMessage in updates.messages {
if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: account.peerId, peerIsForum: false) {
for media in message.media {
if let action = media as? TelegramMediaAction {
if case .paymentSent = action.action {
switch source {
case let .slug(slug):
for media in message.media {
if let action = media as? TelegramMediaAction, case let .paymentSent(_, _, invoiceSlug?, _, _) = action.action, invoiceSlug == slug {
if case let .Id(id) = message.id {
receiptMessageId = id
}
}
}
case let .message(messageId):
for attribute in message.attributes {
if let reply = attribute as? ReplyMessageAttribute {
if reply.messageId == messageId {
if case let .Id(id) = message.id {
receiptMessageId = id
}
}
}
}
case let .premiumGiveaway(_, _, _, _, _, _, randomId, _, _, _, _):
if message.globallyUniqueId == randomId {
if case let .Id(id) = message.id {
receiptMessageId = id
}
}
case let .starsGiveaway(_, _, _, _, _, _, _, randomId, _, _, _, _):
if message.globallyUniqueId == randomId {
if case let .Id(id) = message.id {
receiptMessageId = id
}
}
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid:
receiptMessageId = nil
}
}
}
}
}
}
return .done(receiptMessageId: receiptMessageId, subscriptionPeerId: nil, uniqueStarGift: nil)
case let .paymentVerificationNeeded(url):
return .externalVerificationRequired(url: url)
}
}
|> `catch` { error -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
if error.errorDescription == "BOT_PRECHECKOUT_FAILED" {
return .fail(.precheckoutFailed)
} else if error.errorDescription == "PAYMENT_FAILED" {
return .fail(.paymentFailed)
} else if error.errorDescription == "INVOICE_ALREADY_PAID" {
return .fail(.alreadyPaid)
}
return .fail(.generic)
}
}
}
public struct BotPaymentReceipt : Equatable {
public let invoice: BotPaymentInvoice
public let date: Int32
public let info: BotPaymentRequestedInfo?
public let shippingOption: BotPaymentShippingOption?
public let credentialsTitle: String
public let invoiceMedia: TelegramMediaInvoice
public let tipAmount: Int64?
public let botPaymentId: PeerId
public let transactionId: String?
public static func ==(lhs: BotPaymentReceipt, rhs: BotPaymentReceipt) -> Bool {
if lhs.invoice != rhs.invoice {
return false
}
if lhs.date != rhs.date {
return false
}
if lhs.info != rhs.info {
return false
}
if lhs.shippingOption != rhs.shippingOption {
return false
}
if lhs.credentialsTitle != rhs.credentialsTitle {
return false
}
if !lhs.invoiceMedia.isEqual(to: rhs.invoiceMedia) {
return false
}
if lhs.tipAmount != rhs.tipAmount {
return false
}
if lhs.botPaymentId != rhs.botPaymentId {
return false
}
if lhs.transactionId != rhs.transactionId {
return false
}
return true
}
}
public enum RequestBotPaymentReceiptError {
case generic
}
func _internal_requestBotPaymentReceipt(account: Account, messageId: MessageId) -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> {
let accountPeerId = account.peerId
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> castError(RequestBotPaymentReceiptError.self)
|> mapToSignal { inputPeer -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> in
guard let inputPeer = inputPeer else {
return .fail(.generic)
}
return account.network.request(Api.functions.payments.getPaymentReceipt(peer: inputPeer, msgId: messageId.id))
|> mapError { _ -> RequestBotPaymentReceiptError in
return .generic
}
|> mapToSignal { result -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> in
return account.postbox.transaction { transaction -> BotPaymentReceipt in
switch result {
case let .paymentReceipt(_, date, botId, _, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init)
let shippingOption = shipping.flatMap(BotPaymentShippingOption.init)
let invoiceMedia = TelegramMediaInvoice(
title: title,
description: description,
photo: photo.flatMap(TelegramMediaWebFile.init),
receiptMessageId: nil,
currency: currency,
totalAmount: totalAmount,
startParam: "",
extendedMedia: nil,
subscriptionPeriod: parsedInvoice.subscriptionPeriod,
flags: [],
version: TelegramMediaInvoice.lastVersion
)
let botPaymentId = PeerId.init(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId))
return BotPaymentReceipt(invoice: parsedInvoice, date: date, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle, invoiceMedia: invoiceMedia, tipAmount: tipAmount, botPaymentId: botPaymentId, transactionId: nil)
case let .paymentReceiptStars(_, date, botId, title, description, photo, invoice, currency, totalAmount, transactionId, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
let invoiceMedia = TelegramMediaInvoice(
title: title,
description: description,
photo: photo.flatMap(TelegramMediaWebFile.init),
receiptMessageId: nil,
currency: currency,
totalAmount: totalAmount,
startParam: "",
extendedMedia: nil,
subscriptionPeriod: parsedInvoice.subscriptionPeriod,
flags: [],
version: TelegramMediaInvoice.lastVersion
)
let botPaymentId = PeerId.init(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId))
return BotPaymentReceipt(invoice: parsedInvoice, date: date, info: nil, shippingOption: nil, credentialsTitle: "", invoiceMedia: invoiceMedia, tipAmount: nil, botPaymentId: botPaymentId, transactionId: transactionId)
}
}
|> castError(RequestBotPaymentReceiptError.self)
}
}
}
public struct BotPaymentInfo: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let paymentInfo = BotPaymentInfo(rawValue: 1 << 0)
public static let shippingInfo = BotPaymentInfo(rawValue: 1 << 1)
}
func _internal_clearBotPaymentInfo(network: Network, info: BotPaymentInfo) -> Signal<Void, NoError> {
var flags: Int32 = 0
if info.contains(.paymentInfo) {
flags |= (1 << 0)
}
if info.contains(.shippingInfo) {
flags |= (1 << 1)
}
return network.request(Api.functions.payments.clearSavedInfo(flags: flags))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
@@ -0,0 +1,420 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public struct PremiumGiftCodeInfo: Equatable {
public let slug: String
public let fromPeerId: EnginePeer.Id?
public let messageId: EngineMessage.Id?
public let toPeerId: EnginePeer.Id?
public let date: Int32
public let months: Int32
public let usedDate: Int32?
public let isGiveaway: Bool
}
public struct PremiumGiftCodeOption: Codable, Equatable {
enum CodingKeys: String, CodingKey {
case users
case months
case storeProductId
case storeQuantity
case currency
case amount
}
public let users: Int32
public let months: Int32
public let storeProductId: String?
public let storeQuantity: Int32
public let currency: String
public let amount: Int64
public init(users: Int32, months: Int32, storeProductId: String?, storeQuantity: Int32, currency: String, amount: Int64) {
self.users = users
self.months = months
self.storeProductId = storeProductId
self.storeQuantity = storeQuantity
self.currency = currency
self.amount = amount
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.users = try container.decode(Int32.self, forKey: .users)
self.months = try container.decode(Int32.self, forKey: .months)
self.storeProductId = try container.decodeIfPresent(String.self, forKey: .storeProductId)
self.storeQuantity = try container.decodeIfPresent(Int32.self, forKey: .storeQuantity) ?? 1
self.currency = try container.decode(String.self, forKey: .currency)
self.amount = try container.decode(Int64.self, forKey: .amount)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.users, forKey: .users)
try container.encode(self.months, forKey: .months)
try container.encodeIfPresent(self.storeProductId, forKey: .storeProductId)
try container.encode(self.storeQuantity, forKey: .storeQuantity)
try container.encode(self.currency, forKey: .currency)
try container.encode(self.amount, forKey: .amount)
}
}
public enum PremiumGiveawayInfo: Equatable {
public enum OngoingStatus: Equatable {
public enum DisallowReason: Equatable {
case joinedTooEarly(Int32)
case channelAdmin(EnginePeer.Id)
case disallowedCountry(String)
}
case notQualified
case notAllowed(DisallowReason)
case participating
case almostOver
}
public enum ResultStatus: Equatable {
case notWon
case wonPremium(slug: String)
case wonStars(stars: Int64)
case refunded
}
case ongoing(startDate: Int32, status: OngoingStatus)
case finished(status: ResultStatus, startDate: Int32, finishDate: Int32, winnersCount: Int32, activatedCount: Int32?)
}
public struct PrepaidGiveaway: Equatable {
public enum Prize: Equatable {
case premium(months: Int32)
case stars(stars: Int64, boosts: Int32)
}
public let id: Int64
public let prize: Prize
public let quantity: Int32
public let date: Int32
}
func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal<PremiumGiveawayInfo?, NoError> {
return account.postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer in
guard let inputPeer = apiInputPeer(peer) else {
return .complete()
}
return account.network.request(Api.functions.payments.getGiveawayInfo(peer: inputPeer, msgId: messageId.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.GiveawayInfo?, NoError> in
return .single(nil)
}
|> map { result -> PremiumGiveawayInfo? in
if let result = result {
switch result {
case let .giveawayInfo(flags, startDate, joinedTooEarlyDate, adminDisallowedChatId, disallowedCountry):
if (flags & (1 << 3)) != 0 {
return .ongoing(startDate: startDate, status: .almostOver)
} else if (flags & (1 << 0)) != 0 {
return .ongoing(startDate: startDate, status: .participating)
} else if let disallowedCountry = disallowedCountry {
return .ongoing(startDate: startDate, status: .notAllowed(.disallowedCountry(disallowedCountry)))
} else if let joinedTooEarlyDate = joinedTooEarlyDate {
return .ongoing(startDate: startDate, status: .notAllowed(.joinedTooEarly(joinedTooEarlyDate)))
} else if let adminDisallowedChatId = adminDisallowedChatId {
return .ongoing(startDate: startDate, status: .notAllowed(.channelAdmin(EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(adminDisallowedChatId)))))
} else {
return .ongoing(startDate: startDate, status: .notQualified)
}
case let .giveawayInfoResults(flags, startDate, giftCodeSlug, stars, finishDate, winnersCount, activatedCount):
let status: PremiumGiveawayInfo.ResultStatus
if (flags & (1 << 1)) != 0 {
status = .refunded
} else if let stars {
status = .wonStars(stars: stars)
} else if let giftCodeSlug = giftCodeSlug {
status = .wonPremium(slug: giftCodeSlug)
} else {
status = .notWon
}
return .finished(status: status, startDate: startDate, finishDate: finishDate, winnersCount: winnersCount, activatedCount: activatedCount)
}
} else {
return nil
}
}
}
}
public final class CachedPremiumGiftCodeOptions: Codable {
public let options: [PremiumGiftCodeOption]
public init(options: [PremiumGiftCodeOption]) {
self.options = options
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.options = try container.decode([PremiumGiftCodeOption].self, forKey: "t")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.options, forKey: "t")
}
}
func _internal_premiumGiftCodeOptions(account: Account, peerId: EnginePeer.Id?, onlyCached: Bool = false) -> Signal<[PremiumGiftCodeOption], NoError> {
if let peerId {
if peerId.namespace == Namespaces.Peer.SecretChat {
return .single([])
}
}
let cached = account.postbox.transaction { transaction -> Signal<[PremiumGiftCodeOption], NoError> in
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPremiumGiftCodeOptions, key: ValueBoxKey(length: 0)))?.get(CachedPremiumGiftCodeOptions.self) {
return .single(entry.options)
}
return .single([])
} |> switchToLatest
let remote = account.postbox.transaction { transaction -> Peer? in
if let peerId = peerId {
return transaction.getPeer(peerId)
}
return nil
}
|> mapToSignal { peer in
let inputPeer = peer.flatMap(apiInputPeer)
var flags: Int32 = 0
if let _ = inputPeer {
flags |= 1 << 0
}
return account.network.request(Api.functions.payments.getPremiumGiftCodeOptions(flags: flags, boostPeer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<[Api.PremiumGiftCodeOption]?, NoError> in
return .single(nil)
}
|> mapToSignal { results -> Signal<[PremiumGiftCodeOption], NoError> in
let options = results?.map { PremiumGiftCodeOption(apiGiftCodeOption: $0) } ?? []
return account.postbox.transaction { transaction -> [PremiumGiftCodeOption] in
if peerId == nil {
if let entry = CodableEntry(CachedPremiumGiftCodeOptions(options: options)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPremiumGiftCodeOptions, key: ValueBoxKey(length: 0)), entry: entry)
}
}
return options
}
}
}
if peerId == nil {
return cached
|> mapToSignal { cached in
if onlyCached && !cached.isEmpty {
return .single(cached)
} else {
return .single(cached)
|> then(remote)
}
}
} else {
return remote
}
}
func _internal_premiumGiftCodeOptions(account: Account, peerId: EnginePeer.Id?) -> Signal<[PremiumGiftCodeOption], NoError> {
if let peerId {
if peerId.namespace == Namespaces.Peer.SecretChat {
return .single([])
}
}
var flags: Int32 = 0
if let _ = peerId {
flags |= 1 << 0
}
return account.postbox.transaction { transaction -> Peer? in
if let peerId = peerId {
return transaction.getPeer(peerId)
}
return nil
}
|> mapToSignal { peer in
let inputPeer = peer.flatMap(apiInputPeer)
return account.network.request(Api.functions.payments.getPremiumGiftCodeOptions(flags: flags, boostPeer: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<[Api.PremiumGiftCodeOption]?, NoError> in
return .single(nil)
}
|> mapToSignal { results -> Signal<[PremiumGiftCodeOption], NoError> in
if let results = results {
return .single(results.map { PremiumGiftCodeOption(apiGiftCodeOption: $0) })
} else {
return .single([])
}
}
}
}
func _internal_checkPremiumGiftCode(account: Account, slug: String) -> Signal<PremiumGiftCodeInfo?, NoError> {
return account.network.request(Api.functions.payments.checkGiftCode(slug: slug))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.CheckedGiftCode?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<PremiumGiftCodeInfo?, NoError> in
if let result = result {
switch result {
case let .checkedGiftCode(_, _, _, _, _, _, _, chats, users):
return account.postbox.transaction { transaction in
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
return PremiumGiftCodeInfo(apiCheckedGiftCode: result, slug: slug)
}
}
} else {
return .single(nil)
}
}
}
public enum ApplyPremiumGiftCodeError {
case generic
case waitForExpiration(Int32)
}
func _internal_applyPremiumGiftCode(account: Account, slug: String) -> Signal<Never, ApplyPremiumGiftCodeError> {
return account.network.request(Api.functions.payments.applyGiftCode(slug: slug))
|> mapError { error -> ApplyPremiumGiftCodeError in
if error.errorDescription.hasPrefix("PREMIUM_SUB_ACTIVE_UNTIL_") {
if let range = error.errorDescription.range(of: "_", options: .backwards) {
if let value = Int32(error.errorDescription[range.upperBound...]) {
return .waitForExpiration(value)
}
}
}
return .generic
}
|> mapToSignal { updates -> Signal<Never, ApplyPremiumGiftCodeError> in
account.stateManager.addUpdates(updates)
return .complete()
}
}
public enum LaunchPrepaidGiveawayError {
case generic
}
public enum LaunchGiveawayPurpose {
case premium
case stars(stars: Int64, users: Int32)
}
func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, purpose: LaunchGiveawayPurpose, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) -> Signal<Never, LaunchPrepaidGiveawayError> {
return account.postbox.transaction { transaction -> Signal<Never, LaunchPrepaidGiveawayError> in
var flags: Int32 = 0
if onlyNewSubscribers {
flags |= (1 << 0)
}
if showWinners {
flags |= (1 << 3)
}
var inputPeer: Api.InputPeer?
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
inputPeer = apiPeer
}
var additionalPeers: [Api.InputPeer] = []
if !additionalPeerIds.isEmpty {
flags |= (1 << 1)
for peerId in additionalPeerIds {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
additionalPeers.append(inputPeer)
}
}
}
if !countries.isEmpty {
flags |= (1 << 2)
}
if let _ = prizeDescription {
flags |= (1 << 4)
}
guard let inputPeer = inputPeer else {
return .complete()
}
let inputPurpose: Api.InputStorePaymentPurpose
switch purpose {
case let .stars(stars, users):
inputPurpose = .inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: inputPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: "", amount: 0, users: users)
case .premium:
inputPurpose = .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: inputPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: "", amount: 0)
}
return account.network.request(Api.functions.payments.launchPrepaidGiveaway(peer: inputPeer, giveawayId: id, purpose: inputPurpose))
|> mapError { _ -> LaunchPrepaidGiveawayError in
return .generic
}
|> mapToSignal { updates -> Signal<Never, LaunchPrepaidGiveawayError> in
account.stateManager.addUpdates(updates)
return .complete()
}
}
|> castError(LaunchPrepaidGiveawayError.self)
|> switchToLatest
}
extension PremiumGiftCodeOption {
init(apiGiftCodeOption: Api.PremiumGiftCodeOption) {
switch apiGiftCodeOption {
case let .premiumGiftCodeOption(_, users, months, storeProduct, storeQuantity, curreny, amount):
self.init(users: users, months: months, storeProductId: storeProduct, storeQuantity: storeQuantity ?? 1, currency: curreny, amount: amount)
}
}
}
extension PremiumGiftCodeInfo {
init(apiCheckedGiftCode: Api.payments.CheckedGiftCode, slug: String) {
switch apiCheckedGiftCode {
case let .checkedGiftCode(flags, fromId, giveawayMsgId, toId, date, months, usedDate, _, _):
self.slug = slug
self.fromPeerId = fromId?.peerId
if let fromId = fromId, let giveawayMsgId = giveawayMsgId {
self.messageId = EngineMessage.Id(peerId: fromId.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)
} else {
self.messageId = nil
}
self.toPeerId = toId.flatMap { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value($0)) }
self.date = date
self.months = months
self.usedDate = usedDate
self.isGiveaway = (flags & (1 << 2)) != 0
}
}
}
public extension PremiumGiftCodeInfo {
var isUsed: Bool {
return self.usedDate != nil
}
}
extension PrepaidGiveaway {
init(apiPrepaidGiveaway: Api.PrepaidGiveaway) {
switch apiPrepaidGiveaway {
case let .prepaidGiveaway(id, months, quantity, date):
self.id = id
self.prize = .premium(months: months)
self.quantity = quantity
self.date = date
case let .prepaidStarsGiveaway(id, stars, quantity, boosts, date):
self.id = id
self.prize = .stars(stars: stars, boosts: boosts)
self.quantity = quantity
self.date = date
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,656 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum StarGiftAuctionReference: Equatable {
case giftId(Int64)
case slug(String)
var apiAuction: Api.InputStarGiftAuction {
switch self {
case let .giftId(giftId):
return .inputStarGiftAuction(giftId: giftId)
case let .slug(slug):
return .inputStarGiftAuctionSlug(slug: slug)
}
}
}
private func _internal_getStarGiftAuctionState(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id, reference: StarGiftAuctionReference, version: Int32) -> Signal<(gift: StarGift, state: GiftAuctionContext.State.AuctionState?, myState: GiftAuctionContext.State.MyState, timeout: Int32)?, NoError> {
return network.request(Api.functions.payments.getStarGiftAuctionState(auction: reference.apiAuction, version: version))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.StarGiftAuctionState?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<(gift: StarGift, state: GiftAuctionContext.State.AuctionState?, myState: GiftAuctionContext.State.MyState, timeout: Int32)?, NoError> in
guard let result else {
return .single(nil)
}
return postbox.transaction { transaction -> (gift: StarGift, state: GiftAuctionContext.State.AuctionState?, myState: GiftAuctionContext.State.MyState, timeout: Int32)? in
switch result {
case let .starGiftAuctionState(apiGift, state, userState, timeout, users, chats):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(chats: chats, users: users))
guard let gift = StarGift(apiStarGift: apiGift) else {
return nil
}
return (
gift: gift,
state: GiftAuctionContext.State.AuctionState(apiAuctionState: state, transaction: transaction),
myState: GiftAuctionContext.State.MyState(apiAuctionUserState: userState),
timeout: timeout
)
}
}
}
}
public final class GiftAuctionContext {
public struct State: Equatable {
public struct BidLevel: Equatable {
public var position: Int32
public var amount: Int64
public var date: Int32
}
public enum Round: Equatable {
case generic(num: Int32, duration: Int32)
case extendable(num: Int32, duration: Int32, extendTop: Int32, extendWindow: Int32)
public var num: Int32 {
switch self {
case let .generic(num, _), let .extendable(num, _, _, _):
return num
}
}
public var duration: Int32 {
switch self {
case let .generic(_, duration), let .extendable(_, duration, _, _):
return duration
}
}
}
public enum AuctionState: Equatable {
case ongoing(version: Int32, startDate: Int32, endDate: Int32, minBidAmount: Int64, bidLevels: [BidLevel], topBidders: [EnginePeer], nextRoundDate: Int32, giftsLeft: Int32, currentRound: Int32, totalRounds: Int32, rounds: [Round], lastGiftNumber: Int32)
case finished(startDate: Int32, endDate: Int32, averagePrice: Int64, listedCount: Int32?, fragmentListedCount: Int32?, fragmentListedUrl: String?)
}
public struct MyState: Equatable {
public var isReturned: Bool
public var bidAmount: Int64?
public var bidDate: Int32?
public var minBidAmount: Int64?
public var bidPeerId: EnginePeer.Id?
public var acquiredCount: Int32
}
public var gift: StarGift
public var auctionState: AuctionState
public var myState: MyState
}
private let queue: Queue = .mainQueue()
private let account: Account
public let gift: StarGift
public var isActive: Bool {
if case .finished = auctionState {
return false
} else {
return myState?.bidAmount != nil
}
}
private let disposable = MetaDisposable()
private var auctionState: State.AuctionState?
private var myState: State.MyState?
private var timeout: Int32?
private var updateTimer: SwiftSignalKit.Timer?
private let stateValue = Promise<State?>()
public var state: Signal<State?, NoError> {
return self.stateValue.get()
}
public var currentBidPeerId: EnginePeer.Id? {
if self.myState?.bidAmount != nil, case .ongoing = self.auctionState {
return self.myState?.bidPeerId
} else {
return nil
}
}
public var isFinished: Bool {
if case .finished = self.auctionState {
return true
} else {
return false
}
}
public var isUpcoming: Bool {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if case let .ongoing(_, startTime, _, _, _, _, _, _, _, _, _, _) = self.auctionState {
return currentTime < startTime
} else {
return false
}
}
public convenience init(account: Account, gift: StarGift) {
self.init(account: account, gift: gift, initialAuctionState: nil, initialMyState: nil, initialTimeout: nil)
}
init(account: Account, gift: StarGift, initialAuctionState: State.AuctionState?, initialMyState: State.MyState?, initialTimeout: Int32?) {
self.account = account
self.gift = gift
self.auctionState = initialAuctionState
self.myState = initialMyState
self.timeout = initialTimeout
self.load()
}
deinit {
self.updateTimer?.invalidate()
self.disposable.dispose()
}
private var currentVersion: Int32 {
var currentVersion: Int32 = 0
if case let .ongoing(version, _, _, _, _, _, _, _, _, _, _, _) = self.auctionState {
currentVersion = version
}
return currentVersion
}
public func load() {
self.pushState()
self.disposable.set((_internal_getStarGiftAuctionState(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, reference: .giftId(self.gift.giftId), version: self.currentVersion)
|> deliverOn(self.queue)).start(next: { [weak self] data in
guard let self else {
return
}
guard let (_, auctionState, myState, timeout) = data else {
return
}
if case let .ongoing(version, _, _, _, _, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion {
} else if let auctionState {
self.auctionState = auctionState
}
self.myState = myState
self.timeout = timeout
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
var effectiveTimeout = timeout
if case let .ongoing(_, _, _, _, _, _, nextRoundDate, _, _, _, _, _) = auctionState {
let delta = nextRoundDate - currentTime
if delta > 0 && delta < timeout {
effectiveTimeout = delta
}
}
self.pushState()
self.updateTimer?.invalidate()
self.updateTimer = SwiftSignalKit.Timer(timeout: Double(effectiveTimeout), repeat: false, completion: { [weak self] _ in
guard let self else {
return
}
self.load()
}, queue: Queue.mainQueue())
self.updateTimer?.start()
}))
}
func updateAuctionState(_ auctionState: GiftAuctionContext.State.AuctionState) {
if case let .ongoing(version, _, _, _, _, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion {
} else {
self.auctionState = auctionState
}
self.pushState()
}
func updateMyState(_ myState: GiftAuctionContext.State.MyState) {
self.myState = myState
self.pushState()
}
private func pushState() {
if let auctionState = self.auctionState, let myState = self.myState {
self.stateValue.set(
.single(State(
gift: self.gift,
auctionState: auctionState,
myState: myState
))
)
} else {
self.stateValue.set(.single(nil))
}
}
}
extension GiftAuctionContext.State.BidLevel {
init(apiBidLevel: Api.AuctionBidLevel) {
switch apiBidLevel {
case let .auctionBidLevel(pos, amount, date):
self.position = pos
self.amount = amount
self.date = date
}
}
}
extension GiftAuctionContext.State.AuctionState {
init?(apiAuctionState: Api.StarGiftAuctionState, peers: [PeerId: Peer]) {
switch apiAuctionState {
case let .starGiftAuctionState(version, startDate, endDate, minBidAmount, bidLevels, topBiddersPeerIds, nextRoundAt, lastGiftNumber, giftsLeft, currentRound, totalRounds, apiRounds):
var topBidders: [EnginePeer] = []
for peerId in topBiddersPeerIds {
if let peer = peers[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(peerId))] {
topBidders.append(EnginePeer(peer))
}
}
var rounds: [GiftAuctionContext.State.Round] = []
for apiRound in apiRounds {
switch apiRound {
case let .starGiftAuctionRound(num, duration):
rounds.append(.generic(num: num, duration: duration))
case let .starGiftAuctionRoundExtendable(num, duration, extendTop, extendWindow):
rounds.append(.extendable(num: num, duration: duration, extendTop: extendTop, extendWindow: extendWindow))
}
}
self = .ongoing(
version: version,
startDate: startDate,
endDate: endDate,
minBidAmount: minBidAmount,
bidLevels: bidLevels.map(GiftAuctionContext.State.BidLevel.init(apiBidLevel:)),
topBidders: topBidders,
nextRoundDate: nextRoundAt,
giftsLeft: giftsLeft,
currentRound: currentRound,
totalRounds: totalRounds,
rounds: rounds,
lastGiftNumber: lastGiftNumber
)
case let .starGiftAuctionStateFinished(_, startDate, endDate, averagePrice, listedCount, fragmentListedCount, fragmentListedUrl):
self = .finished(
startDate: startDate,
endDate: endDate,
averagePrice: averagePrice,
listedCount: listedCount,
fragmentListedCount: fragmentListedCount,
fragmentListedUrl: fragmentListedUrl
)
case .starGiftAuctionStateNotModified:
return nil
}
}
init?(apiAuctionState: Api.StarGiftAuctionState, transaction: Transaction) {
switch apiAuctionState {
case let .starGiftAuctionState(version, startDate, endDate, minBidAmount, bidLevels, topBiddersPeerIds, nextRoundAt, lastGiftNumber, giftsLeft, currentRound, totalRounds, apiRounds):
var topBidders: [EnginePeer] = []
for peerId in topBiddersPeerIds {
if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(peerId))) {
topBidders.append(EnginePeer(peer))
}
}
var rounds: [GiftAuctionContext.State.Round] = []
for apiRound in apiRounds {
switch apiRound {
case let .starGiftAuctionRound(num, duration):
rounds.append(.generic(num: num, duration: duration))
case let .starGiftAuctionRoundExtendable(num, duration, extendTop, extendWindow):
rounds.append(.extendable(num: num, duration: duration, extendTop: extendTop, extendWindow: extendWindow))
}
}
self = .ongoing(
version: version,
startDate: startDate,
endDate: endDate,
minBidAmount: minBidAmount,
bidLevels: bidLevels.map(GiftAuctionContext.State.BidLevel.init(apiBidLevel:)),
topBidders: topBidders,
nextRoundDate: nextRoundAt,
giftsLeft: giftsLeft,
currentRound: currentRound,
totalRounds: totalRounds,
rounds: rounds,
lastGiftNumber: lastGiftNumber
)
case let .starGiftAuctionStateFinished(_, startDate, endDate, averagePrice, listedCount, fragmentListedCount, fragmentListedUrl):
self = .finished(
startDate: startDate,
endDate: endDate,
averagePrice: averagePrice,
listedCount: listedCount,
fragmentListedCount: fragmentListedCount,
fragmentListedUrl: fragmentListedUrl
)
case .starGiftAuctionStateNotModified:
return nil
}
}
}
extension GiftAuctionContext.State.MyState {
init(apiAuctionUserState: Api.StarGiftAuctionUserState) {
switch apiAuctionUserState {
case let .starGiftAuctionUserState(flags, bidAmount, bidDate, minBidAmount, bidPeerId, acquiredCount):
self.isReturned = (flags & (1 << 1)) != 0
self.bidAmount = bidAmount
self.bidDate = bidDate
self.minBidAmount = minBidAmount
self.bidPeerId = bidPeerId?.peerId
self.acquiredCount = acquiredCount
}
}
}
public struct GiftAuctionAcquiredGift: Equatable {
public var nameHidden: Bool
public let peer: EnginePeer
public let date: Int32
public let bidAmount: Int64
public let round: Int32
public let position: Int32
public let text: String?
public let entities: [MessageTextEntity]?
public let number: Int32?
}
func _internal_getGiftAuctionAcquiredGifts(account: Account, giftId: Int64) -> Signal<[GiftAuctionAcquiredGift], NoError> {
return account.network.request(Api.functions.payments.getStarGiftAuctionAcquiredGifts(giftId: giftId))
|> map(Optional.init)
|> `catch` { _ in
return .single(nil)
}
|> mapToSignal { result in
guard let result else {
return .single([])
}
return account.postbox.transaction { transaction -> [GiftAuctionAcquiredGift] in
switch result {
case let .starGiftAuctionAcquiredGifts(gifts, users, chats):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var mappedGifts: [GiftAuctionAcquiredGift] = []
for gift in gifts {
switch gift {
case let .starGiftAuctionAcquiredGift(flags, peerId, date, bidAmount, round, pos, message, number):
if let peer = transaction.getPeer(peerId.peerId) {
var text: String?
var entities: [MessageTextEntity]?
switch message {
case let .textWithEntities(textValue, entitiesValue):
text = textValue
entities = messageTextEntitiesFromApiEntities(entitiesValue)
default:
break
}
mappedGifts.append(GiftAuctionAcquiredGift(
nameHidden: (flags & (1 << 0)) != 0,
peer: EnginePeer(peer),
date: date,
bidAmount: bidAmount,
round: round,
position: pos,
text: text,
entities: entities,
number: number
))
}
}
}
return mappedGifts
}
}
}
}
func _internal_getActiveGiftAuctions(account: Account, hash: Int64) -> Signal<[GiftAuctionContext]?, NoError> {
return account.network.request(Api.functions.payments.getStarGiftActiveAuctions(hash: hash))
|> retryRequest
|> mapToSignal { result in
return account.postbox.transaction { transaction -> [GiftAuctionContext]? in
switch result {
case let .starGiftActiveAuctions(auctions, users, chats):
let parsedPeers = AccumulatedPeers(chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var auctionContexts: [GiftAuctionContext] = []
for auction in auctions {
switch auction {
case let .starGiftActiveAuctionState(apiGift, auctionState, userState):
guard let gift = StarGift(apiStarGift: apiGift) else {
continue
}
auctionContexts.append(GiftAuctionContext(
account: account,
gift: gift,
initialAuctionState: GiftAuctionContext.State.AuctionState(apiAuctionState: auctionState, transaction: transaction),
initialMyState: GiftAuctionContext.State.MyState(apiAuctionUserState: userState),
initialTimeout: nil
))
}
}
return auctionContexts
case .starGiftActiveAuctionsNotModified:
return nil
}
}
}
}
public class GiftAuctionsManager {
private let account: Account
private var auctionContexts: [Int64 : GiftAuctionContext] = [:]
private let disposable = MetaDisposable()
private var updateAuctionStateDisposable: Disposable?
private var updateMyStateDisposable: Disposable?
private let statePromise = Promise<[GiftAuctionContext.State]>([])
public var state: Signal<[GiftAuctionContext.State], NoError> {
return self.statePromise.get()
}
public init(account: Account) {
self.account = account
self.updateAuctionStateDisposable = (self.account.stateManager.updatedStarGiftAuctionState()
|> deliverOnMainQueue).start(next: { [weak self] updates in
guard let self else {
return
}
var reload = false
for (giftId, update) in updates {
if let auctionContext = self.auctionContexts[giftId] {
auctionContext.updateAuctionState(update)
} else if case .ongoing = update {
reload = true
break
}
}
if reload {
self.reload()
}
})
self.updateMyStateDisposable = (self.account.stateManager.updatedStarGiftAuctionMyState()
|> deliverOnMainQueue).start(next: { [weak self] updates in
guard let self else {
return
}
var reload = false
for (giftId, update) in updates {
if let auctionContext = self.auctionContexts[giftId] {
auctionContext.updateMyState(update)
} else {
reload = true
break
}
}
if reload {
self.reload()
}
})
self.reload()
}
deinit {
self.disposable.dispose()
self.updateAuctionStateDisposable?.dispose()
self.updateMyStateDisposable?.dispose()
}
public func reload() {
self.disposable.set((_internal_getActiveGiftAuctions(account: self.account, hash: 0)
|> deliverOnMainQueue).startStrict(next: { [weak self] activeAuctions in
guard let self, let activeAuctions else {
return
}
for auction in activeAuctions {
if self.auctionContexts[auction.gift.giftId] == nil {
self.auctionContexts[auction.gift.giftId] = auction
}
}
self.updateState()
}))
}
public func auctionContext(for reference: StarGiftAuctionReference) -> Signal<GiftAuctionContext?, NoError> {
if case let .giftId(id) = reference, let current = self.auctionContexts[id] {
return .single(current)
} else {
return _internal_getStarGiftAuctionState(
postbox: self.account.postbox,
network: self.account.network,
accountPeerId: self.account.peerId,
reference: reference,
version: 0
) |> mapToSignal { [weak self] result in
if let self, let result {
let auctionContext = GiftAuctionContext(account: self.account, gift: result.gift, initialAuctionState: result.state, initialMyState: result.myState, initialTimeout: result.timeout)
self.auctionContexts[result.gift.giftId] = auctionContext
self.updateState()
return .single(auctionContext)
} else {
return .single(nil)
}
}
}
}
public func storeAuctionContext(auctionContext: GiftAuctionContext) {
self.auctionContexts[auctionContext.gift.giftId] = auctionContext
self.updateState()
}
private func updateState() {
var signals: [Signal<GiftAuctionContext.State?, NoError>] = []
for auction in self.auctionContexts.values.sorted(by: { $0.gift.giftId < $1.gift.giftId }) {
signals.append(auction.state)
}
self.statePromise.set(combineLatest(signals)
|> map { states -> [GiftAuctionContext.State] in
var filteredStates: [GiftAuctionContext.State] = []
for state in states {
if let state, case .ongoing = state.auctionState, state.myState.bidAmount != nil {
filteredStates.append(state)
}
}
return filteredStates
})
}
}
public extension GiftAuctionContext.State {
func getPlace(myBid: Int64?, myBidDate: Int32?) -> Int32? {
guard case let .ongoing(_, _, _, _, bidLevels, _, _, _, _, _, _, _) = self.auctionState else {
return nil
}
guard let myBid = myBid ?? self.myState.bidAmount else {
return nil
}
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let myBidDate = self.myState.bidDate ?? currentTime
let levels = bidLevels
guard !levels.isEmpty else {
return 1
}
func isWorse(than level: GiftAuctionContext.State.BidLevel) -> Bool {
if myBid < level.amount {
return true
}
if myBid == level.amount, myBidDate > level.date {
return true
}
return false
}
var lowerIndex: Int = -1
for (i, level) in levels.enumerated() {
if isWorse(than: level) {
lowerIndex = i
} else {
break
}
}
if lowerIndex == -1 {
return 1
}
let lowerPosition = levels[lowerIndex].position
let nextPosition: Int32
let nextIndex = lowerIndex + 1
if nextIndex < levels.count {
nextPosition = levels[nextIndex].position
} else {
nextPosition = lowerPosition
}
if nextPosition == lowerPosition + 1 {
return lowerPosition + 1
} else {
return nextPosition
}
}
var place: Int32? {
return self.getPlace(myBid: nil, myBidDate: nil)
}
var startDate: Int32 {
switch self.auctionState {
case let .ongoing(_, startDate, _, _, _, _, _, _, _, _, _, _):
return startDate
case let .finished(startDate, _, _, _, _, _):
return startDate
}
}
var endDate: Int32 {
switch self.auctionState {
case let .ongoing(_, _, endDate, _, _, _, _, _, _, _, _, _):
return endDate
case let .finished(_, endDate, _, _, _, _):
return endDate
}
}
}
@@ -0,0 +1,459 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public struct StarGiftCollection: Codable, Equatable {
public let id: Int32
public let title: String
public let icon: TelegramMediaFile?
public let count: Int32
public let hash: Int64
public init(id: Int32, title: String, icon: TelegramMediaFile?, count: Int32, hash: Int64) {
self.id = id
self.title = title
self.icon = icon
self.count = count
self.hash = hash
}
public static func ==(lhs: StarGiftCollection, rhs: StarGiftCollection) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.icon != rhs.icon {
return false
}
if lhs.count != rhs.count {
return false
}
if lhs.hash != rhs.hash {
return false
}
return true
}
}
extension StarGiftCollection {
init?(apiStarGiftCollection: Api.StarGiftCollection) {
switch apiStarGiftCollection {
case let .starGiftCollection(_, collectionId, title, icon, giftsCount, hash):
self.id = collectionId
self.title = title
self.icon = icon.flatMap { telegramMediaFileFromApiDocument($0, altDocuments: nil) }
self.count = giftsCount
self.hash = hash
}
}
}
private final class CachedProfileGiftsCollections: Codable {
enum CodingKeys: String, CodingKey {
case collections
}
let collections: [StarGiftCollection]
init(collections: [StarGiftCollection]) {
self.collections = collections
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.collections = try container.decode([StarGiftCollection].self, forKey: .collections)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.collections, forKey: .collections)
}
}
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedProfileGiftsCollections, key: cacheKey)
}
private func intListSimpleHash(_ list: [Int64]) -> Int64 {
var acc: Int64 = 0
for value in list {
acc = ((acc * 20261) + Int64(0x80000000) + Int64(value)) % Int64(0x80000000)
}
return Int64(Int32(truncatingIfNeeded: acc))
}
private func _internal_getStarGiftCollections(postbox: Postbox, network: Network, peerId: EnginePeer.Id) -> Signal<[StarGiftCollection]?, NoError> {
return postbox.transaction { transaction -> (Api.InputPeer, [StarGiftCollection]?)? in
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
let collections = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileGiftsCollections.self)
return (inputPeer, collections?.collections)
}
|> mapToSignal { inputPeerAndCollections -> Signal<[StarGiftCollection]?, NoError> in
guard let (inputPeer, cachedCollections) = inputPeerAndCollections else {
return .single(nil)
}
var hash: Int64 = 0
if let cachedCollections {
hash = intListSimpleHash(cachedCollections.map { $0.hash })
}
return .single(cachedCollections)
|> then(
network.request(Api.functions.payments.getStarGiftCollections(peer: inputPeer, hash: hash))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.payments.StarGiftCollections?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[StarGiftCollection]?, NoError> in
guard let result else {
return .single(nil)
}
return postbox.transaction { transaction -> [StarGiftCollection]? in
switch result {
case let .starGiftCollections(collections):
let collections = collections.compactMap { StarGiftCollection(apiStarGiftCollection: $0) }
return collections
case .starGiftCollectionsNotModified:
return cachedCollections ?? []
}
}
}
)
}
}
private func _internal_createStarGiftCollection(account: Account, peerId: EnginePeer.Id, title: String, starGifts: [ProfileGiftsContext.State.StarGift]) -> Signal<StarGiftCollection?, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer, [Api.InputSavedStarGift])? in
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
let inputStarGifts = starGifts.compactMap { $0.reference }.compactMap { $0.apiStarGiftReference(transaction: transaction) }
return (inputPeer, inputStarGifts)
}
|> mapToSignal { inputPeerAndGifts -> Signal<StarGiftCollection?, NoError> in
guard let (inputPeer, inputStarGifts) = inputPeerAndGifts else {
return .single(nil)
}
return account.network.request(Api.functions.payments.createStarGiftCollection(peer: inputPeer, title: title, stargift: inputStarGifts))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StarGiftCollection?, NoError> in
return .single(nil)
}
|> map { result -> StarGiftCollection? in
guard let result else {
return nil
}
return StarGiftCollection(apiStarGiftCollection: result)
}
|> beforeNext { collection in
let _ = account.postbox.transaction { transaction in
if let collection, let entry = CodableEntry(CachedProfileGifts(gifts: starGifts.map { $0.withPinnedToTop(false) }, count: Int32(starGifts.count), notificationsEnabled: nil)) {
transaction.putItemCacheEntry(id: giftsEntryId(peerId: peerId, collectionId: collection.id), entry: entry)
}
}.start()
}
}
}
private func _internal_reorderStarGiftCollections(account: Account, peerId: EnginePeer.Id, order: [Int32]) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Bool, NoError> in
guard let inputPeer else {
return .single(false)
}
return account.network.request(Api.functions.payments.reorderStarGiftCollections(peer: inputPeer, order: order))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> map { result -> Bool in
if let result, case .boolTrue = result {
return true
}
return false
}
}
}
private func _internal_updateStarGiftCollection(account: Account, peerId: EnginePeer.Id, collectionId: Int32, giftsContext: ProfileGiftsContext?, allGiftsContext: ProfileGiftsContext?, actions: [ProfileGiftsCollectionsContext.UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
for action in actions {
switch action {
case let .addGifts(gifts):
let gifts = gifts.map { gift in
var collectionIds = gift.collectionIds ?? []
collectionIds.append(collectionId)
return gift.withCollectionIds(collectionIds)
}
giftsContext?.insertStarGifts(gifts: gifts)
case let .removeGifts(gifts):
giftsContext?.removeStarGifts(references: gifts)
case let .reorderGifts(gifts):
giftsContext?.reorderStarGifts(references: gifts)
default:
break
}
}
return account.postbox.transaction { transaction -> (Api.InputPeer, (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.StarGiftCollection>))? in
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
return nil
}
var flags: Int32 = 0
var title: String?
var deleteStarGift: [Api.InputSavedStarGift] = []
var addStarGift: [Api.InputSavedStarGift] = []
var order: [Api.InputSavedStarGift] = []
for action in actions {
switch action {
case let .updateTitle(newTitle):
flags |= (1 << 0)
title = newTitle
case let .addGifts(gifts):
flags |= (1 << 2)
addStarGift.append(contentsOf: gifts.compactMap { $0.reference }.compactMap { $0.apiStarGiftReference(transaction: transaction) })
case let .removeGifts(gifts):
flags |= (1 << 1)
deleteStarGift.append(contentsOf: gifts.compactMap { $0.apiStarGiftReference(transaction: transaction) })
case let .reorderGifts(gifts):
flags |= (1 << 3)
order = gifts.compactMap { $0.apiStarGiftReference(transaction: transaction) }
}
}
let request = Api.functions.payments.updateStarGiftCollection(flags: flags, peer: inputPeer, collectionId: collectionId, title: title, deleteStargift: deleteStarGift, addStargift: addStarGift, order: order)
return (inputPeer, request)
}
|> mapToSignal { peerAndRequest -> Signal<StarGiftCollection?, NoError> in
guard let (_, request) = peerAndRequest else {
return .single(nil)
}
return account.network.request(request)
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.StarGiftCollection?, NoError> in
return .single(nil)
}
|> map { result -> StarGiftCollection? in
guard let result else {
return nil
}
return StarGiftCollection(apiStarGiftCollection: result)
}
}
}
private func _internal_deleteStarGiftCollection(account: Account, peerId: EnginePeer.Id, collectionId: Int32) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Bool, NoError> in
guard let inputPeer else {
return .single(false)
}
return account.network.request(Api.functions.payments.deleteStarGiftCollection(peer: inputPeer, collectionId: collectionId))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> map { result -> Bool in
if let result, case .boolTrue = result {
return true
}
return false
}
}
}
public final class ProfileGiftsCollectionsContext {
public struct State: Equatable {
public var collections: [StarGiftCollection]
public var isLoading: Bool
}
public enum UpdateAction {
case updateTitle(String)
case addGifts([ProfileGiftsContext.State.StarGift])
case removeGifts([StarGiftReference])
case reorderGifts([StarGiftReference])
}
private let queue: Queue = .mainQueue()
private let account: Account
private let peerId: EnginePeer.Id
private weak var allGiftsContext: ProfileGiftsContext?
private let disposable = MetaDisposable()
private var collections: [StarGiftCollection] = []
private var giftsContexts: [Int32: ProfileGiftsContext] = [:]
private var isLoading: Bool = false
private let stateValue = Promise<State>()
public var state: Signal<State, NoError> {
return self.stateValue.get()
}
public init(account: Account, peerId: EnginePeer.Id, allGiftsContext: ProfileGiftsContext?) {
self.account = account
self.peerId = peerId
self.allGiftsContext = allGiftsContext
self.reload()
}
deinit {
self.disposable.dispose()
}
public func giftsContextForCollection(id: Int32) -> ProfileGiftsContext {
if let current = self.giftsContexts[id] {
return current
} else {
let giftsContext = ProfileGiftsContext(account: self.account, peerId: self.peerId, collectionId: id)
self.giftsContexts[id] = giftsContext
return giftsContext
}
}
public func reload() {
guard !self.isLoading else { return }
self.isLoading = true
self.pushState()
self.disposable.set((_internal_getStarGiftCollections(postbox: self.account.postbox, network: self.account.network, peerId: self.peerId)
|> deliverOn(self.queue)).start(next: { [weak self] collections in
guard let self else {
return
}
self.collections = collections ?? []
self.isLoading = false
self.pushState()
self.updateCache()
}))
}
public func createCollection(title: String, starGifts: [ProfileGiftsContext.State.StarGift]) -> Signal<StarGiftCollection?, NoError> {
return _internal_createStarGiftCollection(account: self.account, peerId: self.peerId, title: title, starGifts: starGifts)
|> deliverOn(self.queue)
|> beforeNext { [weak self] collection in
guard let self else {
return
}
if let collection {
self.collections.append(collection)
self.pushState()
self.updateCache()
}
}
}
public func updateCollection(id: Int32, actions: [UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
let giftsContext = self.giftsContextForCollection(id: id)
return _internal_updateStarGiftCollection(account: self.account, peerId: self.peerId, collectionId: id, giftsContext: giftsContext, allGiftsContext: self.allGiftsContext, actions: actions)
|> deliverOn(self.queue)
|> afterNext { [weak self] collection in
guard let self else {
return
}
if let collection {
if let index = self.collections.firstIndex(where: { $0.id == id }) {
self.collections[index] = collection
self.pushState()
self.updateCache()
}
}
}
}
public func addGifts(id: Int32, gifts: [ProfileGiftsContext.State.StarGift]) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.addGifts(gifts)])
}
public func removeGifts(id: Int32, gifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.removeGifts(gifts)])
}
public func reorderGifts(id: Int32, gifts: [StarGiftReference]) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.reorderGifts(gifts)])
}
public func renameCollection(id: Int32, title: String) -> Signal<StarGiftCollection?, NoError> {
return self.updateCollection(id: id, actions: [.updateTitle(title)])
}
public func reorderCollections(order: [Int32]) -> Signal<Bool, NoError> {
let peerId = self.peerId
return _internal_reorderStarGiftCollections(account: self.account, peerId: peerId, order: order)
|> deliverOn(self.queue)
|> afterNext { [weak self] collection in
guard let self else {
return
}
var collectionMap: [Int32: StarGiftCollection] = [:]
for collection in self.collections {
collectionMap[collection.id] = collection
}
var collections: [StarGiftCollection] = []
for id in order {
if let collection = collectionMap[id] {
collections.append(collection)
}
}
self.collections = collections
self.pushState()
self.updateCache()
}
}
public func deleteCollection(id: Int32) -> Signal<Bool, NoError> {
return _internal_deleteStarGiftCollection(account: self.account, peerId: self.peerId, collectionId: id)
|> deliverOn(self.queue)
|> afterNext { [weak self] _ in
guard let self else {
return
}
self.giftsContexts.removeValue(forKey: id)
self.collections.removeAll(where: { $0.id == id })
self.pushState()
self.updateCache()
}
}
private func updateCache() {
let peerId = self.peerId
let collections = self.collections
let _ = (self.account.postbox.transaction { transaction in
if let entry = CodableEntry(CachedProfileGiftsCollections(collections: collections)) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
}).start()
}
private func pushState() {
let state = State(
collections: self.collections,
isLoading: self.isLoading
)
self.stateValue.set(.single(state))
}
}
@@ -0,0 +1,55 @@
import Foundation
import Postbox
import MtProtoKit
import SwiftSignalKit
import TelegramApi
public enum ResolveStarGiftOfferError {
case generic
}
func _internal_resolveStarGiftOffer(account: Account, messageId: EngineMessage.Id, accept: Bool) -> Signal<Never, ResolveStarGiftOfferError> {
var flags: Int32 = 0
if !accept {
flags |= (1 << 0)
}
return account.network.request(Api.functions.payments.resolveStarGiftOffer(flags: flags, offerMsgId: messageId.id))
|> mapError { _ -> ResolveStarGiftOfferError in
return .generic
}
|> mapToSignal { updates -> Signal<Never, ResolveStarGiftOfferError> in
account.stateManager.addUpdates(updates)
return .complete()
}
|> ignoreValues
}
public enum SendStarGiftOfferError {
case generic
}
func _internal_sendStarGiftOffer(account: Account, peerId: EnginePeer.Id, slug: String, amount: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) -> Signal<Never, SendStarGiftOfferError> {
var flags: Int32 = 0
if let _ = allowPaidStars {
flags |= (1 << 0)
}
return account.postbox.transaction { transaction in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> castError(SendStarGiftOfferError.self)
|> mapToSignal { inputPeer -> Signal<Never, SendStarGiftOfferError> in
guard let inputPeer else {
return .fail(.generic)
}
return account.network.request(Api.functions.payments.sendStarGiftOffer(flags: flags, peer: inputPeer, slug: slug, price: amount.apiAmount, duration: duration, randomId: Int64.random(in: .min ..< .max), allowPaidStars: allowPaidStars))
|> mapError { _ -> SendStarGiftOfferError in
return .generic
}
|> mapToSignal { updates -> Signal<Never, SendStarGiftOfferError> in
account.stateManager.addUpdates(updates)
return .complete()
}
}
|> ignoreValues
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
import Foundation
import SwiftSignalKit
import Postbox
public extension TelegramEngine {
final class Payments {
private let account: Account
init(account: Account) {
self.account = account
}
public func getBankCardInfo(cardNumber: String) -> Signal<BankCardInfo?, NoError> {
return _internal_getBankCardInfo(account: self.account, cardNumber: cardNumber)
}
public func fetchBotPaymentInvoice(source: BotPaymentInvoiceSource) -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> {
return _internal_fetchBotPaymentInvoice(postbox: self.account.postbox, network: self.account.network, source: source)
}
public func fetchBotPaymentForm(source: BotPaymentInvoiceSource, themeParams: [String: Any]?) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
return _internal_fetchBotPaymentForm(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, source: source, themeParams: themeParams)
}
public func validateBotPaymentForm(saveInfo: Bool, source: BotPaymentInvoiceSource, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
return _internal_validateBotPaymentForm(account: self.account, saveInfo: saveInfo, source: source, formInfo: formInfo)
}
public func sendBotPaymentForm(source: BotPaymentInvoiceSource, formId: Int64, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
return _internal_sendBotPaymentForm(account: self.account, formId: formId, source: source, validatedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, tipAmount: tipAmount, credentials: credentials)
}
public func requestBotPaymentReceipt(messageId: MessageId) -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> {
return _internal_requestBotPaymentReceipt(account: self.account, messageId: messageId)
}
public func clearBotPaymentInfo(info: BotPaymentInfo) -> Signal<Void, NoError> {
return _internal_clearBotPaymentInfo(network: self.account.network, info: info)
}
public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, receipt: receipt, purpose: purpose)
}
public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return _internal_canPurchasePremium(postbox: self.account.postbox, network: self.account.network, purpose: purpose)
}
public func checkPremiumGiftCode(slug: String) -> Signal<PremiumGiftCodeInfo?, NoError> {
return _internal_checkPremiumGiftCode(account: self.account, slug: slug)
}
public func applyPremiumGiftCode(slug: String) -> Signal<Never, ApplyPremiumGiftCodeError> {
return _internal_applyPremiumGiftCode(account: self.account, slug: slug)
}
public func premiumGiftCodeOptions(peerId: EnginePeer.Id?, onlyCached: Bool = false) -> Signal<[PremiumGiftCodeOption], NoError> {
return _internal_premiumGiftCodeOptions(account: self.account, peerId: peerId, onlyCached: onlyCached)
}
public func premiumGiveawayInfo(peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal<PremiumGiveawayInfo?, NoError> {
return _internal_getPremiumGiveawayInfo(account: self.account, peerId: peerId, messageId: messageId)
}
public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, purpose: LaunchGiveawayPurpose, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) -> Signal<Never, LaunchPrepaidGiveawayError> {
return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, purpose: purpose, id: id, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate)
}
public func starsTopUpOptions() -> Signal<[StarsTopUpOption], NoError> {
return _internal_starsTopUpOptions(account: self.account)
}
public func starsGiftOptions(peerId: EnginePeer.Id?) -> Signal<[StarsGiftOption], NoError> {
return _internal_starsGiftOptions(account: self.account, peerId: peerId)
}
public func starsGiveawayOptions() -> Signal<[StarsGiveawayOption], NoError> {
return _internal_starsGiveawayOptions(account: self.account)
}
public func peerStarsContext() -> StarsContext {
return StarsContext(account: self.account, ton: false)
}
public func peerTonContext() -> StarsContext {
return StarsContext(account: self.account, ton: true)
}
public func peerStarsRevenueContext(peerId: EnginePeer.Id, ton: Bool) -> StarsRevenueStatsContext {
return StarsRevenueStatsContext(account: self.account, peerId: peerId, ton: ton)
}
public func peerStarsTransactionsContext(subject: StarsTransactionsContext.Subject, mode: StarsTransactionsContext.Mode) -> StarsTransactionsContext {
return StarsTransactionsContext(account: self.account, subject: subject, mode: mode)
}
public func peerStarsSubscriptionsContext(starsContext: StarsContext?, missingBalance: Bool = false) -> StarsSubscriptionsContext {
return StarsSubscriptionsContext(account: self.account, starsContext: starsContext, missingBalance: missingBalance)
}
public func sendStarsPaymentForm(formId: Int64, source: BotPaymentInvoiceSource) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source)
}
public func fulfillStarsSubscription(peerId: EnginePeer.Id, subscriptionId: String) -> Signal<Never, FulfillStarsSubsciptionError> {
return _internal_fulfillStarsSubscription(account: self.account, peerId: peerId, subscriptionId: subscriptionId)
}
public func cachedStarGifts() -> Signal<[StarGift]?, NoError> {
return _internal_cachedStarGifts(postbox: self.account.postbox)
|> map { starGiftsList in
return starGiftsList?.items
}
}
public func keepStarGiftsUpdated() -> Signal<Never, NoError> {
return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
}
public func convertStarGift(reference: StarGiftReference) -> Signal<Never, NoError> {
return _internal_convertStarGift(account: self.account, reference: reference)
}
public func updateStarGiftAddedToProfile(reference: StarGiftReference, added: Bool) -> Signal<Never, NoError> {
return _internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: added)
}
public func dropStarGiftOriginalDetails(reference: StarGiftReference) -> Signal<Never, DropStarGiftOriginalDetailsError> {
return _internal_dropStarGiftOriginalDetails(account: self.account, reference: reference)
}
public func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal<Never, TransferStarGiftError> {
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
}
public func buyStarGift(slug: String, peerId: EnginePeer.Id, price: CurrencyAmount?) -> Signal<Never, BuyStarGiftError> {
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId, price: price)
}
public func upgradeStarGift(formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
return _internal_upgradeStarGift(account: self.account, formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
}
public func starGiftUpgradePreview(giftId: Int64) -> Signal<StarGiftUpgradePreview?, NoError> {
return _internal_starGiftUpgradePreview(account: self.account, giftId: giftId)
}
public func checkCanSendStarGift(giftId: Int64) -> Signal<CanSendGiftResult, NoError> {
return _internal_checkCanSendStarGift(account: self.account, giftId: giftId)
}
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift?, NoError> {
return _internal_getUniqueStarGift(account: self.account, slug: slug)
}
public func getUniqueStarGiftValueInfo(slug: String) -> Signal<StarGift.UniqueGift.ValueInfo?, NoError> {
return _internal_getUniqueStarGiftValueInfo(account: self.account, slug: slug)
}
public func checkStarGiftWithdrawalAvailability(reference: StarGiftReference) -> Signal<Never, RequestStarGiftWithdrawalError> {
return _internal_checkStarGiftWithdrawalAvailability(account: self.account, reference: reference)
}
public func requestStarGiftWithdrawalUrl(reference: StarGiftReference, password: String) -> Signal<String, RequestStarGiftWithdrawalError> {
return _internal_requestStarGiftWithdrawalUrl(account: account, reference: reference, password: password)
}
public func toggleStarGiftsNotifications(peerId: EnginePeer.Id, enabled: Bool) -> Signal<Never, NoError> {
return _internal_toggleStarGiftsNotifications(account: self.account, peerId: peerId, enabled: enabled)
}
public func updateStarGiftResalePrice(reference: StarGiftReference, price: CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError> {
return _internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price)
}
public func getGiftAuctionAcquiredGifts(giftId: Int64) -> Signal<[GiftAuctionAcquiredGift], NoError> {
return _internal_getGiftAuctionAcquiredGifts(account: self.account, giftId: giftId)
}
public func getStarsTransaction(reference: StarsTransactionReference) -> Signal<StarsContext.State.Transaction?, NoError> {
return _internal_getStarsTransaction(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, transactionReference: reference)
}
public func resolveStarGiftOffer(messageId: EngineMessage.Id, accept: Bool) -> Signal<Never, ResolveStarGiftOfferError> {
return _internal_resolveStarGiftOffer(account: self.account, messageId: messageId, accept: accept)
}
public func sendStarGiftOffer(peerId: EnginePeer.Id, slug: String, amount: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) -> Signal<Never, SendStarGiftOfferError> {
return _internal_sendStarGiftOffer(account: self.account, peerId: peerId, slug: slug, amount: amount, duration: duration, allowPaidStars: allowPaidStars)
}
public func getStarGiftUpgradeAttributes(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute]?, NoError> {
return _internal_getStarGiftUpgradeAttributes(account: self.account, giftId: giftId)
}
}
}
public extension TelegramEngineUnauthorized {
final class Payments {
private let account: UnauthorizedAccount
init(account: UnauthorizedAccount) {
self.account = account
}
public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return _internal_canPurchasePremium(postbox: self.account.postbox, network: self.account.network, purpose: purpose)
}
public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, receipt: receipt, purpose: purpose)
}
}
}