From 66850dd73555e8f42cd1ec1c99b17f2a7422aca0 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Thu, 30 Jun 2022 16:14:33 +0200 Subject: [PATCH] Fix displaying forwarded messages Fix order of forward's caption --- src/components/chat/bubbles.ts | 103 +++--- src/components/chat/input.ts | 4 +- .../sidebarRight/tabs/sharedMedia.ts | 17 +- src/components/wrappers/album.ts | 17 +- src/helpers/object/getObjectKeysAndSort.ts | 2 +- src/helpers/searchListLoader.ts | 14 +- src/lib/appManagers/appMessagesManager.ts | 346 +++++++++--------- .../utils/messages/getAlbumText.ts | 23 ++ src/lib/rootScope.ts | 10 +- src/tests/splitString.test.ts | 63 ++++ 10 files changed, 337 insertions(+), 262 deletions(-) create mode 100644 src/lib/appManagers/utils/messages/getAlbumText.ts create mode 100644 src/tests/splitString.test.ts diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index c70924e2..ee862fab 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -36,7 +36,7 @@ import { BOT_START_PARAM, NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtprot import { FocusDirection, ScrollStartCallbackDimensions } from "../../helpers/fastSmoothScroll"; import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck"; import { fastRaf, fastRafPromise } from "../../helpers/schedulers"; -import deferredPromise, { CancellablePromise } from "../../helpers/cancellablePromise"; +import deferredPromise from "../../helpers/cancellablePromise"; import RepliesElement from "./replies"; import DEBUG from "../../config/debug"; import { SliceEnd } from "../../helpers/slicedArray"; @@ -49,7 +49,6 @@ import { getMiddleware } from "../../helpers/middleware"; import cancelEvent from "../../helpers/dom/cancelEvent"; import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent"; import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment"; -import positionElementByIndex from "../../helpers/dom/positionElementByIndex"; import reflowScrollableElement from "../../helpers/dom/reflowScrollableElement"; import replaceContent from "../../helpers/dom/replaceContent"; import setInnerHTML from "../../helpers/dom/setInnerHTML"; @@ -98,7 +97,6 @@ import getPeerId from "../../lib/appManagers/utils/peers/getPeerId"; import getServerMessageId from "../../lib/appManagers/utils/messageId/getServerMessageId"; import generateMessageId from "../../lib/appManagers/utils/messageId/generateMessageId"; import { AppManagers } from "../../lib/appManagers/managers"; -import filterAsync from "../../helpers/array/filterAsync"; import { Awaited } from "../../types"; import idleController from "../../helpers/idleController"; import overlayCounter from "../../helpers/overlayCounter"; @@ -106,10 +104,10 @@ import { cancelContextMenuOpening } from "../../helpers/dom/attachContextMenuLis import contextMenuController from "../../helpers/contextMenuController"; import { AckedResult } from "../../lib/mtproto/superMessagePort"; import middlewarePromise from "../../helpers/middlewarePromise"; -import findAndSplice from "../../helpers/array/findAndSplice"; import { EmoticonsDropdown } from "../emoticonsDropdown"; import indexOfAndSplice from "../../helpers/array/indexOfAndSplice"; import noop from "../../helpers/noop"; +import getAlbumText from "../../lib/appManagers/utils/messages/getAlbumText"; const USE_MEDIA_TAILS = false; const IGNORE_ACTIONS: Set = new Set([ @@ -283,11 +281,12 @@ export default class ChatBubbles { // * events // will call when sent for update pos - this.listenerSetter.add(rootScope)('history_update', async({storageKey, mid, message}) => { + this.listenerSetter.add(rootScope)('history_update', async({storageKey, sequential, message}) => { if(this.chat.messagesStorageKey !== storageKey || this.chat.type === 'scheduled') { return; } + const {mid} = message; const log = false ? this.log.bindPrefix('history_update-' + mid) : undefined; log && log('start'); @@ -317,16 +316,22 @@ export default class ChatBubbles { return; } - const group = item.group; - const newItem = this.bubbleGroups.createItem(bubble, message); - // newItem.mid = item.mid; - const _items = this.bubbleGroups.itemsArr.slice(); - indexOfAndSplice(_items, item); - const foundItem = this.bubbleGroups.findGroupSiblingByItem(newItem, _items); - if(group === foundItem?.group || (group === this.bubbleGroups.getLastGroup() && group.items.length === 1)/* && false */) { - log && log('item has correct position', item); - this.bubbleGroups.changeBubbleMid(bubble, mid); - return; + if(sequential) { + const group = item.group; + const newItem = this.bubbleGroups.createItem(bubble, message); + // newItem.mid = item.mid; + const _items = this.bubbleGroups.itemsArr.slice(); + indexOfAndSplice(_items, item); + const foundItem = this.bubbleGroups.findGroupSiblingByItem(newItem, _items); + if( + group === foundItem?.group + || (group === this.bubbleGroups.getLastGroup() && group.items.length === 1 && newItem.dateTimestamp === item.dateTimestamp) + || (this.peerId === rootScope.myId && sequential && newItem.dateTimestamp === item.dateTimestamp) + ) { + log && log('item has correct position', item); + this.bubbleGroups.changeBubbleMid(bubble, mid); + return; + } } // return; @@ -436,12 +441,12 @@ export default class ChatBubbles { let messages: (Message.message | Message.messageService)[], tempIds: number[]; const groupedId = (message as Message.message).grouped_id; if(groupedId) { - const mids = await this.managers.appMessagesManager.getMidsByMessage(message); + messages = await this.managers.appMessagesManager.getMessagesByAlbum(groupedId); + const mids = messages.map(({mid}) => mid); if(!mids.length || getMainMidForGrouped(mids) !== mid || bubbles[mid] !== _bubble) { return; } - messages = await Promise.all(mids.map((mid) => this.chat.getMessage(mid))); if(bubbles[mid] !== _bubble) { return; } @@ -835,13 +840,13 @@ export default class ChatBubbles { public constructPeerHelpers() { // will call when message is sent (only 1) - this.listenerSetter.add(rootScope)('history_append', async({storageKey, mid}) => { + this.listenerSetter.add(rootScope)('history_append', async({storageKey, message}) => { if(storageKey !== this.chat.messagesStorageKey) return; if(!this.scrollable.loadedAll.bottom) { this.chat.setMessageId(); } else { - this.renderNewMessagesByIds([mid], true); + this.renderNewMessage(message, true); } if(rootScope.settings.animationsEnabled) { @@ -852,10 +857,9 @@ export default class ChatBubbles { } }); - this.listenerSetter.add(rootScope)('history_multiappend', (msgIdsByPeer) => { - if(!(this.peerId in msgIdsByPeer)) return; - const msgIds = Array.from(msgIdsByPeer[this.peerId]).slice().sort((a, b) => b - a); - this.renderNewMessagesByIds(msgIds); + this.listenerSetter.add(rootScope)('history_multiappend', (message) => { + if(this.peerId !== message.peerId) return; + this.renderNewMessage(message); }); this.listenerSetter.add(rootScope)('history_delete', ({peerId, msgs}) => { @@ -1377,10 +1381,10 @@ export default class ChatBubbles { this.chat.topbar.setTitle((await this.managers.appMessagesManager.getScheduledMessagesStorage(this.peerId)).size); }; - this.listenerSetter.add(rootScope)('scheduled_new', ({peerId, mid}) => { - if(peerId !== this.peerId) return; + this.listenerSetter.add(rootScope)('scheduled_new', (message) => { + if(message.peerId !== this.peerId) return; - this.renderNewMessagesByIds([mid]); + this.renderNewMessage(message); onUpdate(); }); @@ -2162,8 +2166,8 @@ export default class ChatBubbles { }; } - private renderNewMessagesByIds(mids: number[], scrolledDown?: boolean) { - const promise = this._renderNewMessagesByIds(mids, scrolledDown); + private renderNewMessage(message: MyMessage, scrolledDown?: boolean) { + const promise = this._renderNewMessage(message, scrolledDown); this.renderNewPromises.add(promise); promise.catch(noop).finally(() => { this.renderNewPromises.delete(promise); @@ -2171,15 +2175,17 @@ export default class ChatBubbles { return promise; } - private async _renderNewMessagesByIds(mids: number[], scrolledDown?: boolean) { + private async _renderNewMessage(message: MyMessage, scrolledDown?: boolean) { if(!this.scrollable.loadedAll.bottom) { // seems search active or sliced //this.log('renderNewMessagesByIds: seems search is active, skipping render:', mids); const setPeerPromise = this.chat.setPeerPromise; if(setPeerPromise) { const middleware = this.getMiddleware(); - setPeerPromise.then(() => { + setPeerPromise.then(async() => { if(!middleware()) return; - this.renderNewMessagesByIds(mids); + const newMessage = await this.chat.getMessage(message.mid); + if(!middleware()) return; + this.renderNewMessage(newMessage); }); } @@ -2187,14 +2193,15 @@ export default class ChatBubbles { } if(this.chat.threadId) { - mids = await filterAsync(mids, async(mid) => { - const message = await this.chat.getMessage(mid); - const replyTo = message?.reply_to as MessageReplyHeader; - return replyTo && (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) === this.chat.threadId; - }); + const replyTo = message?.reply_to as MessageReplyHeader; + if(!(replyTo && (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) === this.chat.threadId)) { + return; + } } - mids = mids.filter((mid) => !this.bubbles[mid]); + if(this.bubbles[message.mid]) { + return; + } // ! should scroll even without new messages /* if(!mids.length) { return; @@ -2211,7 +2218,7 @@ export default class ChatBubbles { const middleware = this.getMiddleware(); const {isPaddingNeeded, unsetPadding} = this.setTopPadding(middleware); - const promise = this.performHistoryResult({history: mids}, false); + const promise = this.performHistoryResult({history: [message]}, false); if(scrolledDown) { promise.then(() => { if(!middleware()) return; @@ -2221,7 +2228,7 @@ export default class ChatBubbles { let bubble: HTMLElement; if(this.chat.type === 'scheduled') { - bubble = this.bubbles[Math.max(...mids)]; + bubble = this.bubbles[message.mid]; } const promise = bubble ? this.scrollToBubbleEnd(bubble) : this.scrollToEnd(); @@ -3373,10 +3380,12 @@ export default class ChatBubbles { const isMessage = message._ === 'message'; const groupedId = isMessage && message.grouped_id; let albumMids: number[], reactionsMessage: Message.message; + const albumMessages = groupedId ? await this.managers.appMessagesManager.getMessagesByAlbum(groupedId) : undefined; const albumMustBeRenderedFull = this.chat.type !== 'pinned'; + if(groupedId && albumMustBeRenderedFull) { // will render only last album's message - albumMids = await this.managers.appMessagesManager.getMidsByAlbum(groupedId); + albumMids = albumMessages.map((message) => message.mid); const mainMid = getMainMidForGrouped(albumMids); if(message.mid !== mainMid) { return; @@ -3384,7 +3393,7 @@ export default class ChatBubbles { } if(isMessage) { - reactionsMessage = groupedId ? await this.managers.appMessagesManager.getGroupsFirstMessage(message) : message; + reactionsMessage = groupedId ? albumMessages[0] : message; } // * can't use 'message.pFlags.out' here because this check will be used to define side of message (left-right) @@ -3470,7 +3479,7 @@ export default class ChatBubbles { !['video', 'gif'].includes(((messageMedia as MessageMedia.messageMediaDocument).document as MyDocument).type)) { // * just filter these cases for documents caption } else if(groupedId && albumMustBeRenderedFull) { - const t = await this.managers.appMessagesManager.getAlbumText(groupedId); + const t = getAlbumText(albumMessages); messageMessage = t.message; //totalEntities = t.entities; totalEntities = t.totalEntities; @@ -3745,8 +3754,8 @@ export default class ChatBubbles { if(albumMustBeRenderedFull && groupedId && albumMids.length !== 1) { bubble.classList.add('is-album', 'is-grouped'); - const promise = wrapAlbum({ - groupId: groupedId, + wrapAlbum({ + messages: albumMessages, attachmentDiv, middleware: this.getMiddleware(), isOut: our, @@ -3756,8 +3765,6 @@ export default class ChatBubbles { autoDownload: this.chat.autoDownload, }); - loadPromises.push(promise); - break; } @@ -4001,8 +4008,8 @@ export default class ChatBubbles { if(albumMustBeRenderedFull && groupedId && albumMids.length !== 1) { bubble.classList.add('is-album', 'is-grouped'); - await wrapAlbum({ - groupId: groupedId, + wrapAlbum({ + messages: albumMessages, attachmentDiv, middleware: this.getMiddleware(), isOut: our, diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index ce2ece31..1c291b80 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -2395,7 +2395,7 @@ export default class ChatInput { // * wait for sendText set messageId for invokeAfterMsg if(this.forwarding) { const forwarding = copy(this.forwarding); - setTimeout(() => { + // setTimeout(() => { for(const fromPeerId in forwarding) { this.managers.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), forwarding[fromPeerId], { ...sendingParams, @@ -2407,7 +2407,7 @@ export default class ChatInput { if(!value) { this.onMessageSent(); } - }, 0); + // }, 0); } // this.onMessageSent(); diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index 44d707a1..1355e654 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -21,6 +21,7 @@ import PopupPeer, { PopupPeerButtonCallbackCheckboxes, PopupPeerCheckboxOptions import ButtonCorner from "../../buttonCorner"; import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import PeerProfile from "../../peerProfile"; +import { Message } from "../../../layer"; const historiesStorage: { [peerId: PeerId]: Partial<{ @@ -156,10 +157,8 @@ export default class AppSharedMediaTab extends SliderSuperTab { } }); - this.listenerSetter.add(rootScope)('history_multiappend', (msgIdsByPeer) => { - for(const peerId in msgIdsByPeer) { - this.renderNewMessages(peerId.toPeerId(), Array.from(msgIdsByPeer[peerId])); - } + this.listenerSetter.add(rootScope)('history_multiappend', (message) => { + this.renderNewMessages(message); }); this.listenerSetter.add(rootScope)('history_delete', ({peerId, msgs}) => { @@ -168,7 +167,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { // Calls when message successfully sent and we have an id this.listenerSetter.add(rootScope)('message_sent', ({message}) => { - this.renderNewMessages(message.peerId, [message.mid]); + this.renderNewMessages(message); }); //this.container.prepend(this.closeBtn.parentElement); @@ -321,14 +320,12 @@ export default class AppSharedMediaTab extends SliderSuperTab { //console.log('construct shared media time:', performance.now() - perf); } - public async renderNewMessages(peerId: PeerId, mids: number[]) { + public async renderNewMessages(message: Message.message | Message.messageService) { if(this.init) return; // * not inited yet + const {peerId} = message; if(!historiesStorage[peerId]) return; - const messages = await Promise.all(mids.map((mid) => this.managers.appMessagesManager.getMessageByPeer(peerId, mid))); - - mids = mids.slice().reverse(); // ! because it will be ascend sorted array for(const mediaTab of this.searchSuper.mediaTabs) { const inputFilter = mediaTab.inputFilter; const history = historiesStorage[peerId][inputFilter]; @@ -336,7 +333,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { continue; } - const filtered = this.searchSuper.filterMessagesByType(messages, inputFilter).filter((message) => !history.find((m) => m.mid === message.mid && m.peerId === message.peerId)); + const filtered = this.searchSuper.filterMessagesByType([message], inputFilter).filter((message) => !history.find((m) => m.mid === message.mid && m.peerId === message.peerId)); if(filtered.length) { history.unshift(...filtered.map((message) => ({mid: message.mid, peerId: message.peerId}))); diff --git a/src/components/wrappers/album.ts b/src/components/wrappers/album.ts index 55bb2840..d03f9913 100644 --- a/src/components/wrappers/album.ts +++ b/src/components/wrappers/album.ts @@ -6,7 +6,7 @@ import { ChatAutoDownloadSettings } from "../../helpers/autoDownload"; import mediaSizes from "../../helpers/mediaSizes"; -import { PhotoSize } from "../../layer"; +import { Message, PhotoSize } from "../../layer"; import { AppManagers } from "../../lib/appManagers/managers"; import getMediaFromMessage from "../../lib/appManagers/utils/messages/getMediaFromMessage"; import choosePhotoSize from "../../lib/appManagers/utils/photos/choosePhotoSize"; @@ -17,8 +17,8 @@ import prepareAlbum from "../prepareAlbum"; import wrapPhoto from "./photo"; import wrapVideo from "./video"; -export default async function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut, chat, loadPromises, autoDownload, managers = rootScope.managers}: { - groupId: string, +export default function wrapAlbum({messages, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut, chat, loadPromises, autoDownload, managers = rootScope.managers}: { + messages: Message.message[], attachmentDiv: HTMLElement, middleware?: () => boolean, lazyLoadQueue?: LazyLoadQueue, @@ -32,8 +32,6 @@ export default async function wrapAlbum({groupId, attachmentDiv, middleware, upl const items: {size: PhotoSize.photoSize, media: any, message: any}[] = []; // !lowest msgID will be the FIRST in album - const storage = await managers.appMessagesManager.getMidsByAlbum(groupId); - const messages = await Promise.all(storage.map((mid) => chat.getMessage(mid))); for(const message of messages) { const media = getMediaFromMessage(message); @@ -63,8 +61,9 @@ export default async function wrapAlbum({groupId, attachmentDiv, middleware, upl div.dataset.peerId = '' + message.peerId; const mediaDiv = div.firstElementChild as HTMLElement; const isPhoto = media._ === 'photo'; + let thumbPromise: Promise; if(isPhoto) { - wrapPhoto({ + thumbPromise = wrapPhoto({ photo: media, message, container: mediaDiv, @@ -79,7 +78,7 @@ export default async function wrapAlbum({groupId, attachmentDiv, middleware, upl managers }); } else { - wrapVideo({ + thumbPromise = wrapVideo({ doc: message.media.document, container: mediaDiv, message, @@ -94,5 +93,9 @@ export default async function wrapAlbum({groupId, attachmentDiv, middleware, upl managers }); } + + if(thumbPromise && loadPromises) { + loadPromises.push(thumbPromise); + } }); } diff --git a/src/helpers/object/getObjectKeysAndSort.ts b/src/helpers/object/getObjectKeysAndSort.ts index 77c3d78a..64aeb901 100644 --- a/src/helpers/object/getObjectKeysAndSort.ts +++ b/src/helpers/object/getObjectKeysAndSort.ts @@ -1,4 +1,4 @@ -export default function getObjectKeysAndSort(object: {[key: string]: any}, sort: 'asc' | 'desc' = 'asc') { +export default function getObjectKeysAndSort(object: {[key: string]: any} | Map, sort: 'asc' | 'desc' = 'asc') { if(!object) return []; const ids = object instanceof Map ? [...object.keys()] : Object.keys(object).map((i) => +i); if(sort === 'asc') return ids.sort((a, b) => a - b); diff --git a/src/helpers/searchListLoader.ts b/src/helpers/searchListLoader.ts index 6bfa99d0..54b41f2a 100644 --- a/src/helpers/searchListLoader.ts +++ b/src/helpers/searchListLoader.ts @@ -114,9 +114,7 @@ export default class SearchListLoader; - }) => { + protected onHistoryMultiappend = async(message: Message.message | Message.messageService) => { if(this.searchContext.folderId !== undefined) { return; } @@ -126,13 +124,11 @@ export default class SearchListLoader a - b); - const filtered = await this.filterMids(sorted); + const filtered = await this.filterMids([message.mid]); const targets = (await Promise.all(filtered.map((message) => this.processItem(message)))).filter(Boolean); if(targets.length) { /* const {previous, current, next} = this; @@ -159,9 +155,7 @@ export default class SearchListLoader { - this.onHistoryMultiappend({ - [message.peerId]: new Set([message.mid]) - }); + this.onHistoryMultiappend(message); }; public setSearchContext(context: SearchSuperContext) { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 47762baf..794b615c 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -58,6 +58,8 @@ import type { Progress } from "./appDownloadManager"; import noop from "../../helpers/noop"; import appTabsManager from "./appTabsManager"; import MTProtoMessagePort from "../mtproto/mtprotoMessagePort"; +import getAlbumText from "./utils/messages/getAlbumText"; +import pause from "../../helpers/schedulers/pause"; //console.trace('include'); // TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках @@ -134,6 +136,11 @@ type PendingMessageDetails = { sequential?: boolean }; +const processAfter = (cb: () => void) => { + // setTimeout(cb, 0); + cb(); +}; + export class AppMessagesManager extends AppManager { private messagesStorageByPeerId: {[peerId: string]: MessagesStorage}; private groupedMessagesStorage: {[groupId: string]: MessagesStorage}; // will be used for albums @@ -186,8 +193,6 @@ export class AppMessagesManager extends AppManager { public migratedFromTo: {[peerId: PeerId]: PeerId} = {}; public migratedToFrom: {[peerId: PeerId]: PeerId} = {}; - private newMessagesHandleTimeout = 0; - private newMessagesToHandle: {[peerId: PeerId]: Set} = {}; private newDialogsHandlePromise: Promise; private newDialogsToHandle: {[peerId: PeerId]: Dialog} = {}; public newUpdatesAfterReloadToHandle: {[peerId: PeerId]: Set} = {}; @@ -474,37 +479,31 @@ export class AppMessagesManager extends AppManager { scheduleDate: number, silent: true, sendAsPeerId: PeerId, - }> = {}) { + }> = {}): Promise { if(!text.trim()) { return; } + options.entities ??= []; + //this.checkSendOptions(options); - if(options.threadId && !options.replyToMsgId) { options.replyToMsgId = options.threadId; } - + const config = await this.apiManager.getConfig(); const MAX_LENGTH = config.message_length_max; - if(text.length > MAX_LENGTH) { - const splitted = splitStringByLength(text, MAX_LENGTH); - text = splitted[0]; - - if(splitted.length > 1) { + const splitted = splitStringByLength(text, MAX_LENGTH); + text = splitted[0]; + if(splitted.length > 1) { + if(options.webPage?._ === 'webPage' && !text.includes(options.webPage.url)) { delete options.webPage; } - - for(let i = 1; i < splitted.length; ++i) { - setTimeout(() => { - this.sendText(peerId, splitted[i], options); - }, i); - } } peerId = this.appPeersManager.getPeerMigratedTo(peerId) || peerId; - - let entities = options.entities || []; + + let entities = options.entities; if(!options.viaBotId) { text = parseMarkdown(text, entities); //entities = mergeEntities(entities, parseEntities(text)); @@ -653,7 +652,12 @@ export class AppMessagesManager extends AppManager { sequential: true }); - return message.promise; + const promises: ReturnType[] = [message.promise]; + for(let i = 1; i < splitted.length; ++i) { + promises.push(this.sendText(peerId, splitted[i], options)); + } + + return Promise.all(promises).then(noop); } public sendFile(peerId: PeerId, file: File | Blob | MyDocument, options: Partial<{ @@ -684,6 +688,9 @@ export class AppMessagesManager extends AppManager { noSound: boolean, waveform: Uint8Array, + + // ! only for internal use + processAfter?: typeof processAfter }> = {}) { peerId = this.appPeersManager.getPeerMigratedTo(peerId) || peerId; @@ -1033,7 +1040,8 @@ export class AppMessagesManager extends AppManager { isGroupedItem: options.isGroupedItem, isScheduled: !!options.scheduleDate || undefined, threadId: options.threadId, - clearDraft: options.clearDraft + clearDraft: options.clearDraft, + processAfter: options.processAfter }); if(!options.isGroupedItem) { @@ -1128,6 +1136,11 @@ export class AppMessagesManager extends AppManager { const groupId = '' + ++this.groupedTempId; + const callbacks: Array<() => void> = []; + const processAfter = (cb: () => void) => { + callbacks.push(cb); + }; + const messages = files.map((file, idx) => { const details = options.sendFileDetails[idx]; const o: Parameters[2] = { @@ -1139,6 +1152,7 @@ export class AppMessagesManager extends AppManager { threadId: options.threadId, sendAsPeerId: options.sendAsPeerId, groupId, + processAfter, ...details }; @@ -1152,11 +1166,15 @@ export class AppMessagesManager extends AppManager { }); if(options.clearDraft) { - setTimeout(() => { + callbacks.push(() => { this.appDraftsManager.clearDraft(peerId, options.threadId); - }, 0); + }); } - + + callbacks.forEach((callback) => { + callback(); + }); + // * test pending //return; @@ -1451,18 +1469,19 @@ export class AppMessagesManager extends AppManager { isScheduled: true, threadId: number, clearDraft: true, - sequential: boolean + sequential: boolean, + processAfter?: (cb: () => void) => void }> = {}) { const messageId = message.id; const peerId = this.getMessagePeer(message); const storage = options.isScheduled ? this.getScheduledMessagesStorage(peerId) : this.getHistoryMessagesStorage(peerId); - + let callbacks: Array<() => void> = []; if(options.isScheduled) { //if(!options.isGroupedItem) { this.saveMessages([message], {storage, isScheduled: true, isOutgoing: true}); - setTimeout(() => { - this.rootScope.dispatchEvent('scheduled_new', {peerId, mid: messageId}); - }, 0); + callbacks.push(() => { + this.rootScope.dispatchEvent('scheduled_new', message); + }); } else { /* if(options.threadId && this.threadsStorage[peerId]) { delete this.threadsStorage[peerId][options.threadId]; @@ -1478,12 +1497,11 @@ export class AppMessagesManager extends AppManager { } } - //if(!options.isGroupedItem) { this.saveMessages([message], {storage, isOutgoing: true}); this.setDialogTopMessage(message); - setTimeout(() => { - this.rootScope.dispatchEvent('history_append', {storageKey: storage.key, peerId, mid: messageId}); - }, 0); + callbacks.push(() => { + this.rootScope.dispatchEvent('history_append', {storageKey: storage.key, message}); + }); } const pending: PendingMessageDetails = this.pendingByRandomId[message.random_id] = { @@ -1495,13 +1513,21 @@ export class AppMessagesManager extends AppManager { }; if(!options.isGroupedItem && message.send) { - setTimeout(() => { + callbacks.push(() => { if(options.clearDraft) { this.appDraftsManager.clearDraft(peerId, options.threadId); } message.send(); - }, 0/* 3000 */); + }); + } + + if(callbacks.length) { + (options.processAfter || processAfter)(() => { + for(const callback of callbacks) { + callback(); + } + }); } return pending; @@ -2280,51 +2306,47 @@ export class AppMessagesManager extends AppManager { return promise || this.reloadConversationsPromise; } - this.reloadConversationsPromise = new Promise((resolve, reject) => { - setTimeout(() => { - const inputDialogPeers: InputDialogPeer[] = []; - const promises: {[peerId: string]: typeof promise} = {}; - for(const [peerId, {inputDialogPeer, promise}] of this.reloadConversationsPeers) { - inputDialogPeers.push(inputDialogPeer); - promises[peerId] = promise; - } - - this.reloadConversationsPeers.clear(); - - const fullfillLeft = () => { - for(const peerId in promises) { - promises[peerId].resolve(undefined); - } - }; + this.reloadConversationsPromise = pause(0).then(() => { + const inputDialogPeers: InputDialogPeer[] = []; + const promises: {[peerId: string]: typeof promise} = {}; + for(const [peerId, {inputDialogPeer, promise}] of this.reloadConversationsPeers) { + inputDialogPeers.push(inputDialogPeer); + promises[peerId] = promise; + } - this.apiManager.invokeApi('messages.getPeerDialogs', {peers: inputDialogPeers}).then((result) => { - this.dialogsStorage.applyDialogs(result); + this.reloadConversationsPeers.clear(); - result.dialogs.forEach((dialog) => { - const peerId = dialog.peerId; - if(peerId) { - promises[peerId].resolve(dialog as Dialog); - delete promises[peerId]; - } - }); + const fullfillLeft = () => { + for(const peerId in promises) { + promises[peerId].resolve(undefined); + } + }; - // fullfillLeft(); - // resolve(); - }, (err) => { - // fullfillLeft(); - // resolve(); - // reject(err); - }).finally(() => { - fullfillLeft(); - resolve(); - - this.reloadConversationsPromise = null; + return this.apiManager.invokeApi('messages.getPeerDialogs', {peers: inputDialogPeers}).then((result) => { + this.dialogsStorage.applyDialogs(result); - if(this.reloadConversationsPeers.size) { - this.reloadConversation(); + result.dialogs.forEach((dialog) => { + const peerId = dialog.peerId; + if(peerId) { + promises[peerId].resolve(dialog as Dialog); + delete promises[peerId]; } }); - }, 0); + + // fullfillLeft(); + // resolve(); + }, (err) => { + // fullfillLeft(); + // resolve(); + // reject(err); + }).then(() => { + fullfillLeft(); + + this.reloadConversationsPromise = null; + if(this.reloadConversationsPeers.size) { + this.reloadConversation(); + } + }); }); return promise || this.reloadConversationsPromise; @@ -2506,24 +2528,7 @@ export class AppMessagesManager extends AppManager { public getAlbumText(grouped_id: string) { const group = this.groupedMessagesStorage[grouped_id]; - let foundMessages = 0, message: string, totalEntities: MessageEntity[], entities: MessageEntity[]; - for(const [mid, m] of group) { - assumeType(m); - if(m.message) { - if(++foundMessages > 1) break; - message = m.message; - totalEntities = m.totalEntities; - entities = m.entities; - } - } - - if(foundMessages > 1) { - message = undefined; - totalEntities = undefined; - entities = undefined; - } - - return {message, entities, totalEntities}; + return getAlbumText(Array.from(group.values()) as Message.message[]); } public getGroupsFirstMessage(message: Message.message) { @@ -2537,11 +2542,17 @@ export class AppMessagesManager extends AppManager { } } - return storage.get(minMid) as Message.message; + return this.getMessageFromStorage(storage, minMid) as Message.message; + } + + public getMidsByAlbum(groupedId: string, sort: 'asc' | 'desc' = 'asc') { + return getObjectKeysAndSort(this.groupedMessagesStorage[groupedId], sort); } - public getMidsByAlbum(grouped_id: string, sort: 'asc' | 'desc' = 'asc') { - return getObjectKeysAndSort(this.groupedMessagesStorage[grouped_id], sort); + public getMessagesByAlbum(groupedId: string) { + const mids = this.getMidsByAlbum(groupedId, 'asc'); + const storage = this.groupedMessagesStorage[groupedId]; + return mids.map((mid) => this.getMessageFromStorage(storage, mid) as Message.message); } public getMidsByMessage(message: Message) { @@ -3541,22 +3552,10 @@ export class AppMessagesManager extends AppManager { }); } - private handleNewMessage(peerId: PeerId, mid: number) { - (this.newMessagesToHandle[peerId] ??= new Set()).add(mid); - // if(!this.newMessagesHandleTimeout) { - // this.newMessagesHandleTimeout = ctx.setTimeout(this.handleNewMessages, 0); - // } - this.handleNewMessages(); + private handleNewMessage(message: MyMessage) { + this.rootScope.dispatchEvent('history_multiappend', message); } - private handleNewMessages = () => { - clearTimeout(this.newMessagesHandleTimeout); - this.newMessagesHandleTimeout = 0; - - this.rootScope.dispatchEvent('history_multiappend', this.newMessagesToHandle); - this.newMessagesToHandle = {}; - }; - private handleNewDialogs = () => { let newMaxSeenId = 0; const obj = this.newDialogsToHandle; @@ -3589,12 +3588,9 @@ export class AppMessagesManager extends AppManager { } if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise; - return this.newDialogsHandlePromise = new Promise((resolve) => { - setTimeout(() => { - resolve(); - this.newDialogsHandlePromise = undefined; - this.handleNewDialogs(); - }, 0); + return this.newDialogsHandlePromise = pause(0).then(() => { + this.newDialogsHandlePromise = undefined; + this.handleNewDialogs(); }); } @@ -4144,7 +4140,7 @@ export class AppMessagesManager extends AppManager { // commented to render the message if it's been sent faster than history_append came to main thread // if(!pendingMessage) { - this.handleNewMessage(peerId, message.mid); + this.handleNewMessage(message); // } if(isLocalThreadUpdate) { @@ -4749,7 +4745,7 @@ export class AppMessagesManager extends AppManager { } else { const pendingMessage = this.checkPendingMessage(message); if(!pendingMessage) { - this.rootScope.dispatchEvent('scheduled_new', {peerId, mid: message.mid}); + this.rootScope.dispatchEvent('scheduled_new', message as Message.message); } } }; @@ -4826,7 +4822,7 @@ export class AppMessagesManager extends AppManager { if(randomId) { const pendingData = this.pendingByRandomId[randomId]; if(pendingMessage = this.finalizePendingMessage(randomId, message)) { - this.rootScope.dispatchEvent('history_update', {storageKey: pendingData.storage.key, peerId: message.peerId, mid: message.mid, message, sequential: pendingData.sequential}); + this.rootScope.dispatchEvent('history_update', {storageKey: pendingData.storage.key, message, sequential: pendingData.sequential}); } delete this.pendingByMessageId[message.mid]; @@ -5175,7 +5171,7 @@ export class AppMessagesManager extends AppManager { } public getMessageWithReplies(message: Message.message) { - return this.filterMessages(message, message => !!(message as Message.message).replies)[0] as any; + return this.filterMessages(message, (message) => !!(message as Message.message).replies)[0] as any; } public getMessageWithCommentReplies(message: Message.message) { @@ -5211,7 +5207,7 @@ export class AppMessagesManager extends AppManager { // if there is no id - then request by first id because cannot request by id 0 with backLimit const historyResult = await this.getHistory(peerId, slice[0] ?? 1, 0, 50, threadId); for(let i = 0, length = historyResult.history.length; i < length; ++i) { - this.handleNewMessage(peerId, historyResult.history[i]); + this.handleNewMessage(this.getMessageByPeer(peerId, historyResult.history[i])); } return {isBottomEnd: historyStorage.history.slice.isEnd(SliceEnd.Bottom)}; @@ -5531,70 +5527,67 @@ export class AppMessagesManager extends AppManager { return this.fetchSingleMessagesPromise; } - return this.fetchSingleMessagesPromise = new Promise((resolve) => { - setTimeout(() => { - const requestPromises: Promise[] = []; - - for(const [peerId, map] of this.needSingleMessages) { - const mids = [...map.keys()]; - const msgIds: InputMessage[] = mids.map((mid) => { - return { - _: 'inputMessageID', - id: getServerMessageId(mid) - }; + return this.fetchSingleMessagesPromise = pause(0).then(() => { + const requestPromises: Promise[] = []; + + for(const [peerId, map] of this.needSingleMessages) { + const mids = [...map.keys()]; + const msgIds: InputMessage[] = mids.map((mid) => { + return { + _: 'inputMessageID', + id: getServerMessageId(mid) + }; + }); + + let promise: Promise; + if(peerId.isAnyChat() && this.appPeersManager.isChannel(peerId)) { + promise = this.apiManager.invokeApiSingle('channels.getMessages', { + channel: this.appChatsManager.getChannelInput(peerId.toChatId()), + id: msgIds }); - - let promise: Promise; - if(peerId.isAnyChat() && this.appPeersManager.isChannel(peerId)) { - promise = this.apiManager.invokeApiSingle('channels.getMessages', { - channel: this.appChatsManager.getChannelInput(peerId.toChatId()), - id: msgIds - }); - } else { - promise = this.apiManager.invokeApiSingle('messages.getMessages', { - id: msgIds - }); - } - - const after = promise.then((getMessagesResult) => { - assumeType>(getMessagesResult); + } else { + promise = this.apiManager.invokeApiSingle('messages.getMessages', { + id: msgIds + }); + } - this.appUsersManager.saveApiUsers(getMessagesResult.users); - this.appChatsManager.saveApiChats(getMessagesResult.chats); - const messages = this.saveMessages(getMessagesResult.messages); + const after = promise.then((getMessagesResult) => { + assumeType>(getMessagesResult); - for(let i = 0; i < messages.length; ++i) { - const message = messages[i]; - if(!message) { - continue; - } + this.appUsersManager.saveApiUsers(getMessagesResult.users); + this.appChatsManager.saveApiChats(getMessagesResult.chats); + const messages = this.saveMessages(getMessagesResult.messages); - const mid = generateMessageId(message.id); - const promise = map.get(mid); - promise.resolve(message); - map.delete(mid); + for(let i = 0; i < messages.length; ++i) { + const message = messages[i]; + if(!message) { + continue; } - if(map.size) { - for(const [mid, promise] of map) { - promise.resolve(this.generateEmptyMessage(mid)); - } + const mid = generateMessageId(message.id); + const promise = map.get(mid); + promise.resolve(message); + map.delete(mid); + } + + if(map.size) { + for(const [mid, promise] of map) { + promise.resolve(this.generateEmptyMessage(mid)); } - }).finally(() => { - this.rootScope.dispatchEvent('messages_downloaded', {peerId, mids}); - }); - - requestPromises.push(after); - } + } + }).finally(() => { + this.rootScope.dispatchEvent('messages_downloaded', {peerId, mids}); + }); + + requestPromises.push(after); + } - this.needSingleMessages.clear(); + this.needSingleMessages.clear(); - Promise.all(requestPromises).finally(() => { - this.fetchSingleMessagesPromise = null; - if(this.needSingleMessages.size) this.fetchSingleMessages(); - resolve(); - }); - }, 0); + return Promise.all(requestPromises).then(noop, noop).then(() => { + this.fetchSingleMessagesPromise = null; + if(this.needSingleMessages.size) this.fetchSingleMessages(); + }); }); } @@ -5745,11 +5738,6 @@ export class AppMessagesManager extends AppManager { } storage.delete(mid); - - const peerMessagesToHandle = this.newMessagesToHandle[peerId]; - if(peerMessagesToHandle && peerMessagesToHandle.has(mid)) { - peerMessagesToHandle.delete(mid); - } } if(history.albums) { diff --git a/src/lib/appManagers/utils/messages/getAlbumText.ts b/src/lib/appManagers/utils/messages/getAlbumText.ts new file mode 100644 index 00000000..c156c7be --- /dev/null +++ b/src/lib/appManagers/utils/messages/getAlbumText.ts @@ -0,0 +1,23 @@ +import assumeType from "../../../../helpers/assumeType"; +import { Message, MessageEntity } from "../../../../layer"; + +export default function getAlbumText(messages: Message.message[]) { + let foundMessages = 0, message: string, totalEntities: MessageEntity[], entities: MessageEntity[]; + for(const m of messages) { + assumeType(m); + if(m.message) { + if(++foundMessages > 1) break; + message = m.message; + totalEntities = m.totalEntities; + entities = m.entities; + } + } + + if(foundMessages > 1) { + message = undefined; + totalEntities = undefined; + entities = undefined; + } + + return {message, entities, totalEntities}; +} diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 393f5dc6..35ff9588 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -15,12 +15,12 @@ import type { GroupCallId } from "./appManagers/appGroupCallsManager"; import type { AppManagers } from "./appManagers/managers"; import type { State } from "../config/state"; import type { Progress } from "./appManagers/appDownloadManager"; +import type { CallId } from "./appManagers/appCallsManager"; import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config"; import EventListenerBase from "../helpers/eventListenerBase"; import { MOUNT_CLASS_TO } from "../config/debug"; import MTProtoMessagePort from "./mtproto/mtprotoMessagePort"; import { IS_WORKER } from "../helpers/context"; -import { CallId } from "./appManagers/appCallsManager"; export type BroadcastEvents = { 'chat_full_update': ChatId, @@ -58,10 +58,10 @@ export type BroadcastEvents = { // 'dialog_order': {dialog: Dialog, pos: number}, 'dialogs_multiupdate': {[peerId: PeerId]: Dialog}, - 'history_append': {storageKey: MessagesStorageKey, peerId: PeerId, mid: number}, - 'history_update': {storageKey: MessagesStorageKey, peerId: PeerId, mid: number, message: MyMessage, sequential?: boolean}, + 'history_append': {storageKey: MessagesStorageKey, message: Message.message}, + 'history_update': {storageKey: MessagesStorageKey, message: MyMessage, sequential?: boolean}, 'history_reply_markup': {peerId: PeerId}, - 'history_multiappend': AppMessagesManager['newMessagesToHandle'], + 'history_multiappend': MyMessage, 'history_delete': {peerId: PeerId, msgs: Set}, 'history_forbidden': PeerId, 'history_reload': PeerId, @@ -78,7 +78,7 @@ export type BroadcastEvents = { 'replies_updated': Message.message, - 'scheduled_new': {peerId: PeerId, mid: number}, + 'scheduled_new': Message.message, 'scheduled_delete': {peerId: PeerId, mids: number[]}, 'album_edit': {peerId: PeerId, groupId: string, deletedMids: number[], messages: Message.message[]}, diff --git a/src/tests/splitString.test.ts b/src/tests/splitString.test.ts new file mode 100644 index 00000000..0a600191 --- /dev/null +++ b/src/tests/splitString.test.ts @@ -0,0 +1,63 @@ +import pause from "../helpers/schedulers/pause"; +import splitStringByLength from "../helpers/string/splitStringByLength"; +import { MessageEntity } from "../layer"; + +const text = 'abc def ghi jkl mno pqr stu vwx yz'; +// const text = 'abcdefghijklmnopqrstuvwxyz'; +const entities: MessageEntity[] = []; +const maxLength = 3; +const parts = ['abc def ghi', 'jkl mno pqr', 'stu vwx yz']; + +async function split(str: string, maxLength: number, entities: MessageEntity[]) { + if(str.length <= maxLength) return [str]; + + const delimiter = ' '; + const out: {part: string, entities: MessageEntity[]}[] = []; + + let offset = 0; + while(str.length) { + const isEnd = (offset + maxLength) >= str.length; + const sliced = str.slice(offset, offset + maxLength); + if(!sliced.length) { + break; + } + + const delimiterIndex = !isEnd ? sliced.lastIndexOf(delimiter) : -1; + console.log(`sliced='${sliced}'`); + let good: string; + if(delimiterIndex !== -1) { + offset += delimiter.length; + good = sliced.slice(0, delimiterIndex); + } else { + good = sliced; + } + + if(!good.length) { + continue; + } + + offset += good.length; + out.push({part: good, entities: []}); + console.log(`'${good}'`); + + // await pause(1000); + } + + return out; +} + +describe('Split string', () => { + const splitted = split(text, maxLength, []); + + // console.log(parts, splitted); + + test('parts', () => { + expect(1).toEqual(1); + }); + + test('a', async() => { + console.log(await splitted); + }); + + // test('') +});