Messages: Handle 100+ messages forward (#5329)
This commit is contained in:
parent
ed761133bc
commit
5e4924189a
@ -228,14 +228,21 @@ export function buildApiUrlAuthResult(result: GramJs.TypeUrlAuthResult): ApiUrlA
|
||||
}
|
||||
|
||||
export function buildApiConfig(config: GramJs.Config): ApiConfig {
|
||||
const defaultReaction = config.reactionsDefault && buildApiReaction(config.reactionsDefault);
|
||||
const {
|
||||
testMode, expires, gifSearchUsername, chatSizeMax, autologinToken, reactionsDefault,
|
||||
messageLengthMax, editTimeLimit, forwardedCountMax,
|
||||
} = config;
|
||||
const defaultReaction = reactionsDefault && buildApiReaction(reactionsDefault);
|
||||
return {
|
||||
isTestServer: config.testMode,
|
||||
expiresAt: config.expires,
|
||||
gifSearchUsername: config.gifSearchUsername,
|
||||
isTestServer: testMode,
|
||||
expiresAt: expires,
|
||||
gifSearchUsername,
|
||||
defaultReaction,
|
||||
maxGroupSize: config.chatSizeMax,
|
||||
autologinToken: config.autologinToken,
|
||||
maxGroupSize: chatSizeMax,
|
||||
autologinToken,
|
||||
maxMessageLength: messageLengthMax,
|
||||
editTimeLimit,
|
||||
maxForwardedCount: forwardedCountMax,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1963,7 +1963,10 @@ function handleMultipleLocalMessagesUpdate(
|
||||
return true;
|
||||
});
|
||||
|
||||
handleGramJsUpdate(otherUpdates);
|
||||
// Illegal monkey patching. Easier than creating mock update object
|
||||
update.updates = otherUpdates;
|
||||
|
||||
handleGramJsUpdate(update);
|
||||
}
|
||||
|
||||
function handleLocalMessageUpdate(
|
||||
|
||||
@ -249,6 +249,9 @@ export interface ApiConfig {
|
||||
maxGroupSize: number;
|
||||
autologinToken?: string;
|
||||
isTestServer?: boolean;
|
||||
maxMessageLength: number;
|
||||
editTimeLimit: number;
|
||||
maxForwardedCount: number;
|
||||
}
|
||||
|
||||
export type ApiPeerColorSet = string[];
|
||||
|
||||
@ -44,6 +44,7 @@ import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import {
|
||||
BASE_EMOJI_KEYWORD_LANG,
|
||||
DEFAULT_MAX_MESSAGE_LENGTH,
|
||||
EDITABLE_INPUT_MODAL_ID,
|
||||
HEART_REACTION,
|
||||
MAX_UPLOAD_FILEPART_SIZE,
|
||||
@ -270,6 +271,7 @@ type StateProps =
|
||||
areEffectsSupported?: boolean;
|
||||
canPlayEffect?: boolean;
|
||||
shouldPlayEffect?: boolean;
|
||||
maxMessageLength: number;
|
||||
};
|
||||
|
||||
enum MainButtonState {
|
||||
@ -291,7 +293,6 @@ const SCREEN_WIDTH_TO_HIDE_PLACEHOLDER = 600; // px
|
||||
|
||||
const MOBILE_KEYBOARD_HIDE_DELAY_MS = 100;
|
||||
const SELECT_MODE_TRANSITION_MS = 200;
|
||||
const MESSAGE_MAX_LENGTH = 4096;
|
||||
const SENDING_ANIMATION_DURATION = 350;
|
||||
const MOUNT_ANIMATION_DURATION = 430;
|
||||
|
||||
@ -384,6 +385,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
areEffectsSupported,
|
||||
canPlayEffect,
|
||||
shouldPlayEffect,
|
||||
maxMessageLength,
|
||||
}) => {
|
||||
const {
|
||||
sendMessage,
|
||||
@ -871,7 +873,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const validateTextLength = useLastCallback((text: string, isAttachmentModal?: boolean) => {
|
||||
const maxLength = isAttachmentModal ? captionLimit : MESSAGE_MAX_LENGTH;
|
||||
const maxLength = isAttachmentModal ? captionLimit : maxMessageLength;
|
||||
if (text?.length > maxLength) {
|
||||
const extraLength = text.length - maxLength;
|
||||
showDialog({
|
||||
@ -2145,6 +2147,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
const effect = effectId ? global.availableEffectById[effectId] : undefined;
|
||||
const effectReactions = global.reactions.effectReactions;
|
||||
|
||||
const maxMessageLength = global.config?.maxMessageLength || DEFAULT_MAX_MESSAGE_LENGTH;
|
||||
|
||||
return {
|
||||
availableReactions: global.reactions.availableReactions,
|
||||
topReactions: type === 'story' ? global.reactions.topReactions : undefined,
|
||||
@ -2220,6 +2224,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
areEffectsSupported,
|
||||
canPlayEffect,
|
||||
shouldPlayEffect,
|
||||
maxMessageLength,
|
||||
};
|
||||
},
|
||||
)(Composer));
|
||||
|
||||
@ -16,8 +16,6 @@ import getFilesFromDataTransferItems from '../helpers/getFilesFromDataTransferIt
|
||||
|
||||
import useOldLang from '../../../../hooks/useOldLang';
|
||||
|
||||
const MAX_MESSAGE_LENGTH = 4096;
|
||||
|
||||
const TYPE_HTML = 'text/html';
|
||||
const DOCUMENT_TYPE_WORD = 'urn:schemas-microsoft-com:office:word';
|
||||
const NAMESPACE_PREFIX_WORD = 'xmlns:w';
|
||||
@ -49,7 +47,7 @@ const useClipboardPaste = (
|
||||
return;
|
||||
}
|
||||
|
||||
const pastedText = e.clipboardData.getData('text').substring(0, MAX_MESSAGE_LENGTH);
|
||||
const pastedText = e.clipboardData.getData('text');
|
||||
const html = e.clipboardData.getData('text/html');
|
||||
|
||||
let pastedFormattedText = html ? parseHtmlAsFormattedText(
|
||||
|
||||
@ -44,6 +44,7 @@ import {
|
||||
import {
|
||||
selectActiveDownloads,
|
||||
selectAllowedMessageActionsSlow,
|
||||
selectCanForwardMessage,
|
||||
selectCanPlayAnimatedEmojis,
|
||||
selectCanScheduleUntilOnline,
|
||||
selectCanTranslateMessage,
|
||||
@ -732,7 +733,6 @@ export default memo(withGlobal<OwnProps>(
|
||||
canDelete,
|
||||
canReport,
|
||||
canEdit,
|
||||
canForward,
|
||||
canFaveSticker,
|
||||
canUnfaveSticker,
|
||||
canCopy,
|
||||
@ -743,6 +743,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canRevote,
|
||||
canClosePoll,
|
||||
} = (threadId && selectAllowedMessageActionsSlow(global, message, threadId)) || {};
|
||||
const canForward = selectCanForwardMessage(global, message);
|
||||
|
||||
const userStatus = isPrivate ? selectUserStatus(global, chat.id) : undefined;
|
||||
const isOwn = isOwnMessage(message);
|
||||
|
||||
@ -364,7 +364,7 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
|
||||
dialogFiltersChats: [100, 200],
|
||||
dialogFilters: [10, 20],
|
||||
dialogFolderPinned: [5, 10],
|
||||
captionLength: [1024, 2048],
|
||||
captionLength: [1024, 4096],
|
||||
channels: [500, 1000],
|
||||
channelsPublic: [10, 20],
|
||||
aboutLength: [70, 140],
|
||||
@ -373,6 +373,7 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
|
||||
recommendedChannels: [10, 100],
|
||||
savedDialogsPinned: [5, 100],
|
||||
};
|
||||
export const DEFAULT_MAX_MESSAGE_LENGTH = 4096;
|
||||
|
||||
export const ONE_TIME_MEDIA_TTL_SECONDS = 2147483647;
|
||||
|
||||
|
||||
@ -62,6 +62,7 @@ import {
|
||||
isMessageLocal,
|
||||
isServiceNotificationMessage,
|
||||
isUserBot,
|
||||
splitMessagesForForwarding,
|
||||
} from '../../helpers';
|
||||
import { isApiPeerUser } from '../../helpers/peers';
|
||||
import {
|
||||
@ -99,6 +100,7 @@ import {
|
||||
} from '../../reducers';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import {
|
||||
selectCanForwardMessage,
|
||||
selectChat,
|
||||
selectChatFullInfo,
|
||||
selectChatLastMessageId,
|
||||
@ -1146,23 +1148,29 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
||||
const lastMessageId = selectChatLastMessageId(global, toChat.id);
|
||||
|
||||
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
||||
if (realMessages.length) {
|
||||
const forwardableRealMessages = realMessages.filter((message) => selectCanForwardMessage(global, message));
|
||||
if (forwardableRealMessages.length) {
|
||||
const messageBatches = global.config?.maxForwardedCount
|
||||
? splitMessagesForForwarding(forwardableRealMessages, global.config.maxForwardedCount)
|
||||
: [forwardableRealMessages];
|
||||
(async () => {
|
||||
await rafPromise(); // Wait one frame for any previous `sendMessage` to be processed
|
||||
callApi('forwardMessages', {
|
||||
fromChat,
|
||||
toChat,
|
||||
toThreadId,
|
||||
messages: realMessages,
|
||||
isSilent,
|
||||
scheduledAt,
|
||||
sendAs,
|
||||
withMyScore,
|
||||
noAuthors,
|
||||
noCaptions,
|
||||
isCurrentUserPremium,
|
||||
wasDrafted: Boolean(draft),
|
||||
lastMessageId,
|
||||
messageBatches.forEach((batch) => {
|
||||
callApi('forwardMessages', {
|
||||
fromChat,
|
||||
toChat,
|
||||
toThreadId,
|
||||
messages: batch,
|
||||
isSilent,
|
||||
scheduledAt,
|
||||
sendAs,
|
||||
withMyScore,
|
||||
noAuthors,
|
||||
noCaptions,
|
||||
isCurrentUserPremium,
|
||||
wasDrafted: Boolean(draft),
|
||||
lastMessageId,
|
||||
});
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
@ -50,8 +50,10 @@ import {
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import {
|
||||
selectAllowedMessageActionsSlow,
|
||||
selectCanForwardMessage,
|
||||
selectChat,
|
||||
selectChatLastMessageId,
|
||||
selectChatMessage,
|
||||
selectChatMessages,
|
||||
selectChatScheduledMessages,
|
||||
selectCurrentChat,
|
||||
@ -610,7 +612,16 @@ addActionHandler('openForwardMenuForSelectedMessages', (global, actions, payload
|
||||
|
||||
const { chatId: fromChatId, messageIds } = tabState.selectedMessages;
|
||||
|
||||
actions.openForwardMenu({ fromChatId, messageIds, tabId });
|
||||
const forwardableMessageIds = messageIds.filter((id) => {
|
||||
const message = selectChatMessage(global, fromChatId, id);
|
||||
return message && selectCanForwardMessage(global, message);
|
||||
});
|
||||
|
||||
if (!forwardableMessageIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
actions.openForwardMenu({ fromChatId, messageIds: forwardableMessageIds, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('cancelMediaDownload', (global, actions, payload): ActionReturnType => {
|
||||
|
||||
@ -408,3 +408,34 @@ export function getMessageLink(peer: ApiPeer, topicId?: ThreadId, messageId?: nu
|
||||
const messagePart = messageId ? `/${messageId}` : '';
|
||||
return `${TME_LINK_PREFIX}${chatPart}${topicPart}${messagePart}`;
|
||||
}
|
||||
|
||||
export function splitMessagesForForwarding(messages: ApiMessage[], limit: number): ApiMessage[][] {
|
||||
const result: ApiMessage[][] = [];
|
||||
let currentArr: ApiMessage[] = [];
|
||||
|
||||
// Group messages by `groupedId`
|
||||
messages.reduce<ApiMessage[][]>((acc, message) => {
|
||||
const lastGroup = acc[acc.length - 1];
|
||||
if (message.groupedId && lastGroup?.[0]?.groupedId === message.groupedId) {
|
||||
lastGroup.push(message);
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.push([message]);
|
||||
return acc;
|
||||
}, []).forEach((batch) => {
|
||||
// Fit them into `limit` size
|
||||
if (currentArr.length + batch.length > limit) {
|
||||
result.push(currentArr);
|
||||
currentArr = [];
|
||||
}
|
||||
|
||||
currentArr.push(...batch);
|
||||
});
|
||||
|
||||
if (currentArr.length) {
|
||||
result.push(currentArr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ import type {
|
||||
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import {
|
||||
ANONYMOUS_USER_ID, GENERAL_TOPIC_ID, SERVICE_NOTIFICATIONS_USER_ID,
|
||||
ANONYMOUS_USER_ID, API_GENERAL_ID_LIMIT, GENERAL_TOPIC_ID, SERVICE_NOTIFICATIONS_USER_ID,
|
||||
} from '../../config';
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { findLast } from '../../util/iteratees';
|
||||
@ -81,8 +81,6 @@ import {
|
||||
selectBot, selectIsCurrentUserPremium, selectUser, selectUserStatus,
|
||||
} from './users';
|
||||
|
||||
const MESSAGE_EDIT_ALLOWED_TIME = 172800; // 48 hours
|
||||
|
||||
export function selectCurrentMessageList<T extends GlobalState>(
|
||||
global: T,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
@ -607,6 +605,28 @@ export function selectCanReplyToMessage<T extends GlobalState>(global: T, messag
|
||||
return !messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics');
|
||||
}
|
||||
|
||||
export function selectCanForwardMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
||||
const isLocal = isMessageLocal(message);
|
||||
const isServiceNotification = isServiceNotificationMessage(message);
|
||||
const isAction = isActionMessage(message);
|
||||
const hasTtl = hasMessageTtl(message);
|
||||
const { content } = message;
|
||||
const story = content.storyData
|
||||
? selectPeerStory(global, content.storyData.peerId, content.storyData.id)
|
||||
: (content.webPage?.story
|
||||
? selectPeerStory(global, content.webPage.story.peerId, content.webPage.story.id)
|
||||
: undefined
|
||||
);
|
||||
const isChatProtected = selectIsChatProtected(global, message.chatId);
|
||||
const isStoryForwardForbidden = story && ('isDeleted' in story || ('noForwards' in story && story.noForwards));
|
||||
const canForward = (
|
||||
!isLocal && !isAction && !isChatProtected && !isStoryForwardForbidden
|
||||
&& (message.isForwardingAllowed || isServiceNotification) && !hasTtl
|
||||
);
|
||||
|
||||
return canForward;
|
||||
}
|
||||
|
||||
// This selector is slow and not to be used within lists (e.g. Message component)
|
||||
export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
global: T, message: ApiMessage, threadId: ThreadId,
|
||||
@ -651,7 +671,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
const isMessageEditable = (
|
||||
(
|
||||
canEditMessagesIndefinitely
|
||||
|| getServerTime() - message.date < MESSAGE_EDIT_ALLOWED_TIME
|
||||
|| getServerTime() - message.date < (global.config?.editTimeLimit || Infinity)
|
||||
) && !(
|
||||
content.sticker || content.contact || content.pollId || content.action
|
||||
|| (content.video?.isRound) || content.location || content.invoice || content.giveaway || content.giveawayResults
|
||||
@ -702,20 +722,6 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
|
||||
const canEdit = !isLocal && !isAction && isMessageEditable && hasMessageEditRight;
|
||||
|
||||
const story = content.storyData
|
||||
? selectPeerStory(global, content.storyData.peerId, content.storyData.id)
|
||||
: (content.webPage?.story
|
||||
? selectPeerStory(global, content.webPage.story.peerId, content.webPage.story.id)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const isChatProtected = selectIsChatProtected(global, message.chatId);
|
||||
const isStoryForwardForbidden = story && ('isDeleted' in story || ('noForwards' in story && story.noForwards));
|
||||
const canForward = (
|
||||
!isLocal && !isAction && !isChatProtected && !isStoryForwardForbidden
|
||||
&& (message.isForwardingAllowed || isServiceNotification) && !hasTtl
|
||||
);
|
||||
|
||||
const hasSticker = Boolean(message.content.sticker);
|
||||
const hasFavoriteSticker = hasSticker && selectIsStickerFavorite(global, message.content.sticker!);
|
||||
const canFaveSticker = !isAction && hasSticker && !hasFavoriteSticker;
|
||||
@ -743,7 +749,6 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
canReport,
|
||||
canDelete,
|
||||
canDeleteForAll,
|
||||
canForward,
|
||||
canFaveSticker,
|
||||
canUnfaveSticker,
|
||||
canCopy,
|
||||
@ -765,7 +770,6 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
canReport,
|
||||
canDelete,
|
||||
canDeleteForAll,
|
||||
canForward,
|
||||
canFaveSticker,
|
||||
canUnfaveSticker,
|
||||
canCopy,
|
||||
@ -790,6 +794,8 @@ export function selectCanDeleteSelectedMessages<T extends GlobalState>(
|
||||
return {};
|
||||
}
|
||||
|
||||
if (selectedMessageIds.length > API_GENERAL_ID_LIMIT) return {};
|
||||
|
||||
const messageActions = selectedMessageIds
|
||||
.map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId))
|
||||
.filter(Boolean);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user