Swiftgram/submodules/WatchBridge/Sources/WatchRequestHandlers.swift
2020-06-02 16:38:24 +04:00

887 lines
47 KiB
Swift

import Foundation
import SwiftSignalKit
import Postbox
import Display
import TelegramCore
import SyncCore
import LegacyComponents
import WatchCommon
import TelegramPresentationData
import AvatarNode
import StickerResources
import PhotoResources
import AccountContext
import WatchBridgeAudio
let allWatchRequestHandlers: [AnyClass] = [
WatchChatListHandler.self,
WatchChatMessagesHandler.self,
WatchSendMessageHandler.self,
WatchPeerInfoHandler.self,
WatchMediaHandler.self,
WatchStickersHandler.self,
WatchAudioHandler.self,
WatchLocationHandler.self,
WatchPeerSettingsHandler.self,
WatchContinuationHandler.self,
]
protocol WatchRequestHandler: AnyObject {
static var handledSubscriptions: [Any] { get }
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal
}
final class WatchChatListHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [TGBridgeChatListSubscription.self]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeChatListSubscription {
let limit = Int(args.limit)
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<(ChatListView, PresentationData), NoError> in
if let context = context {
return context.account.viewTracker.tailChatListView(groupId: .root, count: limit)
|> map { chatListView, _ -> (ChatListView, PresentationData) in
return (chatListView, context.sharedContext.currentPresentationData.with { $0 })
}
} else {
return .complete()
}
})
let disposable = signal.start(next: { chatListView, presentationData in
var chats: [TGBridgeChat] = []
var users: [Int64 : TGBridgeUser] = [:]
for entry in chatListView.entries.reversed() {
if let (chat, chatUsers) = makeBridgeChat(entry, strings: presentationData.strings) {
chats.append(chat)
users = users.merging(chatUsers, uniquingKeysWith: { (_, last) in last })
}
}
subscriber?.putNext([ TGBridgeChatsArrayKey: chats, TGBridgeUsersDictionaryKey: users ])
})
return SBlockDisposable {
disposable.dispose()
}
}
} else {
return SSignal.fail(nil)
}
}
}
final class WatchChatMessagesHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [
TGBridgeChatMessageListSubscription.self,
TGBridgeChatMessageSubscription.self,
TGBridgeReadChatMessageListSubscription.self
]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeChatMessageListSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
return SSignal { subscriber in
let limit = Int(args.rangeMessageCount)
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<(MessageHistoryView, Bool, PresentationData), NoError> in
if let context = context {
return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: limit, fixedCombinedReadStates: nil)
|> map { messageHistoryView, _, _ -> (MessageHistoryView, Bool, PresentationData) in
return (messageHistoryView, peerId == context.account.peerId, context.sharedContext.currentPresentationData.with { $0 })
}
} else {
return .complete()
}
})
let disposable = signal.start(next: { messageHistoryView, savedMessages, presentationData in
var messages: [TGBridgeMessage] = []
var users: [Int64 : TGBridgeUser] = [:]
for entry in messageHistoryView.entries.reversed() {
if let (message, messageUsers) = makeBridgeMessage(entry, strings: presentationData.strings) {
messages.append(message)
users = users.merging(messageUsers, uniquingKeysWith: { (_, last) in last })
}
}
subscriber?.putNext([ TGBridgeMessagesArrayKey: messages, TGBridgeUsersDictionaryKey: users ])
})
return SBlockDisposable {
disposable.dispose()
}
}
} else if let args = subscription as? TGBridgeReadChatMessageListSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<Void, NoError> in
if let context = context {
let messageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId)
return applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
} else {
return .complete()
}
})
let disposable = signal.start(next: { _ in
subscriber?.putNext(true)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
} else if let args = subscription as? TGBridgeChatMessageSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<(Message, PresentationData)?, NoError> in
if let context = context {
let messageSignal = downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId))
|> map { message -> (Message, PresentationData)? in
if let message = message {
return (message, context.sharedContext.currentPresentationData.with { $0 })
} else {
return nil
}
}
return messageSignal |> timeout(3.5, queue: Queue.concurrentDefaultQueue(), alternate: .single(nil))
} else {
return .single(nil)
}
})
let disposable = signal.start(next: { messageAndPresentationData in
if let (message, presentationData) = messageAndPresentationData, let bridgeMessage = makeBridgeMessage(message, strings: presentationData.strings) {
let peers = makeBridgePeers(message)
var response: [String : Any] = [TGBridgeMessageKey: bridgeMessage, TGBridgeUsersDictionaryKey: peers]
if peerId.namespace != Namespaces.Peer.CloudUser {
response[TGBridgeChatKey] = peers[makeBridgeIdentifier(peerId)]
}
subscriber?.putNext(response)
}
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
}
return SSignal.fail(nil)
}
}
final class WatchSendMessageHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [
TGBridgeSendTextMessageSubscription.self,
TGBridgeSendLocationMessageSubscription.self,
TGBridgeSendStickerMessageSubscription.self,
TGBridgeSendForwardedMessageSubscription.self
]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<Bool, NoError> in
if let context = context {
var messageSignal: Signal<(EnqueueMessage?, PeerId?), NoError>?
if let args = subscription as? TGBridgeSendTextMessageSubscription {
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
var replyMessageId: MessageId?
if args.replyToMid != 0, let peerId = peerId {
replyMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.replyToMid)
}
messageSignal = .single((.message(text: args.text, attributes: [], mediaReference: nil, replyToMessageId: replyMessageId, localGroupingKey: nil), peerId))
} else if let args = subscription as? TGBridgeSendLocationMessageSubscription, let location = args.location {
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: makeVenue(from: location.venue), liveBroadcastingTimeout: nil)
messageSignal = .single((.message(text: "", attributes: [], mediaReference: .standalone(media: map), replyToMessageId: nil, localGroupingKey: nil), peerId))
} else if let args = subscription as? TGBridgeSendStickerMessageSubscription {
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
messageSignal = mediaForSticker(documentId: args.document.documentId, account: context.account)
|> map({ media -> (EnqueueMessage?, PeerId?) in
if let media = media {
return (.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil), peerId)
} else {
return (nil, nil)
}
})
} else if let args = subscription as? TGBridgeSendForwardedMessageSubscription {
let peerId = makePeerIdFromBridgeIdentifier(args.targetPeerId)
if let forwardPeerId = makePeerIdFromBridgeIdentifier(args.peerId) {
messageSignal = .single((.forward(source: MessageId(peerId: forwardPeerId, namespace: Namespaces.Message.Cloud, id: args.messageId), grouping: .none, attributes: []), peerId))
}
}
if let messageSignal = messageSignal {
return messageSignal |> mapToSignal({ message, peerId -> Signal<Bool, NoError> in
if let message = message, let peerId = peerId {
return enqueueMessages(account: context.account, peerId: peerId, messages: [message]) |> mapToSignal({ _ in
return .single(true)
})
} else {
return .complete()
}
})
}
}
return .complete()
})
let disposable = signal.start(next: { _ in
subscriber?.putNext(true)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
}
}
final class WatchPeerInfoHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [
TGBridgeUserInfoSubscription.self,
TGBridgeUserBotInfoSubscription.self,
TGBridgeConversationSubscription.self
]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeUserInfoSubscription {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<PeerView, NoError> in
if let context = context, let userId = args.userIds.first as? Int64, let peerId = makePeerIdFromBridgeIdentifier(userId) {
return context.account.viewTracker.peerView(peerId)
} else {
return .complete()
}
})
let disposable = signal.start(next: { view in
if let user = makeBridgeUser(peerViewMainPeer(view), presence: view.peerPresences[view.peerId], cachedData: view.cachedData) {
subscriber?.putNext([user.identifier: user])
} else {
subscriber?.putCompletion()
}
})
return SBlockDisposable {
disposable.dispose()
}
}
} else if let _ = subscription as? TGBridgeUserBotInfoSubscription {
return SSignal.complete()
} else if let args = subscription as? TGBridgeConversationSubscription {
return SSignal { subscriber in
let signal = manager.accountContext.get() |> take(1) |> mapToSignal({ context -> Signal<PeerView, NoError> in
if let context = context, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
return context.account.viewTracker.peerView(peerId)
} else {
return .complete()
}
})
let disposable = signal.start(next: { view in
let (chat, users) = makeBridgeChat(peerViewMainPeer(view), view: view)
subscriber?.putNext([ TGBridgeChatKey: chat, TGBridgeUsersDictionaryKey: users ])
})
return SBlockDisposable {
disposable.dispose()
}
}
}
return SSignal.fail(nil)
}
}
private func mediaForSticker(documentId: Int64, account: Account) -> Signal<TelegramMediaFile?, NoError> {
return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50)
|> take(1)
|> map { view -> TelegramMediaFile? in
for view in view.orderedItemListsViews {
for entry in view.items {
if let file = (entry.contents as? SavedStickerItem)?.file {
if file.id?.id == documentId {
return file
}
} else if let file = (entry.contents as? RecentMediaItem)?.media as? TelegramMediaFile {
if file.id?.id == documentId {
return file
}
}
}
}
return nil
}
}
private let roundCorners = { () -> UIImage in
let diameter: CGFloat = 44.0
UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0)
let context = UIGraphicsGetCurrentContext()!
context.setBlendMode(.copy)
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter)))
context.setBlendMode(.clear)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter)))
let image = UIGraphicsGetImageFromCurrentImageContext()!.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
UIGraphicsEndImageContext()
return image
}()
private func sendData(manager: WatchCommunicationManager, data: Data, key: String, ext: String, type: String, forceAsData: Bool = false) {
if let tempPath = manager.watchTemporaryStorePath, !forceAsData {
let tempFileUrl = URL(fileURLWithPath: tempPath + "/\(key)\(ext)")
let _ = try? data.write(to: tempFileUrl)
let _ = manager.sendFile(url: tempFileUrl, metadata: [TGBridgeIncomingFileTypeKey: type, TGBridgeIncomingFileIdentifierKey: key]).start()
} else {
let _ = manager.sendFile(data: data, metadata: [TGBridgeIncomingFileTypeKey: type, TGBridgeIncomingFileIdentifierKey: key]).start()
}
}
final class WatchMediaHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [
TGBridgeMediaThumbnailSubscription.self,
TGBridgeMediaAvatarSubscription.self,
TGBridgeMediaStickerSubscription.self
]
}
static private let disposable = DisposableSet()
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeMediaAvatarSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
let key = "\(args.url!)_\(args.type.rawValue)"
let targetSize: CGSize
var compressionRate: CGFloat = 0.5
var round = false
switch args.type {
case .small:
targetSize = CGSize(width: 19, height: 19);
compressionRate = 0.5
case .profile:
targetSize = CGSize(width: 44, height: 44);
round = true
case .large:
targetSize = CGSize(width: 150, height: 150);
@unknown default:
fatalError()
}
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<UIImage?, NoError> in
if let context = context {
return context.account.postbox.transaction { transaction -> Peer? in
guard let peer = transaction.getPeer(peerId) else {
return nil
}
if let peer = peer as? TelegramSecretChat {
return transaction.getPeer(peer.regularPeerId)
} else {
return peer
}
} |> mapToSignal({ peer -> Signal<UIImage?, NoError> in
if let peer = peer, let representation = peer.smallProfileImage {
let imageData = peerAvatarImageData(account: context.account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: representation, synchronousLoad: false)
if let imageData = imageData {
return imageData
|> map { data -> UIImage? in
if let data = data, let image = generateImage(targetSize, contextGenerator: { size, context -> Void in
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.setBlendMode(.copy)
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: targetSize))
if round {
context.setBlendMode(.normal)
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: targetSize))
}
}
}, scale: 2.0) {
return image
}
return nil
}
}
}
return .single(nil)
})
} else {
return .complete()
}
})
let disposable = signal.start(next: { image in
if let image = image, let imageData = image.jpegData(compressionQuality: compressionRate) {
sendData(manager: manager, data: imageData, key: key, ext: ".jpg", type: TGBridgeIncomingFileTypeImage, forceAsData: true)
}
subscriber?.putNext(key)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
} else if let args = subscription as? TGBridgeMediaStickerSubscription {
let key = "sticker_\(args.documentId)_\(Int(args.size.width))x\(Int(args.size.height))_\(args.notification ? 1 : 0)"
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<UIImage?, NoError> in
if let context = context {
var mediaSignal: Signal<(TelegramMediaFile, FileMediaReference)?, NoError>? = nil
if args.stickerPackId != 0 {
mediaSignal = mediaForSticker(documentId: args.documentId, account: context.account)
|> map { media -> (TelegramMediaFile, FileMediaReference)? in
if let media = media {
return (media, .standalone(media: media))
} else {
return nil
}
}
} else if args.stickerPeerId != 0, let peerId = makePeerIdFromBridgeIdentifier(args.stickerPeerId) {
mediaSignal = context.account.postbox.transaction { transaction -> Message? in
return transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.stickerMessageId))
}
|> map { message -> (TelegramMediaFile, FileMediaReference)? in
if let message = message {
for media in message.media {
if let media = media as? TelegramMediaFile {
return (media, .message(message: MessageReference(message), media: media))
}
}
}
return nil
}
}
var size: CGSize = args.size
if let mediaSignal = mediaSignal {
return mediaSignal
|> mapToSignal { mediaAndFileReference -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let (media, fileReference) = mediaAndFileReference {
if let dimensions = media.dimensions {
size = dimensions.cgSize
}
self.disposable.add(freeMediaFileInteractiveFetched(account: context.account, fileReference: fileReference).start())
return chatMessageSticker(account: context.account, file: media, small: false, fetched: true, onlyFullSize: true)
}
return .complete()
}
|> map{ f -> UIImage? in
let context = f(TransformImageArguments(corners: ImageCorners(), imageSize: size.fitted(args.size), boundingSize: args.size, intrinsicInsets: UIEdgeInsets(), emptyColor: args.notification ? UIColor(rgb: 0xe5e5ea) : .black, scale: 2.0))
return context?.generateImage()
}
}
}
return .complete()
})
let disposable = signal.start(next: { image in
if let image = image, let imageData = image.jpegData(compressionQuality: 0.2) {
sendData(manager: manager, data: imageData, key: key, ext: ".jpg", type: TGBridgeIncomingFileTypeImage, forceAsData: args.notification)
}
subscriber?.putNext(key)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
} else if let args = subscription as? TGBridgeMediaThumbnailSubscription {
let key = "\(args.peerId)_\(args.messageId)"
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<UIImage?, NoError> in
if let context = context, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
var roundVideo = false
return context.account.postbox.transaction { transaction -> Message? in
return transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId))
}
|> mapToSignal { message -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let message = message, !message.containsSecretMedia {
var imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedMediaReference: AnyMediaReference?
var candidateMediaReference: AnyMediaReference?
var imageDimensions: CGSize?
for media in message.media {
if let image = media as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource {
self.disposable.add(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: resource, storeToDownloadsPeerType: nil).start())
candidateMediaReference = .message(message: MessageReference(message), media: media)
break
} else if let _ = media as? TelegramMediaFile {
candidateMediaReference = .message(message: MessageReference(message), media: media)
break
} else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let resource = largestImageRepresentation(image.representations)?.resource {
self.disposable.add(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: resource, storeToDownloadsPeerType: nil).start())
candidateMediaReference = .webPage(webPage: WebpageReference(webPage), media: image)
break
}
}
if let imageReference = candidateMediaReference?.concrete(TelegramMediaImage.self) {
updatedMediaReference = imageReference.abstract
if let representation = largestRepresentationForPhoto(imageReference.media) {
imageDimensions = representation.dimensions.cgSize
}
} else if let fileReference = candidateMediaReference?.concrete(TelegramMediaFile.self) {
updatedMediaReference = fileReference.abstract
if let representation = largestImageRepresentation(fileReference.media.previewRepresentations), !fileReference.media.isSticker {
imageDimensions = representation.dimensions.cgSize
}
}
if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
imageSignal = chatMessagePhotoThumbnail(account: context.account, photoReference: imageReference, onlyFullSize: true)
} else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
if fileReference.media.isVideo {
imageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference)
roundVideo = fileReference.media.isInstantVideo
} else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
imageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation)
}
}
}
if let signal = imageSignal {
return signal
}
}
return .complete()
} |> map{ f -> UIImage? in
var insets = UIEdgeInsets()
if roundVideo {
insets = UIEdgeInsets(top: -2, left: -2, bottom: -2, right: -2)
}
let context = f(TransformImageArguments(corners: ImageCorners(), imageSize: args.size, boundingSize: args.size, intrinsicInsets: insets, scale: 2.0))
return context?.generateImage()
}
} else {
return .complete()
}
})
let disposable = signal.start(next: { image in
if let image = image, let imageData = image.jpegData(compressionQuality: 0.5) {
sendData(manager: manager, data: imageData, key: key, ext: ".jpg", type: TGBridgeIncomingFileTypeImage, forceAsData: args.notification)
}
subscriber?.putNext(key)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
}
return SSignal.fail(nil)
}
}
final class WatchStickersHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [TGBridgeRecentStickersSubscription.self]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeRecentStickersSubscription {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<ItemCollectionsView, NoError> in
if let context = context {
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50) |> take(1)
} else {
return .complete()
}
})
let disposable = signal.start(next: { view in
var stickers: [TGBridgeDocumentMediaAttachment] = []
var added: Set<Int64> = []
outer: for view in view.orderedItemListsViews {
for entry in view.items {
if let file = (entry.contents as? SavedStickerItem)?.file {
if let sticker = makeBridgeDocument(file), !added.contains(sticker.documentId) {
stickers.append(sticker)
added.insert(sticker.documentId)
}
} else if let file = (entry.contents as? RecentMediaItem)?.media as? TelegramMediaFile {
if let sticker = makeBridgeDocument(file), !added.contains(sticker.documentId) {
stickers.append(sticker)
added.insert(sticker.documentId)
}
}
if stickers.count == args.limit {
break outer
}
}
}
subscriber?.putNext(stickers)
})
return SBlockDisposable {
disposable.dispose()
}
}
}
return SSignal.fail(nil)
}
}
final class WatchAudioHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [
TGBridgeAudioSubscription.self,
TGBridgeAudioSentSubscription.self
]
}
static private let disposable = DisposableSet()
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeAudioSubscription {
let key = "audio_\(args.peerId)_\(args.messageId)"
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<String, NoError> in
if let context = context, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
return context.account.postbox.transaction { transaction -> Message? in
return transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId))
}
|> mapToSignal { message -> Signal<String, NoError> in
if let message = message {
for media in message.media {
if let file = media as? TelegramMediaFile {
self.disposable.add(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: true).start())
return context.account.postbox.mediaBox.resourceData(file.resource)
|> mapToSignal({ data -> Signal<String, NoError> in
if let tempPath = manager.watchTemporaryStorePath, data.complete {
let outputPath = tempPath + "/\(key).m4a"
return legacyDecodeOpusAudio(path: data.path, outputPath: outputPath)
} else {
return .complete()
}
})
}
}
}
return .complete()
}
} else {
return .complete()
}
})
let disposable = signal.start(next: { path in
let _ = manager.sendFile(url: URL(fileURLWithPath: path), metadata: [TGBridgeIncomingFileTypeKey: TGBridgeIncomingFileTypeAudio, TGBridgeIncomingFileIdentifierKey: key]).start()
subscriber?.putNext(key)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
//let outputPath = manager.watchTemporaryStorePath + "/\(key).opus"
} else if let _ = subscription as? TGBridgeAudioSentSubscription {
}
return SSignal.fail(nil)
}
static func handleFile(path: String, metadata: Dictionary<String, Any>, manager: WatchCommunicationManager) -> Signal<Void, NoError> {
let randomId = metadata[TGBridgeIncomingFileRandomIdKey] as? Int64
let peerId = metadata[TGBridgeIncomingFilePeerIdKey] as? Int64
let replyToMid = metadata[TGBridgeIncomingFileReplyToMidKey] as? Int32
if let randomId = randomId, let id = peerId, let peerId = makePeerIdFromBridgeIdentifier(id) {
return combineLatest(manager.accountContext.get() |> take(1), legacyEncodeOpusAudio(path: path))
|> map({ context, pathAndDuration -> Void in
let (path, duration) = pathAndDuration
if let context = context, let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
let resource = LocalFileMediaResource(fileId: randomId)
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
var replyMessageId: MessageId? = nil
if let replyToMid = replyToMid, replyToMid != 0 {
replyMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMid)
}
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: data.count, attributes: [.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)])), replyToMessageId: replyMessageId, localGroupingKey: nil)]).start()
}
})
} else {
return .complete()
}
}
}
final class WatchLocationHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [TGBridgeNearbyVenuesSubscription.self]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeNearbyVenuesSubscription {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<[ChatContextResultMessage], NoError> in
if let context = context {
return resolvePeerByName(account: context.account, name: "foursquare")
|> take(1)
|> mapToSignal { peerId -> Signal<ChatContextResultCollection?, NoError> in
guard let peerId = peerId else {
return .single(nil)
}
return requestChatContextResults(account: context.account, botId: peerId, peerId: context.account.peerId, query: "", location: .single((args.coordinate.latitude, args.coordinate.longitude)), offset: "")
|> map { results -> ChatContextResultCollection? in
return results?.results
}
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
}
|> mapToSignal { contextResult -> Signal<[ChatContextResultMessage], NoError> in
guard let contextResult = contextResult else {
return .single([])
}
return .single(contextResult.results.map { $0.message })
}
} else {
return .complete()
}
})
let disposable = signal.start(next: { results in
var venues: [TGBridgeLocationVenue] = []
for result in results {
if let venue = makeBridgeLocationVenue(result) {
venues.append(venue)
}
}
subscriber?.putNext(venues)
})
return SBlockDisposable {
disposable.dispose()
}
}
}
return SSignal.fail(nil)
}
}
final class WatchPeerSettingsHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [
TGBridgePeerSettingsSubscription.self,
TGBridgePeerUpdateNotificationSettingsSubscription.self,
TGBridgePeerUpdateBlockStatusSubscription.self
]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgePeerSettingsSubscription {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<PeerView, NoError> in
if let context = context, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
return context.account.viewTracker.peerView(peerId)
} else {
return .complete()
}
})
let disposable = signal.start(next: { view in
var muted = false
var blocked = false
if let notificationSettings = view.notificationSettings as? TelegramPeerNotificationSettings, case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
muted = true
}
if let cachedData = view.cachedData as? CachedUserData {
blocked = cachedData.isBlocked
}
subscriber?.putNext([ "muted": muted, "blocked": blocked ])
})
return SBlockDisposable {
disposable.dispose()
}
}
} else {
return SSignal { subscriber in
let signal = manager.accountContext.get()
|> take(1)
|> mapToSignal({ context -> Signal<Bool, NoError> in
if let context = context {
var signal: Signal<Void, NoError>?
if let args = subscription as? TGBridgePeerUpdateNotificationSettingsSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
signal = togglePeerMuted(account: context.account, peerId: peerId)
} else if let args = subscription as? TGBridgePeerUpdateBlockStatusSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
signal = requestUpdatePeerIsBlocked(account: context.account, peerId: peerId, isBlocked: args.blocked)
}
if let signal = signal {
return signal |> mapToSignal({ _ in
return .single(true)
})
} else {
return .complete()
}
} else {
return .complete()
}
})
let disposable = signal.start(next: { _ in
subscriber?.putNext(true)
}, completed: {
subscriber?.putCompletion()
})
return SBlockDisposable {
disposable.dispose()
}
}
}
}
}
final class WatchContinuationHandler: WatchRequestHandler {
static var handledSubscriptions: [Any] {
return [TGBridgeRemoteSubscription.self]
}
static func handle(subscription: TGBridgeSubscription, manager: WatchCommunicationManager) -> SSignal {
if let args = subscription as? TGBridgeRemoteSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) {
manager.requestNavigateToMessage(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: args.messageId))
}
return SSignal.fail(nil)
}
}