diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 3840144291..75ffce2a03 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -292,12 +292,12 @@ public struct ChatControllerInitialAttachBotStart { } public struct ChatControllerInitialBotAppStart { - public let botApp: BotApp + public let botApp: BotApp? public let payload: String? public let justInstalled: Bool public let compact: Bool - public init(botApp: BotApp, payload: String?, justInstalled: Bool, compact: Bool) { + public init(botApp: BotApp?, payload: String?, justInstalled: Bool, compact: Bool) { self.botApp = botApp self.payload = payload self.justInstalled = justInstalled diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index b190c0cb86..5d89917c5c 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -402,156 +402,160 @@ public extension ChatControllerImpl { } } - func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) { + func presentBotApp(botApp: BotApp?, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) { guard let peerId = self.chatLocation.peerId else { return } self.attachmentController?.dismiss(animated: true, completion: nil) - let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in - guard let strongSelf = self else { - return - } - commit() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - return $0.updatedTitlePanelContext { - if !$0.contains(where: { - switch $0 { - case .requestInProgress: - return true - default: - return false - } - }) { - var updatedContexts = $0 - updatedContexts.append(.requestInProgress) - return updatedContexts.sorted() - } - return $0 - } - }) - - let updateProgress = { [weak self] in - Queue.mainQueue().async { - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - return $0.updatedTitlePanelContext { - if let index = $0.firstIndex(where: { - switch $0 { - case .requestInProgress: - return true - default: - return false - } - }) { - var updatedContexts = $0 - updatedContexts.remove(at: index) - return updatedContexts - } - return $0 - } - }) - } - } - } - - let botAddress = botPeer.addressName ?? "" - strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite) - |> afterDisposed { - updateProgress() - }) - |> deliverOnMainQueue).startStrict(next: { [weak self] result in + if let botApp { + let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in guard let strongSelf = self else { return } - let context = strongSelf.context - let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize)) - var presentImpl: ((ViewController, Any?) -> Void)? - let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in - ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in - presentImpl?(c, a) - }, commit: commit) - }, requestSwitchInline: { [weak self] query, chatTypes, completion in - ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) - }, completion: { [weak self] in - self?.chatDisplayNode.historyNode.scrollToEndOfHistory() - }, getNavigationController: { [weak self] in - return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController + commit() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + return $0.updatedTitlePanelContext { + if !$0.contains(where: { + switch $0 { + case .requestInProgress: + return true + default: + return false + } + }) { + var updatedContexts = $0 + updatedContexts.append(.requestInProgress) + return updatedContexts.sorted() + } + return $0 + } }) - controller.navigationPresentation = .flatModal - strongSelf.currentWebAppController = controller - strongSelf.push(controller) - presentImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) - } - - if justInstalled { - let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil) - controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) - } - }, error: { [weak self] error in - if let strongSelf = self { - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - })]), in: .window(.root)) - } - })) - } - - let _ = combineLatest( - queue: Queue.mainQueue(), - ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id), - self.context.engine.messages.attachMenuBots(), - self.context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - ).startStandalone(next: { [weak self] noticed, attachMenuBots, attachMenuBot in - guard let self else { - return - } - - var isAttachMenuBotInstalled: Bool? - if let _ = attachMenuBot { - if let _ = attachMenuBots.first(where: { $0.peer.id == botPeer.id && !$0.flags.contains(.notActivated) }) { - isAttachMenuBotInstalled = true - } else { - isAttachMenuBotInstalled = false - } - } - - let context = self.context - if !noticed || botApp.flags.contains(.notActivated) || isAttachMenuBotInstalled == false { - if let isAttachMenuBotInstalled, let attachMenuBot { - if !isAttachMenuBotInstalled { - let controller = webAppTermsAlertController(context: context, updatedPresentationData: self.updatedPresentationData, bot: attachMenuBot, completion: { allowWrite in - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() - let _ = (context.engine.messages.addBotToAttachMenu(botId: botPeer.id, allowWrite: allowWrite) - |> deliverOnMainQueue).startStandalone(error: { _ in - }, completed: { - openBotApp(allowWrite, true) + let updateProgress = { [weak self] in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + return $0.updatedTitlePanelContext { + if let index = $0.firstIndex(where: { + switch $0 { + case .requestInProgress: + return true + default: + return false + } + }) { + var updatedContexts = $0 + updatedContexts.remove(at: index) + return updatedContexts + } + return $0 + } }) + } + } + } + + let botAddress = botPeer.addressName ?? "" + strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite) + |> afterDisposed { + updateProgress() + }) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let strongSelf = self else { + return + } + let context = strongSelf.context + let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize)) + var presentImpl: ((ViewController, Any?) -> Void)? + let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in + ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in + presentImpl?(c, a) + }, commit: commit) + }, requestSwitchInline: { [weak self] query, chatTypes, completion in + ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) + }, completion: { [weak self] in + self?.chatDisplayNode.historyNode.scrollToEndOfHistory() + }, getNavigationController: { [weak self] in + return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController + }) + controller.navigationPresentation = .flatModal + strongSelf.currentWebAppController = controller + strongSelf.push(controller) + + presentImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } + + if justInstalled { + let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil) + controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + } + }, error: { [weak self] error in + if let strongSelf = self { + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { + })]), in: .window(.root)) + } + })) + } + + let _ = combineLatest( + queue: Queue.mainQueue(), + ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id), + self.context.engine.messages.attachMenuBots(), + self.context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + ).startStandalone(next: { [weak self] noticed, attachMenuBots, attachMenuBot in + guard let self else { + return + } + + var isAttachMenuBotInstalled: Bool? + if let _ = attachMenuBot { + if let _ = attachMenuBots.first(where: { $0.peer.id == botPeer.id && !$0.flags.contains(.notActivated) }) { + isAttachMenuBotInstalled = true + } else { + isAttachMenuBotInstalled = false + } + } + + let context = self.context + if !noticed || botApp.flags.contains(.notActivated) || isAttachMenuBotInstalled == false { + if let isAttachMenuBotInstalled, let attachMenuBot { + if !isAttachMenuBotInstalled { + let controller = webAppTermsAlertController(context: context, updatedPresentationData: self.updatedPresentationData, bot: attachMenuBot, completion: { allowWrite in + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() + let _ = (context.engine.messages.addBotToAttachMenu(botId: botPeer.id, allowWrite: allowWrite) + |> deliverOnMainQueue).startStandalone(error: { _ in + }, completed: { + openBotApp(allowWrite, true) + }) + }) + self.present(controller, in: .window(.root)) + } else { + openBotApp(false, false) + } + } else { + let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() + openBotApp(allowWrite, false) + }, showMore: { [weak self] in + if let self { + self.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil) + } }) self.present(controller, in: .window(.root)) - } else { - openBotApp(false, false) } } else { - let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() - openBotApp(allowWrite, false) - }, showMore: { [weak self] in - if let self { - self.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil) - } - }) - self.present(controller, in: .window(.root)) + openBotApp(false, false) } - } else { - openBotApp(false, false) - } - }) + }) + } else { + self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, peer: botPeer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false) + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 30c8cab465..3e451e83b4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9211,10 +9211,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in if let strongSelf = self, let peer { - strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: { - dismissWebAppControllers() + if let botApp = botAppStart.botApp { + strongSelf.presentBotApp(botApp: botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: { + dismissWebAppControllers() + commit() + }) + } else { + strongSelf.context.sharedContext.openWebApp(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false) commit() - }) + } } }) default: diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 0b336215be..bc5e329a88 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -292,7 +292,20 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) } } return .startAttach(peerName, value, choose) - } else if queryItem.name == "story" { + } else if queryItem.name == "startapp" { + var compact = false + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "mode", value == "compact" { + compact = true + break + } + } + } + } + return .peer(.name(peerName), .appStart("", queryItem.value, compact)) + } else if queryItem.name == "story" { if let id = Int32(value) { return .peer(.name(peerName), .story(id)) } @@ -321,6 +334,19 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) return .peer(.name(peerName), .boost) } else if queryItem.name == "profile" { return .peer(.name(peerName), .profile) + } else if queryItem.name == "startapp" { + var compact = false + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "mode", value == "compact" { + compact = true + break + } + } + } + } + return .peer(.name(peerName), .appStart("", nil, compact)) } } } @@ -720,18 +746,26 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } case let .appStart(name, payload, compact): - return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { botApp -> Signal in - if let botApp { - return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, compact: compact))))) + if name.isEmpty { + if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { + return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: nil, payload: payload, justInstalled: false, compact: compact))))) } else { return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) } - }) + } else { + return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { botApp -> Signal in + if let botApp { + return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, compact: compact))))) + } else { + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) + } + }) + } case let .channelMessage(id, timecode): if case let .channel(channel) = peer, channel.flags.contains(.isForum) { let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 2bb881c944..ce662fea57 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -333,9 +333,6 @@ public final class WebAppController: ViewController, AttachmentContainable { if let parsedUrl = URL(string: url) { self.webView?.load(URLRequest(url: parsedUrl)) } - - self.checkBotIdAndUrl(url) - if let keepAliveSignal = controller.keepAliveSignal { self.keepAliveDisposable = (keepAliveSignal |> deliverOnMainQueue).start(error: { [weak self] _ in @@ -355,7 +352,6 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let strongSelf = self else { return } - strongSelf.checkBotIdAndUrl(result.url) if let parsedUrl = URL(string: result.url) { strongSelf.queryId = result.queryId strongSelf.webView?.load(URLRequest(url: parsedUrl)) @@ -368,17 +364,16 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let self, let controller = self.controller else { return } - guard case let .peer(peer, params) = result, let peer, case let .withBotApp(appStart) = params else { + guard case let .peer(peer, params) = result, let peer, case let .withBotApp(appStart) = params, let botApp = appStart.botApp else { controller.dismiss() return } - let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: appStart.botApp.id, accessHash: appStart.botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true) + let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true) |> deliverOnMainQueue).startStandalone(next: { [weak self] result in guard let self, let parsedUrl = URL(string: result.url) else { return } - self.checkBotIdAndUrl(result.url) - self.controller?.titleView?.title = WebAppTitle(title: appStart.botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified) + self.controller?.titleView?.title = WebAppTitle(title: botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified) self.webView?.load(URLRequest(url: parsedUrl)) }) }) @@ -390,9 +385,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } strongSelf.queryId = result.queryId strongSelf.webView?.load(URLRequest(url: parsedUrl)) - - strongSelf.checkBotIdAndUrl(result.url) - + if let keepAliveSignal = result.keepAliveSignal { strongSelf.keepAliveDisposable = (keepAliveSignal |> deliverOnMainQueue).start(error: { [weak self] _ in @@ -412,14 +405,6 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - func checkBotIdAndUrl(_ url: String) { -// if url.hasPrefix("https://walletbot.me"), let botId = self.controller?.botId.id._internalGetInt64Value(), botId != 1985737506 { -// let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: "Bot id mismatch, please report steps to app developer", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { -// })]) -// self.controller?.present(alertController, in: .window(.root)) -// } - } - @objc fileprivate func mainButtonPressed() { if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled { return