diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index d8d18e5d8..d514dbd0b 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -176,13 +176,15 @@ export function buildApiMessageWithChatId( } = mtpMessage.replyTo || {}; const isEdited = mtpMessage.editDate && !mtpMessage.editHide; const { - inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, + inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective, } = buildReplyButtons(mtpMessage, isInvoiceMedia) || {}; const forwardInfo = mtpMessage.fwdFrom && buildApiMessageForwardInfo(mtpMessage.fwdFrom, isChatWithSelf); const { replies, mediaUnread: isMediaUnread, postAuthor } = mtpMessage; const groupedId = mtpMessage.groupedId && String(mtpMessage.groupedId); const isInAlbum = Boolean(groupedId) && !(content.document || content.audio || content.sticker); const shouldHideKeyboardButtons = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide; + const isHideKeyboardSelective = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide + && mtpMessage.replyMarkup.selective; const isProtected = mtpMessage.noforwards || isInvoiceMedia; const isForwardingAllowed = !mtpMessage.noforwards; const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId); @@ -215,8 +217,10 @@ export function buildApiMessageWithChatId( isInAlbum, }), inlineButtons, - ...(keyboardButtons && { keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse }), - ...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons }), + ...(keyboardButtons && { + keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective, + }), + ...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons, isHideKeyboardSelective }), ...(mtpMessage.viaBotId && { viaBotId: buildApiPeerId(mtpMessage.viaBotId, 'user') }), ...(replies?.comments && { repliesThreadInfo: buildThreadInfo(replies, mtpMessage.id, chatId) }), ...(postAuthor && { postAuthorTitle: postAuthor }), @@ -1242,6 +1246,7 @@ function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: bool ...(replyMarkup instanceof GramJs.ReplyKeyboardMarkup && { keyboardPlaceholder: replyMarkup.placeholder, isKeyboardSingleUse: replyMarkup.singleUse, + isKeyboardSelective: replyMarkup.selective, }), }; } diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index c20fbe64a..6c02515aa 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -413,11 +413,13 @@ export interface ApiMessage { keyboardButtons?: ApiKeyboardButtons; keyboardPlaceholder?: string; isKeyboardSingleUse?: boolean; + isKeyboardSelective?: boolean; viaBotId?: string; repliesThreadInfo?: ApiThreadInfo; postAuthorTitle?: string; isScheduled?: boolean; shouldHideKeyboardButtons?: boolean; + isHideKeyboardSelective?: boolean; isFromScheduled?: boolean; isSilent?: boolean; seenByUserIds?: string[]; @@ -600,6 +602,7 @@ export type ApiKeyboardButtons = ApiKeyboardButton[][]; export type ApiReplyKeyboard = { keyboardPlaceholder?: string; isKeyboardSingleUse?: boolean; + isKeyboardSelective?: boolean; } & { [K in 'inlineButtons' | 'keyboardButtons']?: ApiKeyboardButtons; }; diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index d6c83a616..635817cf6 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -518,6 +518,8 @@ const MessageList: FC = ({ const isEmptyTopic = messageIds?.length === 1 && messagesById?.[messageIds[0]]?.content.action?.type === 'topicCreate'; + const isBotInfoEmpty = botInfo && !botInfo.description && !botInfo.gif && !botInfo.photo; + const className = buildClassName( 'MessageList custom-scroll', noAvatars && 'no-avatars', @@ -546,7 +548,7 @@ const MessageList: FC = ({ ) : botInfo ? (
{isLoadingBotInfo && {lang('Loading')}} - {!botInfo && !isLoadingBotInfo && {lang('NoMessages')}} + {isBotInfoEmpty && !isLoadingBotInfo && {lang('NoMessages')}} {botInfo && (
( const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined; const isChatWithBot = Boolean(chatBot); const isChatWithSelf = selectIsChatWithSelf(global, chatId); - const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId); + const messageWithActualBotKeyboard = selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId); const scheduledIds = selectScheduledIds(global, chatId, threadId); const { language, shouldSuggestStickers, shouldSuggestCustomEmoji } = global.settings.byKey; const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG]; diff --git a/src/global/actions/api/bots.ts b/src/global/actions/api/bots.ts index 191109ef0..b3680c85e 100644 --- a/src/global/actions/api/bots.ts +++ b/src/global/actions/api/bots.ts @@ -175,11 +175,10 @@ addActionHandler('clickBotInlineButton', (global, actions, payload): ActionRetur addActionHandler('sendBotCommand', (global, actions, payload): ActionReturnType => { const { command, chatId, tabId = getCurrentTabId() } = payload; - const { currentUserId } = global; const chat = chatId ? selectChat(global, chatId) : selectCurrentChat(global, tabId); const currentMessageList = selectCurrentMessageList(global, tabId); - if (!currentUserId || !chat || !currentMessageList) { + if (!chat || !currentMessageList) { return; } @@ -188,7 +187,7 @@ addActionHandler('sendBotCommand', (global, actions, payload): ActionReturnType actions.clearWebPagePreview({ tabId }); void sendBotCommand( - chat, currentUserId, command, selectReplyingToId(global, chat.id, threadId), selectSendAs(global, chat.id), + chat, threadId, command, selectReplyingToId(global, chat.id, threadId), selectSendAs(global, chat.id), ); }); @@ -209,7 +208,7 @@ addActionHandler('restartBot', async (global, actions, payload): Promise = global = getGlobal(); global = removeBlockedContact(global, bot.id); setGlobal(global); - void sendBotCommand(chat, currentUserId, '/start', undefined, selectSendAs(global, chatId)); + void sendBotCommand(chat, MAIN_THREAD_ID, '/start', undefined, selectSendAs(global, chatId)); }); addActionHandler('loadTopInlineBots', async (global): Promise => { @@ -927,10 +926,11 @@ async function searchInlineBot(global: T, { } async function sendBotCommand( - chat: ApiChat, currentUserId: string, command: string, replyingTo?: number, sendAs?: ApiChat | ApiUser, + chat: ApiChat, threadId = MAIN_THREAD_ID, command: string, replyingTo?: number, sendAs?: ApiChat | ApiUser, ) { await callApi('sendMessage', { chat, + replyingToTopId: threadId, text: command, replyingTo, sendAs, diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 2cf538579..bd2276959 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -15,7 +15,7 @@ import { GENERAL_TOPIC_ID, LOCAL_MESSAGE_MIN_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; import { - selectChat, selectChatBot, selectIsChatWithBot, selectIsChatWithSelf, + selectChat, selectChatBot, selectIsChatWithSelf, } from './chats'; import { selectIsCurrentUserPremium, selectIsUserOrChatContact, selectUser, selectUserStatus, @@ -921,7 +921,7 @@ export function selectSelectedMessagesCount( } export function selectNewestMessageWithBotKeyboardButtons( - global: T, chatId: string, + global: T, chatId: string, threadId = MAIN_THREAD_ID, ...[tabId = getCurrentTabId()]: TabArgs ): ApiMessage | undefined { const chat = selectChat(global, chatId); @@ -929,23 +929,15 @@ export function selectNewestMessageWithBotKeyboardButtons return undefined; } - if (!selectIsChatWithBot(global, chat)) { - return undefined; - } - const chatMessages = selectChatMessages(global, chatId); - const viewportIds = selectViewportIds(global, chatId, MAIN_THREAD_ID, tabId); + const viewportIds = selectViewportIds(global, chatId, threadId, tabId); if (!chatMessages || !viewportIds) { return undefined; } - const messageId = findLast(viewportIds, (id) => { - return !chatMessages[id].isOutgoing && Boolean(chatMessages[id].keyboardButtons); - }); + const messageId = findLast(viewportIds, (id) => selectShouldDisplayReplyKeyboard(global, chatMessages[id])); - const replyHideMessageId = findLast(viewportIds, (id) => { - return Boolean(chatMessages[id].shouldHideKeyboardButtons); - }); + const replyHideMessageId = findLast(viewportIds, (id) => selectShouldHideReplyKeyboard(global, chatMessages[id])); if (messageId && replyHideMessageId && replyHideMessageId > messageId) { return undefined; @@ -954,6 +946,46 @@ export function selectNewestMessageWithBotKeyboardButtons return messageId ? chatMessages[messageId] : undefined; } +function selectShouldHideReplyKeyboard(global: T, message: ApiMessage) { + const { + shouldHideKeyboardButtons, + isHideKeyboardSelective, + replyToMessageId, + isMentioned, + } = message; + if (!shouldHideKeyboardButtons) return false; + + if (isHideKeyboardSelective) { + if (isMentioned) return true; + if (!replyToMessageId) return false; + + const replyMessage = selectChatMessage(global, message.chatId, replyToMessageId); + return Boolean(replyMessage?.senderId === global.currentUserId); + } + return true; +} + +function selectShouldDisplayReplyKeyboard(global: T, message: ApiMessage) { + const { + keyboardButtons, + shouldHideKeyboardButtons, + isKeyboardSelective, + isMentioned, + replyToMessageId, + } = message; + if (!keyboardButtons || shouldHideKeyboardButtons) return false; + + if (isKeyboardSelective) { + if (isMentioned) return true; + if (!replyToMessageId) return false; + + const replyMessage = selectChatMessage(global, message.chatId, replyToMessageId); + return Boolean(replyMessage?.senderId === global.currentUserId); + } + + return true; +} + export function selectCanAutoLoadMedia(global: T, message: ApiMessage) { const chat = selectChat(global, message.chatId); if (!chat) {