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 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 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 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 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 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 { 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 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 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 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 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 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 = [] 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 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 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 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, manager: WatchCommunicationManager) -> Signal { 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 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 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 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 in if let context = context { var signal: Signal? 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) } }