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,46 @@
import Foundation
import Postbox
public final class ChatUpdatingMessageMedia: Equatable {
public let text: String
public let entities: TextEntitiesMessageAttribute?
public let disableUrlPreview: Bool
public let media: RequestEditMessageMedia
public let invertMediaAttribute: InvertMediaMessageAttribute?
public let progress: Float
init(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, media: RequestEditMessageMedia, invertMediaAttribute: InvertMediaMessageAttribute?, progress: Float) {
self.text = text
self.entities = entities
self.disableUrlPreview = disableUrlPreview
self.media = media
self.invertMediaAttribute = invertMediaAttribute
self.progress = progress
}
public static func ==(lhs: ChatUpdatingMessageMedia, rhs: ChatUpdatingMessageMedia) -> Bool {
if lhs.text != rhs.text {
return false
}
if lhs.entities != rhs.entities {
return false
}
if lhs.disableUrlPreview != rhs.disableUrlPreview {
return false
}
if lhs.media != rhs.media {
return false
}
if (lhs.invertMediaAttribute == nil) != (rhs.invertMediaAttribute == nil) {
return false
}
if lhs.progress != rhs.progress {
return false
}
return true
}
func withProgress(_ progress: Float) -> ChatUpdatingMessageMedia {
return ChatUpdatingMessageMedia(text: self.text, entities: self.entities, disableUrlPreview: self.disableUrlPreview, media: self.media, invertMediaAttribute: self.invertMediaAttribute, progress: progress)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,405 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public final class PeerMediaUploadingItem: Equatable {
public enum ProgressValue {
case progress(Float)
case done(Api.Updates)
}
public enum Error {
case generic
case flood
}
public enum Content: Equatable {
case wallpaper(wallpaper: TelegramWallpaper, forBoth: Bool)
}
public let content: Content
public let messageId: EngineMessage.Id?
public let progress: Float
init(content: Content, messageId: EngineMessage.Id?, progress: Float) {
self.content = content
self.messageId = messageId
self.progress = progress
}
public static func ==(lhs: PeerMediaUploadingItem, rhs: PeerMediaUploadingItem) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.messageId != rhs.messageId {
return false
}
if lhs.progress != rhs.progress {
return false
}
return true
}
func withMessageId(_ messageId: EngineMessage.Id) -> PeerMediaUploadingItem {
return PeerMediaUploadingItem(content: self.content, messageId: messageId, progress: self.progress)
}
func withProgress(_ progress: Float) -> PeerMediaUploadingItem {
return PeerMediaUploadingItem(content: self.content, messageId: self.messageId, progress: progress)
}
}
private func uploadPeerMedia(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) -> Signal<PeerMediaUploadingItem.ProgressValue, PeerMediaUploadingItem.Error> {
switch content {
case let .wallpaper(wallpaper, forBoth):
if case let .image(representations, settings) = wallpaper, let resource = representations.last?.resource as? LocalFileMediaResource {
return _internal_uploadWallpaper(postbox: postbox, network: network, resource: resource, settings: settings, forChat: true)
|> mapError { error -> PeerMediaUploadingItem.Error in
return .generic
}
|> mapToSignal { value -> Signal<PeerMediaUploadingItem.ProgressValue, PeerMediaUploadingItem.Error> in
switch value {
case let .progress(progress):
return .single(.progress(progress))
case let .complete(result):
if case let .file(file) = result {
postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true)
for representation in file.file.previewRepresentations {
postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id, synchronous: true)
}
}
return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: result, forBoth: forBoth, applyUpdates: false)
|> mapError { error -> PeerMediaUploadingItem.Error in
switch error {
case .generic:
return .generic
case .flood:
return .flood
}
}
|> map { updates -> PeerMediaUploadingItem.ProgressValue in
return .done(updates)
}
}
}
} else {
return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: wallpaper, forBoth: forBoth, applyUpdates: false)
|> mapError { error -> PeerMediaUploadingItem.Error in
switch error {
case .generic:
return .generic
case .flood:
return .flood
}
}
|> map { updates -> PeerMediaUploadingItem.ProgressValue in
return .done(updates)
}
}
}
}
private func generatePeerMediaMessage(network: Network, accountPeerId: EnginePeer.Id, transaction: Transaction, peerId: PeerId, content: PeerMediaUploadingItem.Content) -> StoreMessage {
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
var timestamp = Int32(network.context.globalTime())
switch peerId.namespace {
case Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudUser:
if let topIndex = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) {
timestamp = max(timestamp, topIndex.timestamp)
}
default:
break
}
var flags = StoreMessageFlags()
flags.insert(.Unsent)
flags.insert(.Sending)
var attributes: [MessageAttribute] = []
attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId, flags: [], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [], partialReference: nil))
var media: [Media] = []
switch content {
case let .wallpaper(wallpaper, forBoth):
media.append(TelegramMediaAction(action: .setChatWallpaper(wallpaper: wallpaper, forBoth: forBoth)))
}
return StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, customStableId: nil, globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: accountPeerId, text: "", attributes: attributes, media: media)
}
private final class PendingPeerMediaUploadContext {
var value: PeerMediaUploadingItem
let disposable = MetaDisposable()
init(value: PeerMediaUploadingItem) {
self.value = value
}
}
private final class PendingPeerMediaUploadManagerImpl {
let queue: Queue
let postbox: Postbox
let network: Network
let stateManager: AccountStateManager
let accountPeerId: EnginePeer.Id
let sentMessageEventPipe: ValuePipe<EnginePeer.Id>
private var uploadingPeerMediaValue: [EnginePeer.Id: PeerMediaUploadingItem] = [:] {
didSet {
if self.uploadingPeerMediaValue != oldValue {
self.uploadingPeerMediaPromise.set(.single(self.uploadingPeerMediaValue))
}
}
}
private let uploadingPeerMediaPromise = Promise<[EnginePeer.Id: PeerMediaUploadingItem]>()
fileprivate var uploadingPeerMedia: Signal<[EnginePeer.Id: PeerMediaUploadingItem], NoError> {
return self.uploadingPeerMediaPromise.get()
}
private var contexts: [PeerId: PendingPeerMediaUploadContext] = [:]
init(queue: Queue, postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: EnginePeer.Id) {
self.queue = queue
self.postbox = postbox
self.network = network
self.stateManager = stateManager
self.accountPeerId = accountPeerId
self.sentMessageEventPipe = ValuePipe()
self.uploadingPeerMediaPromise.set(.single(self.uploadingPeerMediaValue))
}
deinit {
for (_, context) in self.contexts {
context.disposable.dispose()
}
}
private func updateValues() {
self.uploadingPeerMediaValue = self.contexts.mapValues { context in
return context.value
}
}
func add(peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) {
if let context = self.contexts[peerId] {
self.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
}
let postbox = self.postbox
let network = self.network
let stateManager = self.stateManager
let accountPeerId = self.accountPeerId
let queue = self.queue
let context = PendingPeerMediaUploadContext(value: PeerMediaUploadingItem(content: content, messageId: nil, progress: 0.0))
self.contexts[peerId] = context
context.disposable.set(
(self.postbox.transaction({ transaction -> EngineMessage.Id? in
let storeMessage = generatePeerMediaMessage(network: network, accountPeerId: accountPeerId, transaction: transaction, peerId: peerId, content: content)
let globallyUniqueIdToMessageId = transaction.addMessages([storeMessage], location: .Random)
guard let globallyUniqueId = storeMessage.globallyUniqueId, let messageId = globallyUniqueIdToMessageId[globallyUniqueId] else {
return nil
}
return messageId
})
|> deliverOn(queue)).start(next: { [weak self, weak context] messageId in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
guard let messageId = messageId else {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
return
}
context.value = context.value.withMessageId(messageId)
strongSelf.updateValues()
strongSelf.sentMessageEventPipe.putNext(peerId)
context.disposable.set((uploadPeerMedia(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, content: content)
|> deliverOn(queue)).start(next: { [weak self, weak context] value in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
switch value {
case let .done(result):
context.disposable.set(
(postbox.transaction({ transaction -> Message? in
return transaction.getMessage(messageId)
})
|> deliverOn(queue)
).start(next: { [weak self, weak context] message in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
guard let message = message else {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
return
}
context.disposable.set(
(applyUpdateMessage(
postbox: postbox,
stateManager: stateManager,
message: message,
cacheReferenceKey: nil,
result: result,
accountPeerId: accountPeerId,
pendingMessageEvent: { _ in }
)
|> deliverOn(queue)).start(completed: { [weak self, weak context] in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
strongSelf.contexts.removeValue(forKey: peerId)
context.disposable.dispose()
strongSelf.updateValues()
}
})
)
}
})
)
strongSelf.updateValues()
case let .progress(progress):
context.value = context.value.withProgress(progress)
strongSelf.updateValues()
}
}
}, error: { [weak self, weak context] error in
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[peerId], context === initialContext {
strongSelf.contexts.removeValue(forKey: peerId)
if let messageId = context.value.messageId {
context.disposable.set(strongSelf.postbox.transaction({ transaction in
transaction.deleteMessages([messageId], forEachMedia: nil)
}).start())
} else {
context.disposable.dispose()
}
strongSelf.updateValues()
}
}))
}
})
)
}
func cancel(peerId: EnginePeer.Id) {
if let context = self.contexts[peerId] {
self.contexts.removeValue(forKey: peerId)
if let messageId = context.value.messageId {
context.disposable.set(self.postbox.transaction({ transaction in
transaction.deleteMessages([messageId], forEachMedia: nil)
}).start())
} else {
context.disposable.dispose()
}
self.updateValues()
}
}
func uploadProgress(messageId: EngineMessage.Id) -> Signal<Float?, NoError> {
return self.uploadingPeerMedia
|> map { uploadingPeerMedia in
if let item = uploadingPeerMedia[messageId.peerId], item.messageId == messageId {
return item.progress
} else {
return nil
}
}
|> distinctUntilChanged
}
public func sentMessageEvents(peerId: EnginePeer.Id) -> Signal<Void, NoError> {
return self.sentMessageEventPipe.signal()
|> mapToSignal { eventPeerId -> Signal<Void, NoError> in
if eventPeerId == peerId {
return .single(Void())
} else {
return .complete()
}
}
}
}
public final class PendingPeerMediaUploadManager {
private let queue = Queue()
private let impl: QueueLocalObject<PendingPeerMediaUploadManagerImpl>
public var uploadingPeerMedia: Signal<[EnginePeer.Id: PeerMediaUploadingItem], NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.uploadingPeerMedia.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: EnginePeer.Id) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PendingPeerMediaUploadManagerImpl(queue: queue, postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId)
})
}
public func add(peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) {
self.impl.with { impl in
impl.add(peerId: peerId, content: content)
}
}
public func cancel(peerId: EnginePeer.Id) {
self.impl.with { impl in
impl.cancel(peerId: peerId)
}
}
public func uploadProgress(messageId: EngineMessage.Id) -> Signal<Float?, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.uploadProgress(messageId: messageId).start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public func sentMessageEvents(peerId: EnginePeer.Id) -> Signal<Void, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.sentMessageEvents(peerId: peerId).start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
}
@@ -0,0 +1,177 @@
import Foundation
import SwiftSignalKit
import Postbox
private final class PendingUpdateMessageContext {
var value: ChatUpdatingMessageMedia
let disposable: Disposable
init(value: ChatUpdatingMessageMedia, disposable: Disposable) {
self.value = value
self.disposable = disposable
}
}
private final class PendingUpdateMessageManagerImpl {
let queue: Queue
let postbox: Postbox
let network: Network
let stateManager: AccountStateManager
let messageMediaPreuploadManager: MessageMediaPreuploadManager
let mediaReferenceRevalidationContext: MediaReferenceRevalidationContext
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
private var updatingMessageMediaValue: [MessageId: ChatUpdatingMessageMedia] = [:] {
didSet {
if self.updatingMessageMediaValue != oldValue {
self.updatingMessageMediaPromise.set(.single(self.updatingMessageMediaValue))
}
}
}
private let updatingMessageMediaPromise = Promise<[MessageId: ChatUpdatingMessageMedia]>()
var updatingMessageMedia: Signal<[MessageId: ChatUpdatingMessageMedia], NoError> {
return self.updatingMessageMediaPromise.get()
}
private var contexts: [MessageId: PendingUpdateMessageContext] = [:]
private let errorsPipe = ValuePipe<(MessageId, RequestEditMessageError)>()
var errors: Signal<(MessageId, RequestEditMessageError), NoError> {
return self.errorsPipe.signal()
}
init(queue: Queue, postbox: Postbox, network: Network, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext) {
self.queue = queue
self.postbox = postbox
self.network = network
self.stateManager = stateManager
self.messageMediaPreuploadManager = messageMediaPreuploadManager
self.mediaReferenceRevalidationContext = mediaReferenceRevalidationContext
self.updatingMessageMediaPromise.set(.single(self.updatingMessageMediaValue))
}
deinit {
for (_, context) in self.contexts {
context.disposable.dispose()
}
}
private func updateValues() {
self.updatingMessageMediaValue = self.contexts.mapValues { context in
return context.value
}
}
func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool) {
if let context = self.contexts[messageId] {
self.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
}
let disposable = MetaDisposable()
let context = PendingUpdateMessageContext(value: ChatUpdatingMessageMedia(text: text, entities: entities, disableUrlPreview: disableUrlPreview, media: media, invertMediaAttribute: invertMediaAttribute, progress: 0.0), disposable: disposable)
self.contexts[messageId] = context
let queue = self.queue
disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: nil, invertMediaAttribute: invertMediaAttribute)
|> deliverOn(self.queue)).start(next: { [weak self, weak context] value in
queue.async {
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[messageId], context === initialContext {
switch value {
case .done:
strongSelf.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
strongSelf.updateValues()
case let .progress(progress):
context.value = context.value.withProgress(progress)
strongSelf.updateValues()
}
}
}
}, error: { [weak self, weak context] error in
queue.async {
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[messageId], context === initialContext {
strongSelf.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
strongSelf.updateValues()
}
strongSelf.errorsPipe.putNext((messageId, error))
}
}))
}
func cancel(messageId: MessageId) {
if let context = self.contexts[messageId] {
self.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
self.updateValues()
}
}
}
public final class PendingUpdateMessageManager {
private let queue = Queue()
private let impl: QueueLocalObject<PendingUpdateMessageManagerImpl>
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? {
didSet {
let transformOutgoingMessageMedia = self.transformOutgoingMessageMedia
self.impl.with { impl in
impl.transformOutgoingMessageMedia = transformOutgoingMessageMedia
}
}
}
public var updatingMessageMedia: Signal<[MessageId: ChatUpdatingMessageMedia], NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.updatingMessageMedia.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public var errors: Signal<(MessageId, RequestEditMessageError), NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.errors.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PendingUpdateMessageManagerImpl(queue: queue, postbox: postbox, network: network, stateManager: stateManager, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext)
})
}
public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false) {
self.impl.with { impl in
impl.add(messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview)
}
}
public func cancel(messageId: MessageId) {
self.impl.with { impl in
impl.cancel(messageId: messageId)
}
}
}
@@ -0,0 +1,375 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum RequestEditMessageMedia : Equatable {
case keep
case update(AnyMediaReference)
}
public enum RequestEditMessageResult {
case progress(Float)
case done(Bool)
}
private enum RequestEditMessageInternalError {
case error(RequestEditMessageError)
case invalidReference
}
public enum RequestEditMessageError {
case generic
case restricted
case textTooLong
case invalidGrouping
}
func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute)
}
func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: false)
|> `catch` { error -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
if case .invalidReference = error {
return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: true)
} else {
return .fail(error)
}
}
|> mapError { error -> RequestEditMessageError in
switch error {
case let .error(error):
return error
default:
return .generic
}
}
}
private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, forceReupload: Bool) -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> {
let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError>
switch media {
case .keep:
uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0)))
|> then(.single(nil))
case let .update(media):
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
let augmentedMedia = augmentMediaWithReference(media)
var attributes: [MessageAttribute] = []
if let webpagePreviewAttribute = webpagePreviewAttribute {
attributes.append(webpagePreviewAttribute)
}
if let invertMediaAttribute {
attributes.append(invertMediaAttribute)
}
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes, mediaReference: nil, explicitPartialReference: nil)
}
if let todo = media.media as? TelegramMediaTodo {
var flags: Int32 = 0
if todo.flags.contains(.othersCanAppend) {
flags |= 1 << 0
}
if todo.flags.contains(.othersCanComplete) {
flags |= 1 << 1
}
let inputTodo = Api.InputMedia.inputMediaTodo(todo: .todoList(flags: flags, title: .textWithEntities(text: todo.text, entities: apiEntitiesFromMessageTextEntities(todo.textEntities, associatedPeers: SimpleDictionary())), list: todo.items.map { $0.apiItem }))
uploadedMedia = .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputTodo, text), reuploadInfo: nil, cacheReferenceKey: nil)))
}
else if let uploadSignal = generateUploadSignal(forceReupload) {
uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.027)))
|> then(uploadSignal)
|> map { result -> PendingMessageUploadedContentResult? in
switch result {
case let .progress(value):
return .progress(PendingMessageUploadedContentProgress(progress: max(value.progress, 0.027)))
case let .content(content):
return .content(content)
}
}
|> `catch` { _ -> Signal<PendingMessageUploadedContentResult?, NoError> in
return .single(nil)
}
} else {
uploadedMedia = .single(nil)
}
}
return uploadedMedia
|> mapError { _ -> RequestEditMessageInternalError in }
|> mapToSignal { uploadedMediaResult -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
var pendingMediaContent: PendingMessageUploadedContent?
if let uploadedMediaResult = uploadedMediaResult {
switch uploadedMediaResult {
case let .progress(value):
return .single(.progress(value.progress))
case let .content(content):
pendingMediaContent = content.content
}
}
return postbox.transaction { transaction -> (Peer?, Message?, SimpleDictionary<PeerId, Peer>) in
guard let message = transaction.getMessage(messageId) else {
return (nil, nil, SimpleDictionary())
}
for (_, file) in inlineStickers {
transaction.storeMediaIfNotPresent(media: file)
}
if text.isEmpty {
for media in message.media {
switch media {
case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaTodo:
break
default:
if let _ = scheduleInfoAttribute {
break
} else {
return (nil, nil, SimpleDictionary())
}
}
}
}
var peers = SimpleDictionary<PeerId, Peer>()
if let entities = entities {
for peerId in entities.associatedPeerIds {
if let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
}
}
}
return (transaction.getPeer(messageId.peerId), message, peers)
}
|> mapError { _ -> RequestEditMessageInternalError in }
|> mapToSignal { peer, message, associatedPeers -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
if let peer = peer, let message = message, let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 1 << 11
var apiEntities: [Api.MessageEntity]?
if let entities = entities {
apiEntities = apiTextAttributeEntities(entities, associatedPeers: associatedPeers)
flags |= Int32(1 << 3)
}
if disableUrlPreview {
flags |= Int32(1 << 1)
}
var inputMedia: Api.InputMedia? = nil
if let pendingMediaContent = pendingMediaContent {
switch pendingMediaContent {
case let .media(media, _):
inputMedia = media
default:
break
}
}
if let _ = inputMedia {
flags |= Int32(1 << 14)
}
var effectiveScheduleTime: Int32?
var effectiveScheduleRepeatPeriod: Int32?
if messageId.namespace == Namespaces.Message.ScheduledCloud {
if let scheduleTime = scheduleInfoAttribute?.scheduleTime {
effectiveScheduleTime = scheduleTime
} else {
effectiveScheduleTime = message.timestamp
}
flags |= Int32(1 << 15)
if let scheduleInfoAttribute {
effectiveScheduleRepeatPeriod = scheduleInfoAttribute.repeatPeriod ?? 0
flags |= Int32(1 << 18)
}
}
if let _ = invertMediaAttribute {
flags |= Int32(1 << 16)
}
var quickReplyShortcutId: Int32?
if messageId.namespace == Namespaces.Message.QuickReplyCloud {
quickReplyShortcutId = Int32(clamping: message.threadId ?? 0)
flags |= Int32(1 << 17)
}
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, scheduleRepeatPeriod: effectiveScheduleRepeatPeriod, quickReplyShortcutId: quickReplyShortcutId))
|> map { result -> Api.Updates? in
return result
}
|> `catch` { error -> Signal<Api.Updates?, MTRpcError> in
if error.errorDescription == "MESSAGE_NOT_MODIFIED" {
return .single(nil)
} else {
return .fail(error)
}
}
|> mapError { error -> RequestEditMessageInternalError in
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
return .invalidReference
} else if error.errorDescription.hasSuffix("_TOO_LONG") {
return .error(.textTooLong)
} else if error.errorDescription.hasPrefix("MEDIA_GROUPED_INVALID") {
return .error(.invalidGrouping)
} else if error.errorDescription.hasPrefix("CHAT_SEND_") && error.errorDescription.hasSuffix("_FORBIDDEN") {
return .error(.restricted)
}
return .error(.generic)
}
|> mapToSignal { result -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
if let result = result {
return postbox.transaction { transaction -> RequestEditMessageResult in
var toMedia: Media?
if let message = result.messages.first.flatMap({ StoreMessage(apiMessage: $0, accountPeerId: accountPeerId, peerIsForum: peer.isForumOrMonoForum) }) {
toMedia = message.media.first
}
if case let .update(fromMedia) = media, let toMedia = toMedia {
applyMediaResourceChanges(from: fromMedia.media, to: toMedia, postbox: postbox, force: true)
}
switch result {
case let .updates(updates, users, chats, _, _):
for update in updates {
switch update {
case .updateEditMessage(let message, _, _), .updateNewMessage(let message, _, _), .updateEditChannelMessage(let message, _, _), .updateNewChannelMessage(let message, _, _):
let peers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: peers)
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForumOrMonoForum), case let .Id(id) = message.id {
transaction.updateMessage(id, update: { previousMessage in
var updatedFlags = message.flags
var updatedLocalTags = message.localTags
if previousMessage.localTags.contains(.OutgoingLiveLocation) {
updatedLocalTags.insert(.OutgoingLiveLocation)
}
if previousMessage.flags.contains(.Incoming) {
updatedFlags.insert(.Incoming)
} else {
updatedFlags.remove(.Incoming)
}
var updatedMedia = message.media
if let previousPaidContent = previousMessage.media.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent, case .full = previousPaidContent.extendedMedia.first {
updatedMedia = previousMessage.media
}
return .update(message.withUpdatedLocalTags(updatedLocalTags).withUpdatedFlags(updatedFlags).withUpdatedMedia(updatedMedia))
})
}
default:
break
}
}
default:
break
}
stateManager.addUpdates(result)
return .done(true)
}
|> mapError { _ -> RequestEditMessageInternalError in
}
} else {
return .single(.done(false))
}
}
} else {
return .single(.done(false))
}
}
}
}
func _internal_requestEditLiveLocation(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId, stop: Bool, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?, proximityNotificationRadius: Int32?, extendPeriod: Int32?) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> (Api.InputPeer, TelegramMediaMap)? in
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
return nil
}
guard let message = transaction.getMessage(messageId) else {
return nil
}
for media in message.media {
if let media = media as? TelegramMediaMap {
return (inputPeer, media)
}
}
return nil
}
|> mapToSignal { inputPeerAndMedia -> Signal<Void, NoError> in
guard let (inputPeer, media) = inputPeerAndMedia else {
return .complete()
}
let inputMedia: Api.InputMedia
if let liveBroadcastingTimeout = media.liveBroadcastingTimeout, !stop {
var flags: Int32 = 1 << 1
let inputGeoPoint: Api.InputGeoPoint
if let coordinate = coordinate {
var geoFlags: Int32 = 0
if let _ = coordinate.accuracyRadius {
geoFlags |= 1 << 0
}
inputGeoPoint = .inputGeoPoint(flags: geoFlags, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: coordinate.accuracyRadius.flatMap({ Int32($0) }))
} else {
var geoFlags: Int32 = 0
if let _ = media.accuracyRadius {
geoFlags |= 1 << 0
}
inputGeoPoint = .inputGeoPoint(flags: geoFlags, lat: media.latitude, long: media.longitude, accuracyRadius: media.accuracyRadius.flatMap({ Int32($0) }))
}
if let _ = heading {
flags |= 1 << 2
}
if let _ = proximityNotificationRadius {
flags |= 1 << 3
}
let period: Int32
if let extendPeriod {
if extendPeriod == liveLocationIndefinitePeriod {
period = extendPeriod
} else {
period = liveBroadcastingTimeout + extendPeriod
}
} else {
period = liveBroadcastingTimeout
}
inputMedia = .inputMediaGeoLive(flags: flags, geoPoint: inputGeoPoint, heading: heading, period: period, proximityNotificationRadius: proximityNotificationRadius)
} else {
inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil), heading: nil, period: nil, proximityNotificationRadius: nil)
}
return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Void, NoError> in
if let updates = updates {
stateManager.addUpdates(updates)
}
if coordinate == nil && proximityNotificationRadius == nil && extendPeriod == nil {
return postbox.transaction { transaction -> Void in
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var updatedLocalTags = currentMessage.localTags
updatedLocalTags.remove(.OutgoingLiveLocation)
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
})
}
} else {
return .complete()
}
}
}
}
@@ -0,0 +1,791 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum StandaloneMedia {
case image(Data)
case file(data: Data, mimeType: String, attributes: [TelegramMediaFileAttribute])
}
private enum StandaloneMessageContent {
case text(String)
case media(Api.InputMedia, String)
}
private enum StandaloneSendMessageEvent {
case result(StandaloneMessageContent)
case progress(Float)
}
public enum StandaloneSendMessageError {
case generic
}
public enum StandaloneSendMessageStatus {
case progress(Float)
case done
}
public struct StandaloneSendMessagesError {
public var peerId: PeerId
public var reason: PendingMessageFailureReason?
init(
peerId: PeerId,
reason: PendingMessageFailureReason?
) {
self.peerId = peerId
self.reason = reason
}
}
public struct StandaloneSendEnqueueMessage {
public struct Text {
public var string: String
public var entities: [MessageTextEntity]
public init(
string: String,
entities: [MessageTextEntity]
) {
self.string = string
self.entities = entities
}
}
public struct Image {
public var representation: TelegramMediaImageRepresentation
public init(
representation: TelegramMediaImageRepresentation
) {
self.representation = representation
}
}
public struct Forward {
public var sourceId: MessageId
public var threadId: Int64?
public init(
sourceId: MessageId,
threadId: Int64?
) {
self.sourceId = sourceId
self.threadId = threadId
}
}
public struct ForwardOptions {
public var hideNames: Bool
public var hideCaptions: Bool
public init(
hideNames: Bool,
hideCaptions: Bool
) {
self.hideNames = hideNames
self.hideCaptions = hideCaptions
}
}
public enum Content {
case text(text: Text)
case image(image: Image, text: Text)
case map(map: TelegramMediaMap)
case arbitraryMedia(media: AnyMediaReference, text: Text)
case forward(forward: Forward)
}
public var content: Content
public var replyToMessageId: MessageId?
public var forwardOptions: ForwardOptions?
public var isSilent: Bool = false
public var groupingKey: Int64? = nil
public var sendPaidMessageStars: StarsAmount? = nil
public init(
content: Content,
replyToMessageId: MessageId?
) {
self.content = content
self.replyToMessageId = replyToMessageId
}
}
public func standaloneSendEnqueueMessages(
accountPeerId: PeerId,
postbox: Postbox,
network: Network,
stateManager: AccountStateManager,
auxiliaryMethods: AccountAuxiliaryMethods,
peerId: PeerId,
threadId: Int64?,
messages: [StandaloneSendEnqueueMessage]
) -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> {
struct MessageResult {
var result: PendingMessageUploadedContentResult
var media: [Media]
var attributes: [MessageAttribute]
}
let signals: [Signal<MessageResult, PendingMessageUploadError>] = messages.map { message in
var attributes: [MessageAttribute] = []
var text: String = ""
var media: [Media] = []
switch message.content {
case let .text(textValue):
text = textValue.string
if !textValue.entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: textValue.entities))
}
case let .image(image, textValue):
media.append(TelegramMediaImage(
imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: MediaId.Id.random(in: Int64.min ... Int64.max)),
representations: [image.representation],
immediateThumbnailData: nil,
reference: nil,
partialReference: nil,
flags: []
))
text = textValue.string
if !textValue.entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: textValue.entities))
}
case let .map(mapValue):
media.append(mapValue)
case let .arbitraryMedia(mediaValue, textValue):
media.append(mediaValue.media)
text = textValue.string
if !textValue.entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: textValue.entities))
}
case let .forward(forwardValue):
attributes.append(ForwardSourceInfoAttribute(messageId: forwardValue.sourceId))
}
if let replyToMessageId = message.replyToMessageId {
attributes.append(ReplyMessageAttribute(messageId: replyToMessageId, threadMessageId: nil, quote: nil, isQuote: false, todoItemId: nil))
}
if let forwardOptions = message.forwardOptions {
attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions.hideNames, hideCaptions: forwardOptions.hideCaptions))
}
if message.isSilent {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
if let sendPaidMessageStars = message.sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let content = messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: { _, _, _, _ in
return .single(nil)
}, messageMediaPreuploadManager: MessageMediaPreuploadManager(), revalidationContext: MediaReferenceRevalidationContext(), forceReupload: false, isGrouped: false, passFetchProgress: true, forceNoBigParts: false, peerId: peerId, messageId: nil, attributes: attributes, text: text, media: media)
let contentResult: Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>
switch content {
case let .signal(value, _):
contentResult = value
case let .immediate(value, _):
contentResult = .single(value)
}
return contentResult
|> map { contentResult in
return MessageResult(result: contentResult, media: media, attributes: attributes)
}
}
return combineLatest(signals)
|> mapError { _ -> StandaloneSendMessagesError in
return StandaloneSendMessagesError(peerId: peerId, reason: nil)
}
|> mapToSignal { contentResults -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> in
var progressSum: Float = 0.0
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media], attributes: [MessageAttribute])] = []
var allDone = true
for result in contentResults {
switch result.result {
case let .progress(value):
allDone = false
progressSum += value.progress
case let .content(content):
allResults.append((content, result.media, result.attributes))
}
}
if allDone {
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
for (content, media, attributes) in allResults {
var text: String = ""
switch content.content {
case let .text(textValue):
text = textValue
case let .media(_, textValue):
text = textValue
default:
break
}
sendSignals.append(sendUploadedMessageContent(
auxiliaryMethods: auxiliaryMethods,
postbox: postbox,
network: network,
stateManager: stateManager,
accountPeerId: stateManager.accountPeerId,
peerId: peerId,
content: content,
text: text,
attributes: attributes,
media: media,
threadId: threadId
))
}
return combineLatest(sendSignals)
|> ignoreValues
|> map { _ -> StandaloneSendMessageStatus in
}
|> then(.single(.done))
} else {
return .single(.progress(progressSum / max(1.0, Float(contentResults.count))))
}
}
}
private func sendUploadedMessageContent(
auxiliaryMethods: AccountAuxiliaryMethods,
postbox: Postbox,
network: Network,
stateManager: AccountStateManager,
accountPeerId: PeerId,
peerId: PeerId,
content: PendingMessageUploadedContentAndReuploadInfo,
text: String,
attributes: [MessageAttribute],
media: [Media],
threadId: Int64?
) -> Signal<Never, StandaloneSendMessagesError> {
return postbox.transaction { transaction -> Signal<Never, StandaloneSendMessagesError> in
if peerId.namespace == Namespaces.Peer.SecretChat {
var secretFile: SecretChatOutgoingFile?
switch content.content {
case let .secretMedia(file, size, key):
if let fileReference = SecretChatOutgoingFileReference(file) {
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
}
default:
break
}
var layer: SecretChatLayer?
let state = transaction.getPeerChatState(peerId) as? SecretChatState
if let state = state {
switch state.embeddedState {
case .terminated, .handshake:
break
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
}
if let state = state, let layer = layer {
let messageContents = StandaloneSecretMessageContents(
id: Int64.random(in: Int64.min ... Int64.max),
text: text,
attributes: attributes,
media: media.first,
file: secretFile
)
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state)
if updatedState != state {
transaction.setPeerChatState(peerId, state: updatedState)
}
return managedSecretChatOutgoingOperations(
auxiliaryMethods: auxiliaryMethods,
postbox: postbox,
network: network,
accountPeerId: accountPeerId,
mode: .standaloneComplete(peerId: peerId)
)
|> castError(StandaloneSendMessagesError.self)
|> ignoreValues
} else {
return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none))
}
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var uniqueId: Int64 = 0
var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?
var messageEntities: [Api.MessageEntity]?
var replyMessageId: Int32?
var topMsgId: Int32?
var monoforumPeerId: Api.InputPeer?
if let threadId {
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
if let monoforumTargetPeer = transaction.getPeer(PeerId(threadId)) {
monoforumPeerId = apiInputPeer(monoforumTargetPeer)
}
} else {
replyMessageId = Int32(clamping: threadId)
topMsgId = Int32(clamping: threadId)
}
}
var replyToStoryId: StoryId?
var replyTodoItemId: Int32?
var scheduleTime: Int32?
var scheduleRepeatPeriod: Int32?
var videoTimestamp: Int32?
var sendAsPeerId: PeerId?
var bubbleUpEmojiOrStickersets = false
var allowPaidStars: Int64?
var suggestedPost: Api.SuggestedPost?
var flags: Int32 = 0
for attribute in attributes {
if let replyAttribute = attribute as? ReplyMessageAttribute {
replyMessageId = replyAttribute.messageId.id
replyTodoItemId = replyAttribute.todoItemId
} else if let attribute = attribute as? ReplyStoryAttribute {
replyToStoryId = attribute.storyId
} else if let outgoingInfo = attribute as? OutgoingMessageInfoAttribute {
uniqueId = outgoingInfo.uniqueId
bubbleUpEmojiOrStickersets = !outgoingInfo.bubbleUpEmojiOrStickersets.isEmpty
} else if let attribute = attribute as? ForwardSourceInfoAttribute {
forwardSourceInfoAttribute = attribute
} else if let attribute = attribute as? TextEntitiesMessageAttribute {
var associatedPeers = SimpleDictionary<PeerId, Peer>()
for attributePeerId in attribute.associatedPeerIds {
if let peer = transaction.getPeer(attributePeerId) {
associatedPeers[peer.id] = peer
}
}
messageEntities = apiTextAttributeEntities(attribute, associatedPeers: associatedPeers)
} else if let attribute = attribute as? OutgoingContentInfoMessageAttribute {
if attribute.flags.contains(.disableLinkPreviews) {
flags |= Int32(1 << 1)
}
} else if let attribute = attribute as? NotificationInfoMessageAttribute {
if attribute.flags.contains(.muted) {
flags |= Int32(1 << 5)
}
} else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
flags |= Int32(1 << 10)
scheduleTime = attribute.scheduleTime
if let repeatPeriod = attribute.repeatPeriod {
flags |= Int32(1 << 24)
scheduleRepeatPeriod = repeatPeriod
}
} else if let attribute = attribute as? SendAsMessageAttribute {
sendAsPeerId = attribute.peerId
} else if let attribute = attribute as? ForwardVideoTimestampAttribute {
flags |= Int32(1 << 20)
videoTimestamp = attribute.timestamp
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
}
}
if uniqueId == 0 {
uniqueId = Int64.random(in: Int64.min ... Int64.max)
}
if case .forward = content.content {
} else {
flags |= (1 << 7)
if let _ = replyMessageId {
flags |= Int32(1 << 0)
}
if let _ = messageEntities {
flags |= Int32(1 << 3)
}
}
var sendAsInputPeer: Api.InputPeer?
if let sendAsPeerId = sendAsPeerId, let sendAsPeer = transaction.getPeer(sendAsPeerId), let inputPeer = apiInputPeerOrSelf(sendAsPeer, accountPeerId: accountPeerId) {
sendAsInputPeer = inputPeer
flags |= (1 << 13)
}
if let _ = allowPaidStars {
flags |= 1 << 21
}
let dependencyTag: PendingMessageRequestDependencyTag? = nil//(messageId: messageId)
let sendMessageRequest: Signal<NetworkRequestResult<Api.Updates>, MTRpcError>
switch content.content {
case .text:
if bubbleUpEmojiOrStickersets {
flags |= Int32(1 << 15)
}
var replyTo: Api.InputReplyTo?
if let replyMessageId {
flags |= 1 << 0
var replyFlags: Int32 = 0
if topMsgId != nil {
replyFlags |= 1 << 0
}
if monoforumPeerId != nil {
replyFlags |= 1 << 5
}
if let _ = replyTodoItemId {
replyFlags |= 1 << 6
}
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: topMsgId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: monoforumPeerId, todoItemId: replyTodoItemId)
} else if let replyToStoryId {
if let inputPeer = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputPeer) {
flags |= 1 << 0
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
}
} else if let monoforumPeerId {
replyTo = .inputReplyToMonoForum(monoforumPeerId: monoforumPeerId)
}
if suggestedPost != nil {
flags |= 1 << 22
}
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag)
case let .media(inputMedia, text):
if bubbleUpEmojiOrStickersets {
flags |= Int32(1 << 15)
}
var replyTo: Api.InputReplyTo?
if let replyMessageId = replyMessageId {
flags |= 1 << 0
var replyFlags: Int32 = 0
if topMsgId != nil {
replyFlags |= 1 << 0
}
if monoforumPeerId != nil {
replyFlags |= 1 << 5
}
if let _ = replyTodoItemId {
replyFlags |= 1 << 6
}
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: topMsgId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: monoforumPeerId, todoItemId: replyTodoItemId)
} else if let replyToStoryId = replyToStoryId {
if let inputPeer = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputPeer) {
flags |= 1 << 0
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
}
}
if suggestedPost != nil {
flags |= 1 << 22
}
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag)
|> map(NetworkRequestResult.result)
case let .forward(sourceInfo):
if topMsgId != nil {
flags |= Int32(1 << 9)
}
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag)
|> map(NetworkRequestResult.result)
} else {
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
}
case let .chatContextResult(chatContextResult):
if chatContextResult.hideVia {
flags |= Int32(1 << 11)
}
var replyTo: Api.InputReplyTo?
if let replyMessageId = replyMessageId {
flags |= 1 << 0
var replyFlags: Int32 = 0
if topMsgId != nil {
replyFlags |= 1 << 0
}
if monoforumPeerId != nil {
replyFlags |= 1 << 5
}
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: topMsgId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: monoforumPeerId, todoItemId: nil)
} else if let replyToStoryId = replyToStoryId {
if let inputPeer = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputPeer) {
flags |= 1 << 0
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
}
}
sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: allowPaidStars))
|> map(NetworkRequestResult.result)
case .messageScreenshot:
let replyTo: Api.InputReplyTo
if let replyMessageId = replyMessageId {
let replyFlags: Int32 = 0
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: nil, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
} else if let replyToStoryId = replyToStoryId {
if let inputPeer = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputPeer) {
flags |= 1 << 0
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
} else {
let replyFlags: Int32 = 0
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: 0, topMsgId: nil, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
}
} else {
let replyFlags: Int32 = 0
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: 0, topMsgId: nil, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
}
sendMessageRequest = network.request(Api.functions.messages.sendScreenshotNotification(peer: inputPeer, replyTo: replyTo, randomId: uniqueId))
|> map(NetworkRequestResult.result)
case .secretMedia:
assertionFailure()
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
}
return sendMessageRequest
|> mapToSignal { result -> Signal<Never, MTRpcError> in
switch result {
case .progress:
return .complete()
case .acknowledged:
return .complete()
case let .result(result):
stateManager.addUpdates(result)
return .complete()
}
}
|> mapError { error -> StandaloneSendMessagesError in
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
return StandaloneSendMessagesError(peerId: peerId, reason: nil)
} else if let failureReason = sendMessageReasonForError(error.errorDescription) {
return StandaloneSendMessagesError(peerId: peerId, reason: failureReason)
}
return StandaloneSendMessagesError(peerId: peerId, reason: nil)
}
} else {
return .complete()
}
}
|> castError(StandaloneSendMessagesError.self)
|> switchToLatest
}
public func standaloneSendMessage(account: Account, peerId: PeerId, text: String, attributes: [MessageAttribute], media: StandaloneMedia?, replyToMessageId: MessageId?, threadId: Int32? = nil) -> Signal<Float, StandaloneSendMessageError> {
let content: Signal<StandaloneSendMessageEvent, StandaloneSendMessageError>
if let media = media {
switch media {
case let .image(data):
content = uploadedImage(account: account, data: data)
|> mapError { _ -> StandaloneSendMessageError in return .generic }
|> map { next -> StandaloneSendMessageEvent in
switch next {
case let .progress(progress):
return .progress(progress)
case let .result(media):
return .result(.media(media, text))
}
}
case let .file(data, mimeType, attributes):
content = uploadedFile(account: account, data: data, mimeType: mimeType, attributes: attributes)
|> mapError { _ -> StandaloneSendMessageError in return .generic }
|> map { next -> StandaloneSendMessageEvent in
switch next {
case let .progress(progress):
return .progress(progress)
case let .result(media):
return .result(.media(media, text))
}
}
}
} else {
content = .single(.result(.text(text)))
}
return content
|> mapToSignal { event -> Signal<Float, StandaloneSendMessageError> in
switch event {
case let .progress(progress):
return .single(progress)
case let .result(result):
let sendContent = sendMessageContent(account: account, peerId: peerId, attributes: attributes, content: result, threadId: threadId) |> map({ _ -> Float in return 1.0 })
return .single(1.0) |> then(sendContent |> mapError { _ -> StandaloneSendMessageError in })
}
}
}
private func sendMessageContent(account: Account, peerId: PeerId, attributes: [MessageAttribute], content: StandaloneMessageContent, threadId: Int32?) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if peerId.namespace == Namespaces.Peer.SecretChat {
return .complete()
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var uniqueId: Int64 = Int64.random(in: Int64.min ... Int64.max)
//var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?
var messageEntities: [Api.MessageEntity]?
var replyMessageId: Int32?
var replyToStoryId: StoryId?
var scheduleTime: Int32?
var scheduleRepeatPeriod: Int32?
var sendAsPeerId: PeerId?
var allowPaidStars: Int64?
var suggestedPost: Api.SuggestedPost?
var flags: Int32 = 0
flags |= (1 << 7)
for attribute in attributes {
if let replyAttribute = attribute as? ReplyMessageAttribute {
replyMessageId = replyAttribute.messageId.id
} else if let attribute = attribute as? ReplyStoryAttribute {
replyToStoryId = attribute.storyId
} else if let outgoingInfo = attribute as? OutgoingMessageInfoAttribute {
uniqueId = outgoingInfo.uniqueId
} else if let _ = attribute as? ForwardSourceInfoAttribute {
//forwardSourceInfoAttribute = attribute
} else if let attribute = attribute as? TextEntitiesMessageAttribute {
messageEntities = apiTextAttributeEntities(attribute, associatedPeers: SimpleDictionary())
} else if let attribute = attribute as? OutgoingContentInfoMessageAttribute {
if attribute.flags.contains(.disableLinkPreviews) {
flags |= Int32(1 << 1)
}
} else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
flags |= Int32(1 << 10)
scheduleTime = attribute.scheduleTime
if let repeatPeriod = attribute.repeatPeriod {
flags |= Int32(1 << 24)
scheduleRepeatPeriod = repeatPeriod
}
} else if let attribute = attribute as? SendAsMessageAttribute {
sendAsPeerId = attribute.peerId
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
} else if let attribute = attribute as? SuggestedPostMessageAttribute {
suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10))
}
}
if let _ = messageEntities {
flags |= Int32(1 << 3)
}
var sendAsInputPeer: Api.InputPeer?
if let sendAsPeerId = sendAsPeerId, let sendAsPeer = transaction.getPeer(sendAsPeerId), let inputPeer = apiInputPeerOrSelf(sendAsPeer, accountPeerId: account.peerId) {
sendAsInputPeer = inputPeer
flags |= (1 << 13)
}
if let _ = allowPaidStars {
flags |= 1 << 21
}
let sendMessageRequest: Signal<Api.Updates, NoError>
switch content {
case let .text(text):
var replyTo: Api.InputReplyTo?
if let replyMessageId = replyMessageId {
flags |= 1 << 0
let replyFlags: Int32 = 0
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: nil, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
} else if let replyToStoryId = replyToStoryId {
if let inputPeer = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputPeer) {
flags |= 1 << 0
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
}
} else if let threadId {
flags |= 1 << 0
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
}
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil))
|> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete()
}
case let .media(inputMedia, text):
var replyTo: Api.InputReplyTo?
if let replyMessageId = replyMessageId {
flags |= 1 << 0
let replyFlags: Int32 = 0
replyTo = .inputReplyToMessage(flags: replyFlags, replyToMsgId: replyMessageId, topMsgId: nil, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
} else if let replyToStoryId = replyToStoryId {
if let inputPeer = transaction.getPeer(replyToStoryId.peerId).flatMap(apiInputPeer) {
flags |= 1 << 0
replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id)
}
} else if let threadId {
flags |= 1 << 0
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil)
}
if suggestedPost != nil {
flags |= 1 << 22
}
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost))
|> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete()
}
}
return sendMessageRequest
|> mapToSignal { result -> Signal<Void, NoError> in
return .complete()
}
|> `catch` { _ -> Signal<Void, NoError> in
}
} else {
return .complete()
}
}
|> switchToLatest
}
private enum UploadMediaEvent {
case progress(Float)
case result(Api.InputMedia)
}
private func uploadedImage(account: Account, data: Data) -> Signal<UploadMediaEvent, StandaloneSendMessageError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> mapError { _ -> StandaloneSendMessageError in return .generic }
|> map { next -> UploadMediaEvent in
switch next {
case let .inputFile(inputFile):
return .result(Api.InputMedia.inputMediaUploadedPhoto(flags: 0, file: inputFile, stickers: nil, ttlSeconds: nil))
case .inputSecretFile:
preconditionFailure()
case let .progress(progress):
return .progress(progress)
}
}
}
private func uploadedFile(account: Account, data: Data, mimeType: String, attributes: [TelegramMediaFileAttribute]) -> Signal<UploadMediaEvent, PendingMessageUploadError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: statsCategoryForFileWithAttributes(attributes), userContentType: nil), hintFileSize: Int64(data.count), hintFileIsLarge: false, forceNoBigParts: false)
|> mapError { _ -> PendingMessageUploadError in return .generic }
|> map { next -> UploadMediaEvent in
switch next {
case let .inputFile(inputFile):
return .result(Api.InputMedia.inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, videoCover: nil, videoTimestamp: nil, ttlSeconds: nil))
case .inputSecretFile:
preconditionFailure()
case let .progress(progress):
return .progress(progress)
}
}
}
@@ -0,0 +1,211 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
public enum StandaloneUploadMediaError {
case generic
}
public struct StandaloneUploadSecretFile {
let file: Api.InputEncryptedFile
let size: Int32
let key: SecretFileEncryptionKey
}
public enum StandaloneUploadMediaThumbnailResult {
case pending
case file(Api.InputFile)
case none
var file: Api.InputFile? {
if case let .file(file) = self {
return file
} else {
return nil
}
}
}
public enum StandaloneUploadMediaResult {
case media(AnyMediaReference)
}
public enum StandaloneUploadMediaEvent {
case progress(Float)
case result(StandaloneUploadMediaResult)
}
private func uploadedThumbnail(network: Network, postbox: Postbox, data: Data) -> Signal<Api.InputFile?, StandaloneUploadMediaError> {
return multipartUpload(network: network, postbox: postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { result -> Signal<Api.InputFile?, StandaloneUploadMediaError> in
switch result {
case .progress:
return .complete()
case let .inputFile(inputFile):
return .single(inputFile)
case .inputSecretFile:
return .single(nil)
}
}
}
public func standaloneUploadedImage(postbox: Postbox, network: Network, peerId: PeerId, text: String, data: Data, thumbnailData: Data? = nil, dimensions: PixelDimensions) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> {
return multipartUpload(network: network, postbox: postbox, source: .data(data), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { next -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch next {
case let .inputFile(inputFile):
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapError { _ -> StandaloneUploadMediaError in }
|> mapToSignal { inputPeer -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
if let inputPeer = inputPeer {
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: 0, file: inputFile, stickers: nil, ttlSeconds: nil)))
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch media {
case let .messageMediaPhoto(_, photo, _):
if let photo = photo {
if let mediaImage = telegramMediaImageFromApiPhoto(photo) {
return .single(.result(.media(.standalone(media: mediaImage))))
}
}
default:
break
}
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
case let .inputSecretFile(file, _, key):
return postbox.transaction { transaction -> Api.InputEncryptedChat? in
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
return Api.InputEncryptedChat.inputEncryptedChat(chatId: Int32(peer.id.id._internalGetInt64Value()), accessHash: peer.accessHash)
}
return nil
}
|> castError(StandaloneUploadMediaError.self)
|> mapToSignal { inputChat -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
guard let inputChat = inputChat else {
return .fail(.generic)
}
return network.request(Api.functions.messages.uploadEncryptedFile(peer: inputChat, file: file))
|> mapError { _ -> StandaloneUploadMediaError in return .generic
}
|> mapToSignal { result -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch result {
case let .encryptedFile(id, accessHash, size, dcId, _):
return .single(.result(.media(.standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: Int64(data.count), datacenterId: Int(dcId), key: key), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])))))
case .encryptedFileEmpty:
return .fail(.generic)
}
}
}
case let .progress(progress):
return .single(.progress(progress))
}
}
}
public func standaloneUploadedFile(postbox: Postbox, network: Network, peerId: PeerId, text: String, source: MultipartUploadSource, thumbnailData: Data? = nil, mimeType: String, attributes: [TelegramMediaFileAttribute], hintFileIsLarge: Bool) -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> {
let upload = multipartUpload(network: network, postbox: postbox, source: source, encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: statsCategoryForFileWithAttributes(attributes), userContentType: nil), hintFileSize: nil, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: false)
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
let uploadThumbnail: Signal<StandaloneUploadMediaThumbnailResult, StandaloneUploadMediaError>
if let thumbnailData = thumbnailData {
uploadThumbnail = .single(.pending)
|> then(
uploadedThumbnail(network: network, postbox: postbox, data: thumbnailData)
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> map { result in
if let result = result {
return .file(result)
} else {
return .none
}
}
)
} else {
uploadThumbnail = .single(.none)
}
return combineLatest(upload, uploadThumbnail)
|> mapToSignal { result, thumbnail in
switch result {
case let .progress(progress):
return .single(.progress(progress))
default:
switch thumbnail {
case .pending:
return .complete()
default:
switch result {
case let .inputFile(inputFile):
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapError { _ -> StandaloneUploadMediaError in }
|> mapToSignal { inputPeer -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
if let inputPeer = inputPeer {
var flags: Int32 = 0
let thumbnailFile = thumbnail.file
if let _ = thumbnailFile {
flags |= 1 << 2
}
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, videoCover: nil, videoTimestamp: nil, ttlSeconds: nil)))
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch media {
case let .messageMediaDocument(_, document, altDocuments, _, _, _):
if let document = document {
if let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) {
return .single(.result(.media(.standalone(media: mediaFile))))
}
}
default:
break
}
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
case let .inputSecretFile(file, _, key):
return postbox.transaction { transaction -> Api.InputEncryptedChat? in
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat {
return Api.InputEncryptedChat.inputEncryptedChat(chatId: Int32(peer.id.id._internalGetInt64Value()), accessHash: peer.accessHash)
}
return nil
}
|> castError(StandaloneUploadMediaError.self)
|> mapToSignal { inputChat -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
guard let inputChat = inputChat else {
return .fail(.generic)
}
return network.request(Api.functions.messages.uploadEncryptedFile(peer: inputChat, file: file))
|> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { result -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch result {
case let .encryptedFile(id, accessHash, size, dcId, _):
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: size, datacenterId: Int(dcId), key: key), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: size, attributes: attributes, alternativeRepresentations: [])
return .single(.result(.media(.standalone(media: media))))
case .encryptedFileEmpty:
return .fail(.generic)
}
}
}
case .progress:
return .never()
}
}
}
}
}