Initial support for geo inline bot requests

This commit is contained in:
Ilya Laktyushin 2019-07-10 06:15:09 +02:00
parent f7d85f46b3
commit bcaf3f6569
11 changed files with 107 additions and 33 deletions

View File

@ -15,7 +15,12 @@ import Foundation
#endif
#endif
public func requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String) -> Signal<ChatContextResultCollection?, NoError> {
public enum RequestChatContextResultsError {
case generic
case locationRequired
}
public func requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String) -> Signal<ChatContextResultCollection?, RequestChatContextResultsError> {
return combineLatest(account.postbox.transaction { transaction -> (bot: Peer, peer: Peer)? in
if let bot = transaction.getPeer(botId), let peer = transaction.getPeer(peerId) {
return (bot, peer)
@ -23,7 +28,8 @@ public func requestChatContextResults(account: Account, botId: PeerId, peerId: P
return nil
}
}, location)
|> mapToSignal { botAndPeer, location -> Signal<ChatContextResultCollection?, NoError> in
|> introduceError(RequestChatContextResultsError.self)
|> mapToSignal { botAndPeer, location -> Signal<ChatContextResultCollection?, RequestChatContextResultsError> in
if let (bot, peer) = botAndPeer, let inputBot = apiInputUser(bot) {
var flags: Int32 = 0
var inputPeer: Api.InputPeer = .inputPeerEmpty
@ -39,8 +45,12 @@ public func requestChatContextResults(account: Account, botId: PeerId, peerId: P
|> map { result -> ChatContextResultCollection? in
return ChatContextResultCollection(apiResults: result, botId: bot.id, peerId: peerId, query: query, geoPoint: location)
}
|> `catch` { _ -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
|> mapError { error -> RequestChatContextResultsError in
if error.errorDescription == "BOT_INLINE_GEO_REQUIRED" {
return .locationRequired
} else {
return .generic
}
}
} else {
return .single(nil)

View File

@ -332,6 +332,9 @@ public func searchGifs(account: Account, query: String) -> Signal<ChatContextRes
}
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
return requestChatContextResults(account: account, botId: peer.id, peerId: account.peerId, query: query, offset: "")
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
}
}

View File

@ -4024,6 +4024,17 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
})
}
}
}, error: { [weak self] error in
if let strongSelf = self {
switch error {
case let .inlineBotLocationRequest(peerId):
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: Int32(Date().timeIntervalSince1970 + 10 * 60)).start()
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
let _ = ApplicationSpecificNotice.setInlineBotLocationRequest(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId, value: 0).start()
})]), in: .window(.root))
}
}
}))
inScope = false
if let inScopeResult = inScopeResult {

View File

@ -7,9 +7,13 @@ import TelegramUIPreferences
import TelegramUIPrivateModule
import LegacyComponents
enum ChatContextQueryError {
case inlineBotLocationRequest(PeerId)
}
enum ChatContextQueryUpdate {
case remove
case update(ChatPresentationInputQuery, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)
case update(ChatPresentationInputQuery, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError>)
}
func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
@ -55,10 +59,10 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
return updates
}
private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> {
private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
switch inputQuery {
case let .emoji(query):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery {
switch previousQuery {
case .emoji:
@ -69,11 +73,12 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
} else {
signal = .single({ _ in return .stickers([]) })
}
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
let stickerSettings: StickerSettings = (transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings) as? StickerSettings) ?? .defaultSettings
return stickerSettings
}
|> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], NoError> in
|> introduceError(ChatContextQueryError.self)
|> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
let scope: SearchStickersScope
switch stickerSettings.emojiStickerSuggestionMode {
case .none:
@ -84,6 +89,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
scope = [.installed]
}
return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope)
|> introduceError(ChatContextQueryError.self)
}
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in
@ -92,7 +98,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
}
return signal |> then(stickers)
case let .hashtag(query):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery {
switch previousQuery {
case .hashtag:
@ -104,7 +110,8 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
signal = .single({ _ in return .hashtags([]) })
}
let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = recentlyUsedHashtags(postbox: context.account.postbox) |> map { hashtags -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = recentlyUsedHashtags(postbox: context.account.postbox)
|> map { hashtags -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let normalizedQuery = query.lowercased()
var result: [String] = []
for hashtag in hashtags {
@ -114,12 +121,13 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
}
return { _ in return .hashtags(result) }
}
|> introduceError(ChatContextQueryError.self)
return signal |> then(hashtags)
case let .mention(query, types):
let normalizedQuery = query.lowercased()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery {
switch previousQuery {
case .mention:
@ -168,12 +176,13 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
}
return { _ in return .mentions(sortedPeers) }
}
|> introduceError(ChatContextQueryError.self)
return signal |> then(participants)
case let .command(query):
let normalizedQuery = query.lowercased()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery {
switch previousQuery {
case .command:
@ -185,22 +194,22 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
signal = .single({ _ in return .commands([]) })
}
let participants = peerCommands(account: context.account, id: peer.id)
|> map { commands -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredCommands = commands.commands.filter { command in
if command.command.text.hasPrefix(normalizedQuery) {
return true
}
return false
let commands = peerCommands(account: context.account, id: peer.id)
|> map { commands -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredCommands = commands.commands.filter { command in
if command.command.text.hasPrefix(normalizedQuery) {
return true
}
let sortedCommands = filteredCommands
return { _ in return .commands(sortedCommands) }
return false
}
let sortedCommands = filteredCommands
return { _ in return .commands(sortedCommands) }
}
return signal |> then(participants)
|> introduceError(ChatContextQueryError.self)
return signal |> then(commands)
case let .contextRequest(addressName, query):
var delayRequest = true
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery {
switch previousQuery {
case let .contextRequest(currentAddressName, currentContextQuery) where currentAddressName == addressName:
@ -228,16 +237,20 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
return .single(nil)
}
}
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in
|> introduceError(ChatContextQueryError.self)
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
let contextResults = requestChatContextResults(account: context.account, botId: user.id, peerId: chatPeer.id, query: query, offset: "")
|> mapError { error -> ChatContextQueryError in
return .inlineBotLocationRequest(user.id)
}
|> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in
return .contextRequestResult(user, results)
}
}
let botResult: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ previousResult in
let botResult: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .single({ previousResult in
var passthroughPreviousResult: ChatContextResultCollection?
if let previousResult = previousResult {
if case let .contextRequestResult(previousUser, previousResults) = previousResult {
@ -249,9 +262,10 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
return .contextRequestResult(user, passthroughPreviousResult)
})
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError>
if delayRequest {
maybeDelayedContextResults = contextResults |> delay(0.4, queue: Queue.concurrentDefaultQueue())
maybeDelayedContextResults = contextResults
|> delay(0.4, queue: Queue.concurrentDefaultQueue())
} else {
maybeDelayedContextResults = contextResults
}
@ -291,6 +305,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in return .emojis(result, range) }
}
|> introduceError(ChatContextQueryError.self)
}
}

View File

@ -107,7 +107,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
}
}
let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: incoming ? bubbleVariableColor(variableColor: theme.theme.chat.message.incoming.actionButtonsTextColor, wallpaper: theme.wallpaper) : bubbleVariableColor(variableColor: theme.theme.chat.message.outgoing.actionButtonsTextColor, wallpaper: theme.wallpaper)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
let messageTheme = incoming ? theme.theme.chat.message.incoming : theme.theme.chat.message.outgoing
let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
let backgroundImage: UIImage?
switch position {

View File

@ -381,6 +381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
let content = item.content
let firstMessage = content.firstMessage
let incoming = item.content.effectivelyIncoming(item.context.account.peerId)
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
var sourceReference: SourceReferenceMessageAttribute?
for attribute in item.content.firstMessage.attributes {
@ -857,14 +858,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
headerSize.height += 5.0
}
let inlineBotNameColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
let inlineBotNameColor = messageTheme.accentTextColor
let attributedString: NSAttributedString
var adminBadgeString: NSAttributedString?
if authorIsAdmin {
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Conversation_Admin)", font: inlineBotPrefixFont, textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor)
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Conversation_Admin)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor)
} else if authorIsChannel {
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Channel_Status)", font: inlineBotPrefixFont, textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor)
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Channel_Status)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor)
}
if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString {
let mutableString = NSMutableAttributedString(string: "\(authorNameString) ", attributes: [NSAttributedStringKey.font: nameFont, NSAttributedStringKey.foregroundColor: authorNameColor])

View File

@ -53,6 +53,9 @@ func legacyLocationPickerController(context: AccountContext, selfPeer: Peer, pee
return .single(nil)
}
return requestChatContextResults(account: context.account, botId: peerId, peerId: selfPeer.id, query: query ?? "", location: .single((location?.coordinate.latitude ?? 0.0, location?.coordinate.longitude ?? 0.0)), offset: "")
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
}
|> mapToSignal { contextResult -> Signal<[TGLocationVenue], NoError> in
guard let contextResult = contextResult else {

View File

@ -158,6 +158,11 @@ private struct ApplicationSpecificNoticeKeys {
private static let globalNamespace: Int32 = 2
private static let permissionsNamespace: Int32 = 3
private static let peerReportNamespace: Int32 = 4
private static let inlineBotLocationRequestNamespace: Int32 = 1
static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0))
}
static func botPaymentLiabilityNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: botPaymentLiabilityNamespace), key: noticeKey(peerId: peerId, key: 0))
@ -251,6 +256,22 @@ public struct ApplicationSpecificNotice {
}
}
static func getInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId) -> Signal<Int32?, NoError> {
return accountManager.transaction { transaction -> Int32? in
if let notice = transaction.getNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId)) as? ApplicationSpecificTimestampNotice {
return notice.value
} else {
return nil
}
}
}
static func setInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId, value: Int32) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
transaction.setNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId), ApplicationSpecificTimestampNotice(value: value))
}
}
static func getSecretChatInlineBotUsage(accountManager: AccountManager) -> Signal<Bool, NoError> {
return accountManager.transaction { transaction -> Bool in
if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.secretChatInlineBotUsage()) as? ApplicationSpecificBoolNotice {

View File

@ -477,6 +477,9 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
if let _ = searchContext.loadMoreIndex, let nextOffset = searchContext.result.nextOffset {
let collection = searchContext.result.collection
return requestChatContextResults(account: self.context.account, botId: collection.botId, peerId: collection.peerId, query: searchContext.result.query, location: .single(collection.geoPoint), offset: nextOffset)
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
|> map { nextResults -> (ChatContextResultCollection, String?) in
var results: [ChatContextResult] = []
var existingIds = Set<String>()

View File

@ -748,6 +748,9 @@ final class WatchLocationHandler: WatchRequestHandler {
return .single(nil)
}
return requestChatContextResults(account: context.account, botId: peerId, peerId: context.account.peerId, query: "", location: .single((args.coordinate.latitude, args.coordinate.longitude)), offset: "")
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
}
|> mapToSignal { contextResult -> Signal<[ChatContextResultMessage], NoError> in
guard let contextResult = contextResult else {

View File

@ -10,6 +10,9 @@ import TelegramUIPreferences
func requestContextResults(account: Account, botId: PeerId, query: String, peerId: PeerId, offset: String = "", existingResults: ChatContextResultCollection? = nil, limit: Int = 60) -> Signal<ChatContextResultCollection?, NoError> {
return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset)
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
|> mapToSignal { results -> Signal<ChatContextResultCollection?, NoError> in
var collection = existingResults
var updated: Bool = false