Stars giveaways [skip ci]

This commit is contained in:
Ilya Laktyushin
2024-08-23 18:44:16 +04:00
parent bc33cc7e9b
commit 5941becc33
41 changed files with 1623 additions and 476 deletions

View File

@@ -19,6 +19,7 @@ public enum AppStoreTransactionPurpose {
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)
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)
}
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
@@ -101,6 +102,36 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
}
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 account.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
}
}

View File

@@ -79,12 +79,13 @@ public enum PremiumGiveawayInfo: Equatable {
public enum ResultStatus: Equatable {
case notWon
case won(slug: String)
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)
case finished(status: ResultStatus, startDate: Int32, finishDate: Int32, winnersCount: Int32, activatedCount: Int32?)
}
public struct PrepaidGiveaway: Equatable {
@@ -122,12 +123,14 @@ func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, m
} else {
return .ongoing(startDate: startDate, status: .notQualified)
}
case let .giveawayInfoResults(flags, startDate, giftCodeSlug, finishDate, winnersCount, activatedCount):
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 = .won(slug: giftCodeSlug)
status = .wonPremium(slug: giftCodeSlug)
} else {
status = .notWon
}

View File

@@ -145,6 +145,130 @@ func _internal_starsGiftOptions(account: Account, peerId: EnginePeer.Id?) -> Sig
}
}
public struct StarsGiveawayOption: Equatable, Codable {
enum CodingKeys: String, CodingKey {
case count
case currency
case amount
case yearlyBoosts
case storeProductId
case winners
case isExtended
case isDefault
}
public struct Winners: Equatable, Codable {
enum CodingKeys: String, CodingKey {
case users
case starsPerUser
case isDefault
}
public let users: Int32
public let starsPerUser: Int64
public let isDefault: Bool
public init(users: Int32, starsPerUser: Int64, isDefault: Bool) {
self.users = users
self.starsPerUser = starsPerUser
self.isDefault = isDefault
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.users = try container.decode(Int32.self, forKey: .users)
self.starsPerUser = try container.decode(Int64.self, forKey: .starsPerUser)
self.isDefault = try container.decodeIfPresent(Bool.self, forKey: .isDefault) ?? false
}
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.starsPerUser, forKey: .starsPerUser)
try container.encode(self.isDefault, forKey: .isDefault)
}
}
public let count: Int64
public let yearlyBoosts: Int32
public let currency: String
public let amount: Int64
public let storeProductId: String?
public let winners: [Winners]
public let isExtended: Bool
public let isDefault: Bool
public init(count: Int64, yearlyBoosts: Int32, storeProductId: String?, currency: String, amount: Int64, winners: [Winners], isExtended: Bool, isDefault: Bool) {
self.count = count
self.yearlyBoosts = yearlyBoosts
self.currency = currency
self.amount = amount
self.storeProductId = storeProductId
self.winners = winners
self.isExtended = isExtended
self.isDefault = isDefault
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.count = try container.decode(Int64.self, forKey: .count)
self.yearlyBoosts = try container.decode(Int32.self, forKey: .yearlyBoosts)
self.storeProductId = try container.decodeIfPresent(String.self, forKey: .storeProductId)
self.currency = try container.decode(String.self, forKey: .currency)
self.amount = try container.decode(Int64.self, forKey: .amount)
self.winners = try container.decode([StarsGiveawayOption.Winners].self, forKey: .winners)
self.isExtended = try container.decodeIfPresent(Bool.self, forKey: .isExtended) ?? false
self.isDefault = try container.decodeIfPresent(Bool.self, forKey: .isDefault) ?? false
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.count, forKey: .count)
try container.encode(self.yearlyBoosts, forKey: .yearlyBoosts)
try container.encodeIfPresent(self.storeProductId, forKey: .storeProductId)
try container.encode(self.currency, forKey: .currency)
try container.encode(self.amount, forKey: .amount)
try container.encode(self.winners, forKey: .winners)
try container.encode(self.isExtended, forKey: .isExtended)
try container.encode(self.isDefault, forKey: .isDefault)
}
}
extension StarsGiveawayOption.Winners {
init(apiStarsGiveawayWinnersOption: Api.StarsGiveawayWinnersOption) {
switch apiStarsGiveawayWinnersOption {
case let .starsGiveawayWinnersOption(flags, users, starsPerUser):
self.init(users: users, starsPerUser: starsPerUser, isDefault: (flags & (1 << 0)) != 0)
}
}
}
extension StarsGiveawayOption {
init(apiStarsGiveawayOption: Api.StarsGiveawayOption) {
switch apiStarsGiveawayOption {
case let .starsGiveawayOption(flags, stars, yearlyBoosts, storeProduct, currency, amount, winners):
self.init(count: stars, yearlyBoosts: yearlyBoosts, storeProductId: storeProduct, currency: currency, amount: amount, winners: winners.map { StarsGiveawayOption.Winners(apiStarsGiveawayWinnersOption: $0) }, isExtended: (flags & (1 << 0)) != 0, isDefault: (flags & (1 << 1)) != 0)
}
}
}
func _internal_starsGiveawayOptions(account: Account) -> Signal<[StarsGiveawayOption], NoError> {
return account.network.request(Api.functions.payments.getStarsGiveawayOptions())
|> map(Optional.init)
|> `catch` { _ -> Signal<[Api.StarsGiveawayOption]?, NoError> in
return .single(nil)
}
|> mapToSignal { results -> Signal<[StarsGiveawayOption], NoError> in
if let results = results {
return .single(results.map { StarsGiveawayOption(apiStarsGiveawayOption: $0) })
} else {
return .single([])
}
}
}
struct InternalStarsStatus {
let balance: Int64
let subscriptionsMissingBalance: Int64?
@@ -344,7 +468,7 @@ private final class StarsContextImpl {
}
var transactions = state.transactions
if addTransaction {
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, media: [], subscriptionPeriod: nil), at: 0)
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, giveawayMessageId: nil, media: [], subscriptionPeriod: nil), at: 0)
}
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(0, state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
@@ -366,9 +490,11 @@ private final class StarsContextImpl {
private extension StarsContext.State.Transaction {
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
switch apiTransaction {
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod):
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId):
let parsedPeer: StarsContext.State.Transaction.Peer
var paidMessageId: MessageId?
var giveawayMessageId: MessageId?
switch transactionPeer {
case .starsTransactionPeerAppStore:
parsedPeer = .appStore
@@ -394,6 +520,9 @@ private extension StarsContext.State.Transaction {
paidMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: messageId)
}
}
if let giveawayPostId {
giveawayMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: giveawayPostId)
}
}
var flags: Flags = []
@@ -415,7 +544,7 @@ private extension StarsContext.State.Transaction {
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
let _ = subscriptionPeriod
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, media: media, subscriptionPeriod: subscriptionPeriod)
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod)
}
}
}
@@ -481,6 +610,7 @@ public final class StarsContext {
public let transactionDate: Int32?
public let transactionUrl: String?
public let paidMessageId: MessageId?
public let giveawayMessageId: MessageId?
public let media: [Media]
public let subscriptionPeriod: Int32?
@@ -496,6 +626,7 @@ public final class StarsContext {
transactionDate: Int32?,
transactionUrl: String?,
paidMessageId: MessageId?,
giveawayMessageId: MessageId?,
media: [Media],
subscriptionPeriod: Int32?
) {
@@ -510,6 +641,7 @@ public final class StarsContext {
self.transactionDate = transactionDate
self.transactionUrl = transactionUrl
self.paidMessageId = paidMessageId
self.giveawayMessageId = giveawayMessageId
self.media = media
self.subscriptionPeriod = subscriptionPeriod
}
@@ -548,6 +680,9 @@ public final class StarsContext {
if lhs.paidMessageId != rhs.paidMessageId {
return false
}
if lhs.giveawayMessageId != rhs.giveawayMessageId {
return false
}
if !areMediaArraysEqual(lhs.media, rhs.media) {
return false
}

View File

@@ -74,6 +74,10 @@ public extension TelegramEngine {
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)
}

View File

@@ -93,6 +93,7 @@ public enum AdminLogEventAction {
case changeWallpaper(prev: TelegramWallpaper?, new: TelegramWallpaper?)
case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?)
case changeEmojiPack(prev: StickerPackReference?, new: StickerPackReference?)
case participantSubscriptionExtended(prev: RenderedChannelParticipant, new: RenderedChannelParticipant)
}
public enum ChannelAdminLogEventError {
@@ -449,6 +450,13 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
action = .changeEmojiPack(prev: StickerPackReference(apiInputSet: prevStickerset), new: StickerPackReference(apiInputSet: newStickerset))
case let .channelAdminLogEventActionToggleSignatureProfiles(newValue):
action = .toggleSignatureProfiles(boolFromApiValue(newValue))
case let .channelAdminLogEventActionParticipantSubExtend(prev, new):
let prevParticipant = ChannelParticipant(apiParticipant: prev)
let newParticipant = ChannelParticipant(apiParticipant: new)
if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] {
action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer))
}
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
if let action = action {