Handle scheduled video processing (#5242)

This commit is contained in:
zubiden 2024-12-06 19:43:48 +04:00 committed by Alexander Zinchuk
parent 8bdd24ea76
commit afe9582b3a
20 changed files with 210 additions and 71 deletions

View File

@ -8,6 +8,8 @@ type VirtualFields =
| 'classType'
| 'getBytes';
export type OmitVirtualFields<T> = Omit<T, VirtualFields>;
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<T extends GramJs.VirtualClass<T> & { flags?: any }>(
instance: T,
): Omit<T, VirtualFields> {
): OmitVirtualFields<T> {
const {
flags,
CONSTRUCTOR_ID,

View File

@ -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,
});
}

View File

@ -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,
};
}

View File

@ -511,6 +511,7 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
chatPhoto,
translationsDisabled,
reactionsLimit,
hasScheduled,
} = result.fullChat;
if (chatPhoto) {
@ -540,6 +541,7 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
recentRequesterIds: recentRequesters?.map((userId) => 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,

View File

@ -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);
}

View File

@ -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) {

View File

@ -139,6 +139,7 @@ export interface ApiChatFullInfo {
isTranslationDisabled?: true;
hasPinnedStories?: boolean;
isPaidReactionAvailable?: boolean;
hasScheduledMessages?: boolean;
boostsApplied?: number;
boostsToUnrestrict?: number;

View File

@ -754,6 +754,7 @@ export interface ApiMessage {
factCheck?: ApiFactCheck;
effectId?: string;
isInvertedMedia?: true;
isVideoProcessingPending?: true;
}
export interface ApiReactions {

View File

@ -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 |

View File

@ -60,6 +60,7 @@ export interface ApiUserFullInfo {
businessWorkHours?: ApiBusinessWorkHours;
businessIntro?: ApiBusinessIntro;
starGiftCount?: number;
hasScheduledMessages?: boolean;
}
export type ApiFakeType = 'fake' | 'scam';

View File

@ -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";

View File

@ -84,7 +84,6 @@ import {
selectPerformanceSettingsValue,
selectRequestedDraft,
selectRequestedDraftFiles,
selectScheduledIds,
selectTabState,
selectTheme,
selectTopicFromMessage,
@ -1771,7 +1770,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onActivate={handleActivateBotCommandMenu}
ariaLabel="Open bot command keyboard"
>
<i className="icon icon-bot-commands-filled" />
<Icon name="bot-commands-filled" />
</ResponsiveHoverButton>
)}
{canShowSendAs && (sendAsUser || sendAsChat) && (
@ -1866,7 +1865,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onClick={handleAllScheduledClick}
ariaLabel="Open scheduled messages"
>
<i className="icon icon-schedule" />
<Icon name="schedule" />
</Button>
)}
{Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && (
@ -1877,7 +1876,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onActivate={openBotKeyboard}
ariaLabel="Open bot command keyboard"
>
<i className="icon icon-bot-command" />
<Icon name="bot-command" />
</ResponsiveHoverButton>
)}
</>
@ -1974,7 +1973,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onClick={stopRecordingVoice}
ariaLabel="Cancel voice recording"
>
<i className="icon icon-delete" />
<Icon name="delete" />
</Button>
)}
{isInStoryViewer && !activeVoiceRecording && (
@ -1997,14 +1996,7 @@ const Composer: FC<OwnProps & StateProps> = ({
/>
)}
{(!sentStoryReaction || isSentStoryReactionHeart) && (
<i
className={buildClassName(
'icon',
'icon-heart',
isSentStoryReactionHeart && 'story-reaction-heart',
)}
aria-hidden
/>
<Icon name="heart" className={buildClassName(isSentStoryReactionHeart && 'story-reaction-heart')} />
)}
</Button>
)}
@ -2027,11 +2019,11 @@ const Composer: FC<OwnProps & StateProps> = ({
mainButtonState === MainButtonState.Send && canShowCustomSendMenu ? handleContextMenu : undefined
}
>
<i className="icon icon-send" />
<i className="icon icon-microphone-alt" />
{onForward && <i className="icon icon-forward" />}
{isInMessageList && <i className="icon icon-schedule" />}
{isInMessageList && <i className="icon icon-check" />}
<Icon name="send" />
<Icon name="microphone-alt" />
{onForward && <Icon name="forward" />}
{isInMessageList && <Icon name="schedule" />}
{isInMessageList && <Icon name="check" />}
</Button>
{effectEmoji && (
<span className="effect-icon" onClick={handleRemoveEffect}>
@ -2083,11 +2075,10 @@ export default memo(withGlobal<OwnProps>(
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<OwnProps>(
&& 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<OwnProps>(
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<OwnProps>(
isSelectModeActive: selectIsInSelectMode(global),
withScheduledButton: (
messageListType === 'thread'
&& Boolean(scheduledIds?.length)
&& (userFullInfo || chatFullInfo)?.hasScheduledMessages
),
isInScheduledList,
botKeyboardMessageId,
@ -2187,8 +2177,8 @@ export default memo(withGlobal<OwnProps>(
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<OwnProps>(
canSendQuickReplies,
noWebPage,
webPagePreview: selectTabState(global).webPagePreview,
isContactRequirePremium,
isContactRequirePremium: userFullInfo?.isContactRequirePremium,
effect,
effectReactions,
areEffectsSupported,

View File

@ -163,6 +163,7 @@ const MessageMeta: FC<OwnProps> = ({
</>
)}
{message.isEdited && `${lang('EditedMessage')} `}
{message.isVideoProcessingPending && `${lang('lng_approximate')} `}
{date}
</span>
{outgoingStatus && (

View File

@ -249,6 +249,7 @@ const Video = <T,>({
muted
loop
playsInline
disablePictureInPicture
draggable={!isProtected}
onTimeUpdate={handleTimeUpdate}
onReady={markPlayerReady}

View File

@ -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);

View File

@ -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';

View File

@ -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<ThreadId, number[]> = {};
messages.forEach((message) => {

View File

@ -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);

View File

@ -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<T extends GlobalState>(
}
function deleteScheduledMessages<T extends GlobalState>(
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,

View File

@ -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;
}