Swiftgram/Telegram-iOS/WatchRequestHandlers.swift
2019-06-06 23:17:31 +01:00

875 lines
46 KiB
Swift

import Foundation
import SwiftSignalKit
import Postbox
import Display
import TelegramCore
import TelegramUI
import LegacyComponents
#if BUCK
import WatchUtils
#endif
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), 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);
}
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, peer: peer, authorOfMessage: nil, representation: representation, synchronousLoad: false)
if let imageData = imageData {
return imageData |> deliverOn(Queue.concurrentDefaultQueue())
|> 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 = UIImageJPEGRepresentation(image, compressionRate) {
sendData(manager: manager, data: imageData, key: key, ext: ".jpg", type: TGBridgeIncomingFileTypeImage)
}
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
}
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 = UIImageJPEGRepresentation(image, 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
}
} else if let fileReference = candidateMediaReference?.concrete(TelegramMediaFile.self) {
updatedMediaReference = fileReference.abstract
if let representation = largestImageRepresentation(fileReference.media.previewRepresentations), !fileReference.media.isSticker {
imageDimensions = representation.dimensions
}
}
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 = UIEdgeInsetsMake(-2, -2, -2, -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 = UIImageJPEGRepresentation(image, 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: [], 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: "")
}
|> 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)
}
}