From afe9582b3a60c1a000cb79a9f4ed867aeb4f9186 Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:43:48 +0400 Subject: [PATCH] Handle scheduled video processing (#5242) --- src/api/gramjs/apiBuilders/helpers.ts | 4 +- src/api/gramjs/apiBuilders/messages.ts | 2 + src/api/gramjs/apiBuilders/users.ts | 3 +- src/api/gramjs/methods/chats.ts | 4 + src/api/gramjs/methods/messages.ts | 75 +++++++++++----- src/api/gramjs/updates/mtpUpdateHandler.ts | 1 + src/api/types/chats.ts | 1 + src/api/types/messages.ts | 1 + src/api/types/updates.ts | 12 ++- src/api/types/users.ts | 1 + src/assets/localization/fallback.strings | 4 + src/components/common/Composer.tsx | 42 ++++----- src/components/middle/message/MessageMeta.tsx | 1 + src/components/middle/message/Video.tsx | 1 + .../middle/panes/HeaderPinnedMessage.tsx | 2 +- src/config.ts | 2 +- src/global/actions/api/messages.ts | 5 ++ src/global/actions/apiUpdaters/chats.ts | 28 +++--- src/global/actions/apiUpdaters/messages.ts | 88 +++++++++++++++++-- src/types/language.d.ts | 4 + 20 files changed, 210 insertions(+), 71 deletions(-) diff --git a/src/api/gramjs/apiBuilders/helpers.ts b/src/api/gramjs/apiBuilders/helpers.ts index f565684b1..ea95cc3d6 100644 --- a/src/api/gramjs/apiBuilders/helpers.ts +++ b/src/api/gramjs/apiBuilders/helpers.ts @@ -8,6 +8,8 @@ type VirtualFields = | 'classType' | 'getBytes'; +export type OmitVirtualFields = Omit; + export function bytesToDataUri(bytes: Buffer, shouldOmitPrefix = false, mimeType: string = 'image/jpeg') { const prefix = shouldOmitPrefix ? '' : `data:${mimeType};base64,`; @@ -16,7 +18,7 @@ export function bytesToDataUri(bytes: Buffer, shouldOmitPrefix = false, mimeType export function omitVirtualClassFields & { flags?: any }>( instance: T, -): Omit { +): OmitVirtualFields { const { flags, CONSTRUCTOR_ID, diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 4e135e02c..b8d532147 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -215,6 +215,7 @@ export function buildApiMessageWithChatId( const hasComments = mtpMessage.replies?.comments; const senderBoosts = mtpMessage.fromBoostsApplied; const factCheck = mtpMessage.factcheck && buildApiFactCheck(mtpMessage.factcheck); + const isVideoProcessingPending = mtpMessage.videoProcessingPending; const isInvertedMedia = mtpMessage.invertMedia; @@ -262,6 +263,7 @@ export function buildApiMessageWithChatId( factCheck, effectId: mtpMessage.effect?.toString(), isInvertedMedia, + isVideoProcessingPending, }); } diff --git a/src/api/gramjs/apiBuilders/users.ts b/src/api/gramjs/apiBuilders/users.ts index 31b104a6c..935a74089 100644 --- a/src/api/gramjs/apiBuilders/users.ts +++ b/src/api/gramjs/apiBuilders/users.ts @@ -19,7 +19,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse const { fullUser: { about, commonChatsCount, pinnedMsgId, botInfo, blocked, - profilePhoto, voiceMessagesForbidden, premiumGifts, + profilePhoto, voiceMessagesForbidden, premiumGifts, hasScheduled, fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable, contactRequirePremium, businessWorkHours, businessLocation, businessIntro, birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, @@ -51,6 +51,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse personalChannelMessageId: personalChannelMessage, areAdsEnabled: sponsoredEnabled, starGiftCount: stargiftsCount, + hasScheduledMessages: hasScheduled, }; } diff --git a/src/api/gramjs/methods/chats.ts b/src/api/gramjs/methods/chats.ts index 30bb58b2d..1995dcd2c 100644 --- a/src/api/gramjs/methods/chats.ts +++ b/src/api/gramjs/methods/chats.ts @@ -511,6 +511,7 @@ async function getFullChatInfo(chatId: string): Promise buildApiPeerId(userId, 'user')), isTranslationDisabled: translationsDisabled, isPreHistoryHidden: true, + hasScheduledMessages: hasScheduled, }, chats, userStatusesById, @@ -602,6 +604,7 @@ async function getFullChannelInfo( boostsUnrestrict, canViewRevenue: canViewMonetization, paidReactionsAvailable, + hasScheduled, } = result.fullChat; if (chatPhoto) { @@ -692,6 +695,7 @@ async function getFullChannelInfo( boostsApplied, boostsToUnrestrict: boostsUnrestrict, isPaidReactionAvailable: paidReactionsAvailable, + hasScheduledMessages: hasScheduled, }, chats, userStatusesById: statusesById, diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index 06440ba08..a41d223ea 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -45,7 +45,7 @@ import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnl import { fetchFile } from '../../../util/files'; import { compact, split } from '../../../util/iteratees'; import { getMessageKey } from '../../../util/keys/messageKey'; -import { getServerTimeOffset } from '../../../util/serverTime'; +import { getServerTime, getServerTimeOffset } from '../../../util/serverTime'; import { interpolateArray } from '../../../util/waveform'; import { buildApiChatFromPreview, @@ -1942,22 +1942,42 @@ function handleMultipleLocalMessagesUpdate( return; } - update.updates.forEach((u) => { - if (u instanceof GramJs.UpdateMessageID) { - const localMessage = localMessages[u.randomId.toString()]; - handleLocalMessageUpdate(localMessage, u); - } else { - handleGramJsUpdate(u); - } + const updateMessageIds = update.updates.filter((u): u is GramJs.UpdateMessageID => ( + u instanceof GramJs.UpdateMessageID + )); + + // Server can return `UpdateNewScheduledMessage` that we currently process as video that requires processing + updateMessageIds.forEach((updateMessageId) => { + const updateNewScheduledMessage = update.updates + .find((scheduledUpdate): scheduledUpdate is GramJs.UpdateNewScheduledMessage => { + if (!(scheduledUpdate instanceof GramJs.UpdateNewScheduledMessage)) return false; + return scheduledUpdate.message.id === updateMessageId.id; + }); + + const localMessage = localMessages[updateMessageId.randomId.toString()]; + handleLocalMessageUpdate(localMessage, updateMessageId, updateNewScheduledMessage); }); + + const otherUpdates = update.updates.filter((u) => { + if (u instanceof GramJs.UpdateMessageID) return false; + if (u instanceof GramJs.UpdateNewScheduledMessage) return false; + return true; + }); + + handleGramJsUpdate(otherUpdates); } -function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeUpdates) { +function handleLocalMessageUpdate( + localMessage: ApiMessage, update: GramJs.TypeUpdates, scheduledMessageUpdate?: GramJs.UpdateNewScheduledMessage, +) { let messageUpdate; if (update instanceof GramJs.UpdateShortSentMessage || update instanceof GramJs.UpdateMessageID) { messageUpdate = update; } else if ('updates' in update) { messageUpdate = update.updates.find((u): u is GramJs.UpdateMessageID => u instanceof GramJs.UpdateMessageID); + scheduledMessageUpdate = update.updates.find((u): u is GramJs.UpdateNewScheduledMessage => ( + u instanceof GramJs.UpdateNewScheduledMessage + )); } if (!messageUpdate) { @@ -1987,16 +2007,20 @@ function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeU processMessageAndUpdateThreadInfo(mtpMessage); } - // Edge case for "Send When Online" - const isSentBefore = 'date' in messageUpdate && messageUpdate.date * 1000 < Date.now() + getServerTimeOffset() * 1000; + const newScheduledMessage = scheduledMessageUpdate?.message && buildApiMessage(scheduledMessageUpdate.message); - sendApiUpdate({ - '@type': localMessage.isScheduled && !isSentBefore - ? 'updateScheduledMessageSendSucceeded' - : 'updateMessageSendSucceeded', - chatId: localMessage.chatId, - localId: localMessage.id, - message: { + // Edge case for "Send When Online" + const isSentBefore = 'date' in messageUpdate && messageUpdate.date < getServerTime(); + + if (newScheduledMessage?.isVideoProcessingPending) { + sendApiUpdate({ + '@type': 'updateVideoProcessingPending', + chatId: localMessage.chatId, + localId: localMessage.id, + newScheduledMessageId: newScheduledMessage?.id, + }); + } else { + const updatedMessage: ApiMessage = { ...localMessage, ...(newContent && { content: { @@ -2007,9 +2031,18 @@ function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeU id: messageUpdate.id, sendingState: undefined, ...('date' in messageUpdate && { date: messageUpdate.date }), - }, - poll, - }); + }; + + sendApiUpdate({ + '@type': localMessage.isScheduled && !isSentBefore + ? 'updateScheduledMessageSendSucceeded' + : 'updateMessageSendSucceeded', + chatId: localMessage.chatId, + localId: localMessage.id, + message: updatedMessage, + poll, + }); + } handleGramJsUpdate(update); } diff --git a/src/api/gramjs/updates/mtpUpdateHandler.ts b/src/api/gramjs/updates/mtpUpdateHandler.ts index 50ff28210..0d3a33644 100644 --- a/src/api/gramjs/updates/mtpUpdateHandler.ts +++ b/src/api/gramjs/updates/mtpUpdateHandler.ts @@ -384,6 +384,7 @@ export function updater(update: Update) { sendApiUpdate({ '@type': 'deleteScheduledMessages', ids: update.messages, + newIds: update.sentMessages, chatId: getApiChatIdFromMtpPeer(update.peer), }); } else if (update instanceof GramJs.UpdateDeleteChannelMessages) { diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index a3a4bdb55..d63b2c297 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -139,6 +139,7 @@ export interface ApiChatFullInfo { isTranslationDisabled?: true; hasPinnedStories?: boolean; isPaidReactionAvailable?: boolean; + hasScheduledMessages?: boolean; boostsApplied?: number; boostsToUnrestrict?: number; diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 4cdb09d35..707e26f13 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -754,6 +754,7 @@ export interface ApiMessage { factCheck?: ApiFactCheck; effectId?: string; isInvertedMedia?: true; + isVideoProcessingPending?: true; } export interface ApiReactions { diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index 3e9fdd701..6b7dc2df3 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -285,6 +285,13 @@ export type ApiUpdateMessageSendSucceeded = { poll?: ApiPoll; }; +export type ApiUpdateVideoProcessingPending = { + '@type': 'updateVideoProcessingPending'; + chatId: string; + localId: number; + newScheduledMessageId: number; +}; + export type ApiUpdateMessageSendFailed = { '@type': 'updateMessageSendFailed'; chatId: string; @@ -332,7 +339,8 @@ export type ApiUpdateDeleteMessages = { export type ApiUpdateDeleteScheduledMessages = { '@type': 'deleteScheduledMessages'; ids: number[]; - chatId?: string; + newIds?: number[]; + chatId: string; }; export type ApiUpdateDeleteHistory = { @@ -801,7 +809,7 @@ export type ApiUpdate = ( ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfo | ApiUpdateCommonBoxMessages | ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory | ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed | ApiUpdateServiceNotification | - ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | + ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateVideoProcessingPending | ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage | ApiUpdateError | ApiUpdateResetContacts | ApiUpdateStartEmojiInteraction | ApiUpdateFavoriteStickers | ApiUpdateStickerSet | ApiUpdateStickerSets | ApiUpdateStickerSetsOrder | diff --git a/src/api/types/users.ts b/src/api/types/users.ts index b8360099d..d430f4d1e 100644 --- a/src/api/types/users.ts +++ b/src/api/types/users.ts @@ -60,6 +60,7 @@ export interface ApiUserFullInfo { businessWorkHours?: ApiBusinessWorkHours; businessIntro?: ApiBusinessIntro; starGiftCount?: number; + hasScheduledMessages?: boolean; } export type ApiFakeType = 'fake' | 'scam'; diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index e91136270..295633a11 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1384,6 +1384,10 @@ "CloseMiniApps" = "Close Mini Apps"; "DoNotAskAgain" = "Don't ask again"; "PaymentInfoDone" = "Proceed to checkout"; +"VideoConversionTitle" = "Improving Video..."; +"VideoConversionText" = "The video will be published after it's optimized for the best viewing experience."; +"VideoConversionDone" = "Video published."; +"VideoConversionView" = "View"; "BotSuggestedStatusFor" = "Do you want to set this emoji status suggested by **{bot}** for **{duration}**?"; "BotSuggestedStatus" = "Do you want to set this emoji status suggested by **{bot}**?"; "BotSuggestedStatusTitle" = "Set Emoji Status"; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 4cf4bdb5a..054a0a6eb 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -84,7 +84,6 @@ import { selectPerformanceSettingsValue, selectRequestedDraft, selectRequestedDraftFiles, - selectScheduledIds, selectTabState, selectTheme, selectTopicFromMessage, @@ -1771,7 +1770,7 @@ const Composer: FC = ({ onActivate={handleActivateBotCommandMenu} ariaLabel="Open bot command keyboard" > - + )} {canShowSendAs && (sendAsUser || sendAsChat) && ( @@ -1866,7 +1865,7 @@ const Composer: FC = ({ onClick={handleAllScheduledClick} ariaLabel="Open scheduled messages" > - + )} {Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && ( @@ -1877,7 +1876,7 @@ const Composer: FC = ({ onActivate={openBotKeyboard} ariaLabel="Open bot command keyboard" > - + )} @@ -1974,7 +1973,7 @@ const Composer: FC = ({ onClick={stopRecordingVoice} ariaLabel="Cancel voice recording" > - + )} {isInStoryViewer && !activeVoiceRecording && ( @@ -1997,14 +1996,7 @@ const Composer: FC = ({ /> )} {(!sentStoryReaction || isSentStoryReactionHeart) && ( - + )} )} @@ -2027,11 +2019,11 @@ const Composer: FC = ({ mainButtonState === MainButtonState.Send && canShowCustomSendMenu ? handleContextMenu : undefined } > - - - {onForward && } - {isInMessageList && } - {isInMessageList && } + + + {onForward && } + {isInMessageList && } + {isInMessageList && } {effectEmoji && ( @@ -2083,11 +2075,10 @@ export default memo(withGlobal( const isChatWithBot = Boolean(chatBot); const isChatWithSelf = selectIsChatWithSelf(global, chatId); const isChatWithUser = isUserId(chatId); - const chatBotFullInfo = isChatWithBot ? selectUserFullInfo(global, chatBot.id) : undefined; + const userFullInfo = isChatWithUser ? selectUserFullInfo(global, chatId) : undefined; const chatFullInfo = !isChatWithUser ? selectChatFullInfo(global, chatId) : undefined; const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser) && selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId); - const scheduledIds = selectScheduledIds(global, chatId, threadId); const { language, shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder, } = global.settings.byKey; @@ -2117,7 +2108,7 @@ export default memo(withGlobal( && messageListType === currentMessageList?.type && !isStoryViewerOpen; const user = selectUser(global, chatId); - const canSendVoiceByPrivacy = (user && !selectUserFullInfo(global, user.id)?.noVoiceMessages) ?? true; + const canSendVoiceByPrivacy = (user && !userFullInfo?.noVoiceMessages) ?? true; const slowMode = chatFullInfo?.slowMode; const isCurrentUserPremium = selectIsCurrentUserPremium(global); @@ -2140,7 +2131,6 @@ export default memo(withGlobal( const noWebPage = selectNoWebPage(global, chatId, threadId); - const isContactRequirePremium = selectUserFullInfo(global, chatId)?.isContactRequirePremium; const areEffectsSupported = isChatWithUser && !isChatWithBot && !isInScheduledList && !isChatWithSelf && type !== 'story' && chatId !== SERVICE_NOTIFICATIONS_USER_ID; const canPlayEffect = selectPerformanceSettingsValue(global, 'stickerEffects'); @@ -2165,7 +2155,7 @@ export default memo(withGlobal( isSelectModeActive: selectIsInSelectMode(global), withScheduledButton: ( messageListType === 'thread' - && Boolean(scheduledIds?.length) + && (userFullInfo || chatFullInfo)?.hasScheduledMessages ), isInScheduledList, botKeyboardMessageId, @@ -2187,8 +2177,8 @@ export default memo(withGlobal( emojiKeywords: emojiKeywords?.keywords, inlineBots: tabState.inlineBots.byUsername, isInlineBotLoading: tabState.inlineBots.isLoading, - botCommands: chatBotFullInfo ? (chatBotFullInfo.botInfo?.commands || false) : undefined, - botMenuButton: chatBotFullInfo?.botInfo?.menuButton, + botCommands: userFullInfo ? (userFullInfo.botInfo?.commands || false) : undefined, + botMenuButton: userFullInfo?.botInfo?.menuButton, sendAsUser, sendAsChat, sendAsId, @@ -2218,7 +2208,7 @@ export default memo(withGlobal( canSendQuickReplies, noWebPage, webPagePreview: selectTabState(global).webPagePreview, - isContactRequirePremium, + isContactRequirePremium: userFullInfo?.isContactRequirePremium, effect, effectReactions, areEffectsSupported, diff --git a/src/components/middle/message/MessageMeta.tsx b/src/components/middle/message/MessageMeta.tsx index f811d84f9..4163e85cd 100644 --- a/src/components/middle/message/MessageMeta.tsx +++ b/src/components/middle/message/MessageMeta.tsx @@ -163,6 +163,7 @@ const MessageMeta: FC = ({ )} {message.isEdited && `${lang('EditedMessage')} `} + {message.isVideoProcessingPending && `${lang('lng_approximate')} `} {date} {outgoingStatus && ( diff --git a/src/components/middle/message/Video.tsx b/src/components/middle/message/Video.tsx index f614d1a81..9edb3a3d6 100644 --- a/src/components/middle/message/Video.tsx +++ b/src/components/middle/message/Video.tsx @@ -249,6 +249,7 @@ const Video = ({ muted loop playsInline + disablePictureInPicture draggable={!isProtected} onTimeUpdate={handleTimeUpdate} onReady={markPlayerReady} diff --git a/src/components/middle/panes/HeaderPinnedMessage.tsx b/src/components/middle/panes/HeaderPinnedMessage.tsx index f81dd8a9b..648b53102 100644 --- a/src/components/middle/panes/HeaderPinnedMessage.tsx +++ b/src/components/middle/panes/HeaderPinnedMessage.tsx @@ -132,7 +132,7 @@ const HeaderPinnedMessage = ({ if (isSynced && (threadId === MAIN_THREAD_ID || chat?.isForum)) { loadPinnedMessages({ chatId, threadId }); } - }, [chatId, threadId, isSynced, chat]); + }, [chatId, threadId, isSynced, chat?.isForum]); useEnsureMessage(chatId, pinnedMessageId, pinnedMessage); diff --git a/src/config.ts b/src/config.ts index 09feb1d00..3d193b18c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -51,7 +51,7 @@ export const MEDIA_PROGRESSIVE_CACHE_DISABLED = false; export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive'; export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg'; -export const LANG_CACHE_NAME = 'tt-lang-packs-v44'; +export const LANG_CACHE_NAME = 'tt-lang-packs-v45'; export const ASSET_CACHE_NAME = 'tt-assets'; export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500]; export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global'; diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index be746ba62..9d6bb0536 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -84,6 +84,7 @@ import { updateListedIds, updateMessageTranslation, updateOutlyingLists, + updatePeerFullInfo, updateQuickReplies, updateQuickReplyMessages, updateRequestedMessageTranslation, @@ -1213,6 +1214,10 @@ addActionHandler('loadScheduledHistory', async (global, actions, payload): Promi global = getGlobal(); global = updateScheduledMessages(global, chat.id, byId); global = replaceThreadParam(global, chat.id, MAIN_THREAD_ID, 'scheduledIds', ids); + if (!ids.length) { + global = updatePeerFullInfo(global, chat.id, { hasScheduledMessages: false }); + } + if (chat?.isForum) { const scheduledPerThread: Record = {}; messages.forEach((message) => { diff --git a/src/global/actions/apiUpdaters/chats.ts b/src/global/actions/apiUpdaters/chats.ts index a14489b3b..355c8de8d 100644 --- a/src/global/actions/apiUpdaters/chats.ts +++ b/src/global/actions/apiUpdaters/chats.ts @@ -162,6 +162,8 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { return undefined; } + const isLocal = isLocalMessageId(message.id!); + const chat = selectChat(global, update.chatId); if (!chat) { return undefined; @@ -169,19 +171,21 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { const hasMention = Boolean(update.message.id && update.message.hasUnreadMention); - global = updateChat(global, update.chatId, { - unreadCount: chat.unreadCount ? chat.unreadCount + 1 : 1, - }); - - if (hasMention) { - global = addUnreadMentions(global, update.chatId, chat, [update.message.id!], true); - } - - const topic = chat.isForum ? selectTopicFromMessage(global, message as ApiMessage) : undefined; - if (topic) { - global = updateTopic(global, update.chatId, topic.id, { - unreadCount: topic.unreadCount ? topic.unreadCount + 1 : 1, + if (!isLocal) { + global = updateChat(global, update.chatId, { + unreadCount: chat.unreadCount ? chat.unreadCount + 1 : 1, }); + + if (hasMention) { + global = addUnreadMentions(global, update.chatId, chat, [update.message.id!], true); + } + + const topic = chat.isForum ? selectTopicFromMessage(global, message as ApiMessage) : undefined; + if (topic) { + global = updateTopic(global, update.chatId, topic.id, { + unreadCount: topic.unreadCount ? topic.unreadCount + 1 : 1, + }); + } } setGlobal(global); diff --git a/src/global/actions/apiUpdaters/messages.ts b/src/global/actions/apiUpdaters/messages.ts index cb2df236a..a2f3c15ec 100644 --- a/src/global/actions/apiUpdaters/messages.ts +++ b/src/global/actions/apiUpdaters/messages.ts @@ -43,6 +43,7 @@ import { updateChatMessage, updateListedIds, updateMessageTranslations, + updatePeerFullInfo, updatePoll, updatePollVote, updateQuickReplies, @@ -87,6 +88,8 @@ import { const ANIMATION_DELAY = 350; const SNAP_ANIMATION_DELAY = 1000; +const VIDEO_PROCESSING_NOTIFICATION_DELAY = 1000; +let lastVideoProcessingNotificationTime = 0; addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { switch (update['@type']) { @@ -233,6 +236,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { global = updatePoll(global, poll.id, poll); } + global = updatePeerFullInfo(global, chatId, { + hasScheduledMessages: true, + }); + setGlobal(global); break; @@ -335,6 +342,49 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { break; } + case 'updateVideoProcessingPending': { + const { + chatId, localId, newScheduledMessageId, + } = update; + + global = deleteChatMessages(global, chatId, [localId]); + global = updatePeerFullInfo(global, chatId, { + hasScheduledMessages: true, + }); + + setGlobal(global); + + Object.values(global.byTabId).forEach(({ id: tabId }) => { + const currentMessageList = selectCurrentMessageList(global, tabId); + if (currentMessageList?.chatId !== chatId) return; + + const now = Date.now(); + if (now - lastVideoProcessingNotificationTime < VIDEO_PROCESSING_NOTIFICATION_DELAY) { + return; + } + lastVideoProcessingNotificationTime = now; + + actions.showNotification({ + message: { + key: 'VideoConversionText', + }, + title: { + key: 'VideoConversionTitle', + }, + tabId, + }); + + actions.focusMessage({ + chatId, + messageId: newScheduledMessageId, + messageListType: 'scheduled', + tabId, + }); + }); + + break; + } + case 'updateMessageSendSucceeded': { const { chatId, localId, message, poll, @@ -524,7 +574,37 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } case 'deleteScheduledMessages': { - const { ids, chatId } = update; + const { ids, newIds, chatId } = update; + + const hadVideoProcessing = ids?.some((id) => ( + selectScheduledMessage(global, chatId, id)?.isVideoProcessingPending + )); + const processedVideoId = newIds?.find((id) => { + const message = selectChatMessage(global, chatId, id); + return message?.content.video; + }); + + if (hadVideoProcessing && processedVideoId) { + Object.values(global.byTabId).forEach(({ id: tabId }) => { + actions.showNotification({ + message: { + key: 'VideoConversionDone', + }, + actionText: { + key: 'VideoConversionView', + }, + action: { + action: 'focusMessage', + payload: { + chatId, + messageId: processedVideoId, + tabId, + }, + }, + tabId, + }); + }); + } deleteScheduledMessages(chatId, ids, actions, global); break; @@ -1150,12 +1230,8 @@ export function deleteMessages( } function deleteScheduledMessages( - chatId: string | undefined, ids: number[], actions: RequiredGlobalActions, global: T, + chatId: string, ids: number[], actions: RequiredGlobalActions, global: T, ) { - if (!chatId) { - return; - } - ids.forEach((id) => { global = updateScheduledMessage(global, chatId, id, { isDeleting: true, diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 005d3b56a..76c05328f 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1153,6 +1153,10 @@ export interface LangPair { 'CloseMiniApps': undefined; 'DoNotAskAgain': undefined; 'PaymentInfoDone': undefined; + 'VideoConversionTitle': undefined; + 'VideoConversionText': undefined; + 'VideoConversionDone': undefined; + 'VideoConversionView': undefined; 'BotSuggestedStatusTitle': undefined; 'BotSuggestedStatusUpdated': undefined; }