From 3047285307bbc413b6c21bd4c93041b827116f44 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 19 Dec 2020 03:07:24 +0200 Subject: [PATCH] Refactored message ids again --- src/components/appMediaPlaybackController.ts | 40 +++- src/components/audio.ts | 31 +-- src/components/chat/bubbleGroups.ts | 51 +++- src/components/chat/bubbles.ts | 85 +++---- src/components/popups/newMedia.ts | 25 +- .../sidebarRight/tabs/sharedMedia.ts | 6 +- src/components/wrappers.ts | 39 +-- src/lib/appManagers/appImManager.ts | 6 +- src/lib/appManagers/appMessagesManager.ts | 226 +++++++++++++----- src/lib/appManagers/appPollsManager.ts | 2 +- src/lib/mtproto/apiFileManager.ts | 3 +- src/lib/richtextprocessor.ts | 2 +- 12 files changed, 353 insertions(+), 163 deletions(-) diff --git a/src/components/appMediaPlaybackController.ts b/src/components/appMediaPlaybackController.ts index 01a4717e..62b700ed 100644 --- a/src/components/appMediaPlaybackController.ts +++ b/src/components/appMediaPlaybackController.ts @@ -17,13 +17,22 @@ type MediaType = 'voice' | 'audio' | 'round'; class AppMediaPlaybackController { private container: HTMLElement; - private media: {[mid: string]: HTMLMediaElement} = {}; + private media: { + [peerId: string]: { + [mid: string]: HTMLMediaElement + } + } = {}; private playingMedia: HTMLMediaElement; - private waitingMediaForLoad: {[mid: string]: CancellablePromise} = {}; + private waitingMediaForLoad: { + [peerId: string]: { + [mid: string]: CancellablePromise + } + } = {}; public willBePlayedMedia: HTMLMediaElement; + private currentPeerId: number; private prevMid: number; private nextMid: number; @@ -35,7 +44,8 @@ class AppMediaPlaybackController { } public addMedia(peerId: number, doc: MyDocument, mid: number, autoload = true): HTMLMediaElement { - if(this.media[mid]) return this.media[mid]; + const storage = this.media[peerId] ?? (this.media[peerId] = {}); + if(storage[mid]) return storage[mid]; const media = document.createElement(doc.type == 'round' ? 'video' : 'audio'); //const source = document.createElement('source'); @@ -55,6 +65,8 @@ class AppMediaPlaybackController { this.container.append(media); media.addEventListener('playing', () => { + this.currentPeerId = peerId; + if(this.playingMedia != media) { if(this.playingMedia && !this.playingMedia.paused) { this.playingMedia.pause(); @@ -76,8 +88,8 @@ class AppMediaPlaybackController { const onError = (e: Event) => { if(this.nextMid == mid) { this.loadSiblingsMedia(peerId, doc.type as MediaType, mid).then(() => { - if(this.nextMid && this.media[this.nextMid]) { - this.media[this.nextMid].play(); + if(this.nextMid && storage[this.nextMid]) { + storage[this.nextMid].play(); } }); } @@ -89,7 +101,8 @@ class AppMediaPlaybackController { if(autoload) { deferred.resolve(); } else { - this.waitingMediaForLoad[mid] = deferred; + const waitingStorage = this.waitingMediaForLoad[peerId] ?? (this.waitingMediaForLoad[peerId] = {}); + waitingStorage[mid] = deferred; } // если что - загрузит voice или round заранее, так правильнее @@ -105,7 +118,7 @@ class AppMediaPlaybackController { media.src = doc.url; }, onError); - return this.media[mid] = media; + return storage[mid] = media; } // safari подгрузит последний чанк и песня включится, @@ -136,11 +149,12 @@ class AppMediaPlaybackController { }/* , {once: true} */); } - public resolveWaitingForLoadMedia(mid: number) { - const promise = this.waitingMediaForLoad[mid]; + public resolveWaitingForLoadMedia(peerId: number, mid: number) { + const storage = this.waitingMediaForLoad[peerId] ?? (this.waitingMediaForLoad[peerId] = {}); + const promise = storage[mid]; if(promise) { promise.resolve(); - delete this.waitingMediaForLoad[mid]; + delete storage[mid]; } } @@ -167,13 +181,13 @@ class AppMediaPlaybackController { //console.log('on media end'); if(this.nextMid) { - const media = this.media[this.nextMid]; + const media = this.media[this.currentPeerId][this.nextMid]; /* if(isSafari) { media.autoplay = true; } */ - this.resolveWaitingForLoadMedia(this.nextMid); + this.resolveWaitingForLoadMedia(this.currentPeerId, this.nextMid); setTimeout(() => { media.play()//.catch(() => {}); @@ -189,7 +203,7 @@ class AppMediaPlaybackController { //_: type == 'audio' ? 'inputMessagesFilterMusic' : (type == 'round' ? 'inputMessagesFilterRoundVideo' : 'inputMessagesFilterVoice') _: type == 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice' }, mid, 3, 0, 2).then(value => { - if(this.playingMedia != media) { + if(this.playingMedia !== media) { return; } diff --git a/src/components/audio.ts b/src/components/audio.ts index 64be2340..61ccae54 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -61,10 +61,11 @@ export function decodeWaveform(waveform: Uint8Array | number[]) { return result; } -function wrapVoiceMessage(peerId: number, doc: MyDocument, audioEl: AudioElement, mid: number) { +function wrapVoiceMessage(audioEl: AudioElement) { audioEl.classList.add('is-voice'); - const message = appMessagesManager.getMessageByPeer(peerId, mid); + const message = audioEl.message; + const doc = message.media.document as MyDocument; const isOut = message.fromId == rootScope.myId && message.peerId != rootScope.myId; let isUnread = message && message.pFlags.media_unread; if(isUnread) { @@ -171,7 +172,7 @@ function wrapVoiceMessage(peerId: number, doc: MyDocument, audioEl: AudioElement audioEl.addAudioListener('playing', () => { if(isUnread && !isOut && audioEl.classList.contains('is-unread')) { audioEl.classList.remove('is-unread'); - appMessagesManager.readMessages(peerId, [mid]); + appMessagesManager.readMessages(audioEl.message.peerId, [audioEl.message.mid]); isUnread = false; } @@ -249,9 +250,10 @@ function wrapVoiceMessage(peerId: number, doc: MyDocument, audioEl: AudioElement return onLoad; } -function wrapAudio(doc: MyDocument, audioEl: AudioElement) { - const withTime = !!+audioEl.getAttribute('with-time'); +function wrapAudio(audioEl: AudioElement) { + const withTime = audioEl.withTime; + const doc = audioEl.message.media.document; const title = doc.audioTitle || doc.file_name; let subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : ''; @@ -314,6 +316,8 @@ function wrapAudio(doc: MyDocument, audioEl: AudioElement) { export default class AudioElement extends HTMLElement { public audio: HTMLAudioElement; public preloader: ProgressivePreloader; + public message: any; + public withTime = false; private attachedHandlers: {[name: string]: any[]} = {}; private onTypeDisconnect: () => void; @@ -329,11 +333,8 @@ export default class AudioElement extends HTMLElement { this.classList.add('audio'); - const peerId = +this.getAttribute('peer-id'); - const mid = +this.getAttribute('message-id'); - const docId = this.getAttribute('doc-id'); - const doc = appDocsManager.getDoc(docId); - const uploading = +doc.id < 0; + const doc = this.message.media.document; + const uploading = this.message.pFlags.is_outgoing; const durationStr = String(doc.duration | 0).toHHMMSS(true); @@ -352,13 +353,13 @@ export default class AudioElement extends HTMLElement { this.append(downloadDiv); } - const onTypeLoad = doc.type == 'voice' ? wrapVoiceMessage(peerId, doc, this, mid) : wrapAudio(doc, this); + const onTypeLoad = doc.type == 'voice' ? wrapVoiceMessage(this) : wrapAudio(this); const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement; audioTimeDiv.innerHTML = durationStr; const onLoad = (autoload = true) => { - const audio = this.audio = appMediaPlaybackController.addMedia(peerId, doc, mid, autoload); + const audio = this.audio = appMediaPlaybackController.addMedia(this.message.peerId, this.message.media.document, this.message.mid, autoload); this.onTypeDisconnect = onTypeLoad(); @@ -449,7 +450,7 @@ export default class AudioElement extends HTMLElement { const r = (e: Event) => { //onLoad(); //cancelEvent(e); - appMediaPlaybackController.resolveWaitingForLoadMedia(mid); + appMediaPlaybackController.resolveWaitingForLoadMedia(this.message.peerId, this.message.mid); appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio @@ -465,7 +466,7 @@ export default class AudioElement extends HTMLElement { preloader.attach(downloadDiv); this.append(downloadDiv); - new Promise((resolve) => { + new Promise((resolve) => { if(this.audio.readyState >= 2) resolve(); else this.addAudioListener('canplay', resolve); }).then(() => { @@ -473,7 +474,7 @@ export default class AudioElement extends HTMLElement { //setTimeout(() => { // release loaded audio - if(appMediaPlaybackController.willBePlayedMedia == this.audio) { + if(appMediaPlaybackController.willBePlayedMedia === this.audio) { this.audio.play(); appMediaPlaybackController.willBePlayedMedia = null; } diff --git a/src/components/chat/bubbleGroups.ts b/src/components/chat/bubbleGroups.ts index f4535315..f66f692f 100644 --- a/src/components/chat/bubbleGroups.ts +++ b/src/components/chat/bubbleGroups.ts @@ -1,6 +1,7 @@ import rootScope from "../../lib/rootScope"; import { generatePathData } from "../../helpers/dom"; import { MyMessage } from "../../lib/appManagers/appMessagesManager"; +import Chat from "./chat"; type Group = {bubble: HTMLDivElement, mid: number, timestamp: number}[]; type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: Group}; @@ -10,6 +11,10 @@ export default class BubbleGroups { //updateRAFs: Map = new Map(); private newGroupDiff = 121; // * 121 in scheduled messages + constructor(private chat: Chat) { + + } + removeBubble(bubble: HTMLDivElement, mid: number) { const details = this.bubbles.findAndSplice(g => g.mid === mid); if(details && details.group.length) { @@ -38,10 +43,37 @@ export default class BubbleGroups { const insertObject = {bubble, mid, timestamp}; if(this.bubbles.length) { - const foundBubble = this.bubbles.find(bubble => { + let foundBubble: BubbleGroup; + let foundAtIndex = -1; + for(let i = 0; i < this.bubbles.length; ++i) { + const bubble = this.bubbles[i]; + const diff = Math.abs(bubble.timestamp - timestamp); + const good = bubble.fromId === fromId && diff <= this.newGroupDiff; + + if(good) { + foundAtIndex = i; + + if(this.chat.type === 'scheduled') { + break; + } + } else { + foundAtIndex = -1; + } + + if(this.chat.type !== 'scheduled') { + if(mid > bubble.mid) { + break; + } + } + } + + if(foundAtIndex !== -1) { + foundBubble = this.bubbles[foundAtIndex]; + } + /* const foundBubble = this.bubbles.find(bubble => { const diff = Math.abs(bubble.timestamp - timestamp); return bubble.fromId === fromId && diff <= this.newGroupDiff; - }); + }); */ if(!foundBubble) this.groups.push(group = [insertObject]); else { @@ -70,8 +102,21 @@ export default class BubbleGroups { } //console.log('[BUBBLE]: addBubble', bubble, message.mid, fromId, reverse, group); + + if(mid > 0) { + let insertIndex = 0; + for(; insertIndex < this.bubbles.length; ++insertIndex) { + if(this.bubbles[insertIndex].mid < mid) { + break; + } + } + + this.bubbles.splice(insertIndex, 0, {timestamp, fromId, mid: message.mid, group}); + } else { + this.bubbles.unshift({timestamp, fromId, mid: message.mid, group}); + } - this.bubbles.push({timestamp, fromId, mid: message.mid, group}); + //this.bubbles.push({timestamp, fromId, mid: message.mid, group}); this.updateGroup(group); } diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index c2a3a6d1..743fafdc 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -36,6 +36,7 @@ import { AppChatsManager } from "../../lib/appManagers/appChatsManager"; import Chat from "./chat"; import ListenerSetter from "../../helpers/listenerSetter"; import PollElement from "../poll"; +import AudioElement from "../audio"; const IGNORE_ACTIONS = ['messageActionHistoryClear']; @@ -120,7 +121,7 @@ export default class ChatBubbles { // * constructor end this.log = this.chat.log; - this.bubbleGroups = new BubbleGroups(); + this.bubbleGroups = new BubbleGroups(this.chat); this.preloader = new ProgressivePreloader(null, false); this.lazyLoadQueue = new LazyLoadQueue(); this.lazyLoadQueue.queueId = ++queueId; @@ -165,7 +166,7 @@ export default class ChatBubbles { this.log('message_sent', e.detail); - const mounted = this.getMountedBubble(tempId) || this.getMountedBubble(mid); + const mounted = this.getMountedBubble(tempId, tempMessage) || this.getMountedBubble(mid); if(mounted) { const message = this.chat.getMessage(mid); const bubble = mounted.bubble; @@ -175,7 +176,7 @@ export default class ChatBubbles { // set new mids to album items for mediaViewer if(message.grouped_id) { - const item = bubble.querySelector(`.grouped-item[data-mid="${tempId}"]`) as HTMLElement; + const item = (bubble.querySelector(`.grouped-item[data-mid="${tempId}"]`) as HTMLElement) || bubble; // * it can be .document-container item.dataset.mid = '' + mid; } @@ -190,9 +191,10 @@ export default class ChatBubbles { } if(['audio', 'voice'].includes(message.media?.document?.type)) { - const audio = bubble.querySelector(`audio-element[message-id="${tempId}"]`); + const audio = bubble.querySelector(`audio-element[message-id="${tempId}"]`) as AudioElement; audio.setAttribute('doc-id', message.media.document.id); audio.setAttribute('message-id', '' + mid); + audio.message = message; } /* bubble.classList.remove('is-sending'); @@ -219,7 +221,7 @@ export default class ChatBubbles { delete this.bubbles[tempId]; bubble.classList.remove('is-sending'); - bubble.classList.add('is-sent'); + bubble.classList.add(this.peerId === rootScope.myId && this.chat.type !== 'scheduled' ? 'is-read' : 'is-sent'); bubble.dataset.mid = '' + mid; this.bubbleGroups.removeBubble(bubble, tempId); @@ -695,9 +697,7 @@ export default class ChatBubbles { return Array.from(bubble.querySelectorAll('.grouped-item')) as HTMLElement[]; } - public getMountedBubble(mid: number) { - const message = this.chat.getMessage(mid); - + public getMountedBubble(mid: number, message = this.chat.getMessage(mid)) { if(message.grouped_id && this.appMessagesManager.getMidsByAlbum(message.grouped_id).length > 1) { const a = this.getGroupedBubble(message.grouped_id); if(a) { @@ -1350,40 +1350,40 @@ export default class ChatBubbles { public setBubblePosition(bubble: HTMLElement, message: any, reverse: boolean) { const dateMessage = this.getDateContainerByMessage(message, reverse); - let children = Array.from(dateMessage.container.children).slice(1) as HTMLElement[]; - let i = 0, foundMidOnSameTimestamp = 0; - for(; i < children.length; ++i) { - const t = children[i]; - const timestamp = +t.dataset.timestamp; - if(message.date < timestamp) { - break; - } else if(message.date === timestamp) { - foundMidOnSameTimestamp = +t.dataset.mid; + if(this.chat.type === 'scheduled' || this.chat.type === 'pinned') { + let children = Array.from(dateMessage.container.children).slice(2) as HTMLElement[]; + let i = 0, foundMidOnSameTimestamp = 0; + for(; i < children.length; ++i) { + const t = children[i]; + const timestamp = +t.dataset.timestamp; + if(message.date < timestamp) { + break; + } else if(message.date === timestamp) { + foundMidOnSameTimestamp = +t.dataset.mid; + } + + if(foundMidOnSameTimestamp && message.mid < foundMidOnSameTimestamp) { + break; + } } - - if(foundMidOnSameTimestamp && message.mid < foundMidOnSameTimestamp) { - break; + + // * 1 for date, 1 for date sentinel + let index = 2 + i; + if(bubble.parentElement) { // * if already mounted + const currentIndex = whichChild(bubble); + if(index > currentIndex) { + index -= 1; // * minus for already mounted + } } - } - - // * 1 for date - let index = 1 + i; - if(bubble.parentElement) { // * if already mounted - const currentIndex = whichChild(bubble); - if(index > currentIndex) { - index -= 1; // * minus for already mounted + + positionElementByIndex(bubble, dateMessage.container, index); + } else { + if(reverse) { + dateMessage.container.insertBefore(bubble, dateMessage.container.children[1].nextSibling); + } else { + dateMessage.container.append(bubble); } } - - positionElementByIndex(bubble, dateMessage.container, index); - - //this.bubbleGroups.updateGroupByMessageId(message.mid); - - /* if(reverse) { - dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling); - } else { - dateMessage.container.append(bubble); - } */ } // * will change .cleaned in cleanup() and new instance will be created @@ -1637,10 +1637,11 @@ export default class ChatBubbles { bubbleContainer.prepend(containerDiv); } + const isOutgoing = message.pFlags.is_outgoing/* && this.peerId != rootScope.myId */; if(our) { - if(message.pFlags.unread || message.mid < 0) this.unreadOut.add(message.mid); // message.mid < 0 added 11.02.2020 + if(message.pFlags.unread || isOutgoing) this.unreadOut.add(message.mid); let status = ''; - if(message.mid < 0) status = 'is-sending'; + if(isOutgoing) status = 'is-sending'; else status = message.pFlags.unread ? 'is-sent' : 'is-read'; bubble.classList.add(status); } @@ -1839,7 +1840,9 @@ export default class ChatBubbles { }); //} } else { - const docDiv = wrapDocument(this.peerId, doc, false, false, message.mid); + const docDiv = wrapDocument({ + message + }); preview.append(docDiv); preview.classList.add('preview-with-document'); //messageDiv.classList.add((webpage.type || 'document') + '-message'); diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index ff750069..94ce779e 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -256,13 +256,24 @@ export default class PopupNewMedia extends PopupElement { params.objectURL = URL.createObjectURL(file); } - const docDiv = wrapDocument(0, { - file: file, - file_name: file.name || '', - size: file.size, - type: isPhoto ? 'photo' : 'doc', - url: params.objectURL - } as any, false, true); + const docDiv = wrapDocument({ + message: { + _: 'message', + mid: 0, + peerId: 0, + media: { + _: 'messageMediaDocument', + document: { + _: 'document', + file: file, + file_name: file.name || '', + size: file.size, + type: isPhoto ? 'photo' : 'doc', + url: params.objectURL + } + } + } as any + }); const finish = () => { itemDiv.append(docDiv); diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index 422ec86f..2cd19ece 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -528,7 +528,11 @@ export default class AppSharedMediaTab implements SliderTab { case 'inputMessagesFilterMusic': case 'inputMessagesFilterDocument': { for(const message of messages) { - const div = wrapDocument(this.peerId, message.media.document, true, false, message.mid, 400); + const div = wrapDocument({ + message, + withTime: true, + fontWeight: 400 + }); div.dataset.mid = '' + message.mid; elemsToAppend.push(div); } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index de94db7b..2f9ba120 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -343,9 +343,21 @@ export const formatDate = (timestamp: number, monthShort = false, withYear = tru return str + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2); }; -export function wrapDocument(peerId: number, doc: MyDocument, withTime = false, uploading = false, mid?: number, fontWeight = 500): HTMLElement { +export function wrapDocument({message, withTime, uploading, fontWeight}: { + message: any, + withTime?: boolean, + uploading?: boolean, + fontWeight?: number +}): HTMLElement { + if(!fontWeight) fontWeight = 500; + + const doc = message.media.document; if(doc.type == 'audio' || doc.type == 'voice') { - const audioElement = wrapAudio(peerId, doc, withTime, mid); + const audioElement = new AudioElement(); + audioElement.setAttribute('message-id', '' + message.mid); + audioElement.setAttribute('peer-id', '' + message.peerId); + audioElement.withTime = withTime; + audioElement.message = message; audioElement.dataset.fontWeight = '' + fontWeight; return audioElement; } @@ -438,15 +450,6 @@ export function wrapDocument(peerId: number, doc: MyDocument, withTime = false, return docDiv; } -export function wrapAudio(peerId: number, doc: MyDocument, withTime = false, mid?: number): HTMLElement { - let elem = new AudioElement(); - elem.setAttribute('peer-id', '' + peerId); - elem.setAttribute('doc-id', doc.id); - elem.setAttribute('with-time', '' + +withTime); - elem.setAttribute('message-id', '' + mid); - return elem; -} - function wrapMediaWithTail(photo: MyPhoto | MyDocument, message: {mid: number, message: string}, container: HTMLElement, boxWidth: number, boxHeight: number, isOut: boolean) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add('bubble__media-container', isOut ? 'is-out' : 'is-in'); @@ -921,10 +924,10 @@ export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLo items.push({size, media, message: m}); } - // * pending + /* // * pending if(storage[0] < 0) { items.reverse(); - } + } */ prepareAlbum({ container: attachmentDiv, @@ -979,15 +982,17 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, }) { let nameContainer: HTMLDivElement; const mids = albumMustBeRenderedFull ? chat.getMidsByMid(message.mid) : [message.mid]; - const isPending = message.mid < 0; - if(isPending) { + const isPending = message.pFlags.is_outgoing; + /* if(isPending) { mids.reverse(); - } + } */ mids.forEach((mid, idx) => { const message = chat.getMessage(mid); const doc = message.media.document; - const div = wrapDocument(chat.peerId, doc, false, isPending, mid); + const div = wrapDocument({ + message + }); const container = document.createElement('div'); container.classList.add('document-container'); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index cad3939a..fa350e77 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -137,6 +137,10 @@ export class AppImManager { const hash = location.hash; const splitted = hash.split('?'); + if(!splitted[1]) { + return; + } + const params: any = {}; splitted[1].split('&').forEach(item => { params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]); @@ -148,7 +152,7 @@ export class AppImManager { case '#/im': { const p = params.p; if(p[0] === '@') { - let postId = params.post !== undefined ? +params.post : undefined; + let postId = params.post !== undefined ? appMessagesManager.generateMessageId(+params.post) : undefined; appUsersManager.resolveUsername(p).then(peer => { const isUser = peer._ == 'user'; const peerId = isUser ? peer.id : -peer.id; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 3678049a..4eebf679 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -78,7 +78,10 @@ export type PinnedStorage = Partial<{ count: number, maxId: number }>; -export type MessagesStorage = {[mid: string]: any}; +export type MessagesStorage = { + //generateIndex: (message: any) => void + [mid: string]: any +}; export class AppMessagesManager { public messagesStorageByPeerId: {[peerId: string]: MessagesStorage} = {}; public groupedMessagesStorage: {[groupId: string]: MessagesStorage} = {}; // will be used for albums @@ -107,7 +110,7 @@ export class AppMessagesManager { public pendingAfterMsgs: any = {}; public pendingTopMsgs: {[peerId: string]: number} = {}; public sendFilePromise: CancellablePromise = Promise.resolve(); - public tempId = -1; + public tempNum = 0; public tempFinalizeCallbacks: { [tempId: string]: { [callbackName: string]: Partial<{ @@ -231,7 +234,7 @@ export class AppMessagesManager { let removeUnread = 0; for(const mid of history) { const message = this.getMessageByPeer(dialog.peerId, mid); - if(/* message._ != 'messageEmpty' && */message.id > 0) { + if(/* message._ != 'messageEmpty' && */!message.pFlags.is_outgoing) { messages.push(message); if(message.fromId != dialog.peerId) { @@ -239,6 +242,7 @@ export class AppMessagesManager { } dialog.top_message = message.mid; + this.setDialogIndexByMessage(dialog, message); break; } else if(message.pFlags && message.pFlags.unread) { @@ -341,7 +345,7 @@ export class AppMessagesManager { const {mid, peerId} = message; - if(mid < 0) { + if(message.pFlags.is_outgoing) { return this.invokeAfterMessageIsSent(mid, 'edit', (message) => { this.log('invoke editMessage callback', message); return this.editMessage(message, text, options); @@ -357,7 +361,7 @@ export class AppMessagesManager { const schedule_date = options.scheduleDate || (message.pFlags.is_scheduled ? message.date : undefined); return apiManager.invokeApi('messages.editMessage', { peer: appPeersManager.getInputPeerById(peerId), - id: mid, + id: message.id, message: text, media: options.newMedia, entities: entities ? this.getInputEntities(entities) : undefined, @@ -426,10 +430,10 @@ export class AppMessagesManager { sendEntites = undefined; } - var messageId = this.tempId--; + var messageId = this.generateTempMessageId(peerId); var randomIdS = randomLong(); var pFlags: any = {}; - var replyToMsgId = options.replyToMsgId; + var replyToMsgId = options.replyToMsgId ? this.getLocalMessageId(options.replyToMsgId) : undefined; var isChannel = appPeersManager.isChannel(peerId); var isMegagroup = isChannel && appPeersManager.isMegagroup(peerId); var asChannel = isChannel && !isMegagroup ? true : false; @@ -589,10 +593,10 @@ export class AppMessagesManager { }> = {}) { peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; //this.checkSendOptions(options); - const messageId = this.tempId--; + const messageId = this.generateTempMessageId(peerId); const randomIdS = randomLong(); const pFlags: any = {}; - const replyToMsgId = options.replyToMsgId; + const replyToMsgId = options.replyToMsgId ? this.getLocalMessageId(options.replyToMsgId) : undefined; const isChannel = appPeersManager.isChannel(peerId); const isMegagroup = isChannel && appPeersManager.isMegagroup(peerId); const asChannel = !!(isChannel && !isMegagroup); @@ -936,7 +940,7 @@ export class AppMessagesManager { } peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; - const replyToMsgId = options.replyToMsgId; + const replyToMsgId = options.replyToMsgId ? this.getLocalMessageId(options.replyToMsgId) : undefined; let caption = options.caption || ''; let entities: MessageEntity[]; @@ -966,20 +970,24 @@ export class AppMessagesManager { return this.sendFile(peerId, file, o).message; }); - const groupId = messages[0].id; + const message = messages[messages.length - 1]; + const groupId = message.id; messages.forEach(message => { message.grouped_id = groupId; }); const storage = options.scheduleDate ? this.getScheduledMessagesStorage(peerId) : this.getMessagesStorage(peerId); if(options.scheduleDate) { - this.saveMessages(messages, {storage, isScheduled: true}); + this.saveMessages(messages, {storage, isScheduled: true, isOutgoing: true}); rootScope.broadcast('scheduled_new', {peerId, mid: groupId}); } else { - this.saveMessages(messages, {storage}); + this.saveMessages(messages, {storage, isOutgoing: true}); rootScope.broadcast('history_append', {peerId, messageId: groupId, my: true}); + + this.setDialogTopMessage(message); } + // * test pending //return; const toggleError = (message: any, on: boolean) => { @@ -1061,9 +1069,9 @@ export class AppMessagesManager { peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; //this.checkSendOptions(options); - const messageId = this.tempId--; + const messageId = this.generateTempMessageId(peerId); const randomIdS = randomLong(); - const replyToMsgId = options.replyToMsgId; + const replyToMsgId = options.replyToMsgId ? this.getLocalMessageId(options.replyToMsgId) : undefined; const isChannel = appPeersManager.isChannel(peerId); const isMegagroup = isChannel && appPeersManager.isMegagroup(peerId); const asChannel = isChannel && !isMegagroup ? true : false; @@ -1266,16 +1274,19 @@ export class AppMessagesManager { if(options.isScheduled) { if(!options.isGroupedItem) { - this.saveMessages([message], {storage, isScheduled: true}); + this.saveMessages([message], {storage, isScheduled: true, isOutgoing: true}); rootScope.broadcast('scheduled_new', {peerId, mid: messageId}); } } else { const historyStorage = this.getHistoryStorage(peerId); - historyStorage.pending.unshift(messageId); + //historyStorage.pending.unshift(messageId); + historyStorage.history.unshift(messageId); if(!options.isGroupedItem) { - this.saveMessages([message], {storage}); + this.saveMessages([message], {storage, isOutgoing: true}); rootScope.broadcast('history_append', {peerId, messageId, my: true}); + + this.setDialogTopMessage(message); } } @@ -1284,6 +1295,25 @@ export class AppMessagesManager { if(!options.isGroupedItem) { setTimeout(message.send, 0); //setTimeout(message.send, 4000); + //setTimeout(message.send, 7000); + } + } + + private setDialogIndexByMessage(dialog: MTDialog.dialog, message: MyMessage) { + if(!dialog.pFlags.pinned || !dialog.index) { + dialog.index = this.dialogsStorage.generateDialogIndex(message.date); + } + } + + public setDialogTopMessage(message: MyMessage) { + const dialog = this.getDialogByPeerId(message.peerId)[0]; + if(dialog) { + dialog.top_message = message.mid; + + this.setDialogIndexByMessage(dialog, message); + + this.newDialogsToHandle[message.peerId] = dialog; + this.scheduleHandleNewDialogs(); } } @@ -1526,7 +1556,7 @@ export class AppMessagesManager { scheduleDate: number }> = {}) { peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; - msgIds = msgIds.slice().sort((a, b) => a - b); + msgIds = msgIds.slice().sort((a, b) => a - b).map(mid => this.getLocalMessageId(mid)); const randomIds: string[] = msgIds.map(() => randomLong()); @@ -1564,8 +1594,30 @@ export class AppMessagesManager { }; } + private createMessageStorage() { + const storage: MessagesStorage = {} as any; + + /* let num = 0; + Object.defineProperty(storage, 'num', { + get: () => ++num, + set: (_num: number) => num = _num, + enumerable: false + }); + + Object.defineProperty(storage, 'generateIndex', { + value: (message: any) => { + if(message.index === undefined) { + message.index = (message.date * 0x10000) + (storage.num & 0xFFFF); + } + }, + enumerable: false + }); */ + + return storage; + } + public getMessagesStorage(peerId: number) { - return this.messagesStorageByPeerId[peerId] ?? (this.messagesStorageByPeerId[peerId] = {}); + return this.messagesStorageByPeerId[peerId] ?? (this.messagesStorageByPeerId[peerId] = this.createMessageStorage()); } public getMessageById(messageId: number) { @@ -1712,13 +1764,13 @@ export class AppMessagesManager { }); } - public updatePinnedMessage(peerId: number, mid: number, unpin?: true, silent?: true, oneSide?: true) { + public updatePinnedMessage(peerId: number, id: number, unpin?: true, silent?: true, oneSide?: true) { return apiManager.invokeApi('messages.updatePinnedMessage', { peer: appPeersManager.getInputPeerById(peerId), unpin, silent, pm_oneside: oneSide, - id: mid + id }).then(updates => { this.log('pinned updates:', updates); apiUpdatesManager.processUpdateMessage(updates); @@ -1789,9 +1841,47 @@ export class AppMessagesManager { else return [message.mid]; } + public generateTempMessageId(peerId: number) { + const dialog = this.getDialogByPeerId(peerId)[0]; + return this.generateMessageId(dialog?.top_message || 0, true); + } + + public generateMessageId(messageId: number, temp = false) { + const q = 0xFFFFFFFF; + const num = temp ? ++this.tempNum : 0; + if(messageId > q) { + if(temp) { + return messageId + (num & 0xFFFF); + } + + return messageId; + } + + return q + (messageId * 0x10000 + (num & 0xFFFF)); + } + + /** + * * will ignore outgoing offset + */ + public getLocalMessageId(messageId: number) { + const q = 0xFFFFFFFF; + if(messageId < q) { + return messageId; + } + + const l = 0xFFFF; + const used = messageId & l; + if(used !== l) { + messageId -= used + 1; + } + + return (messageId - q) / 0x10000; + } + public saveMessages(messages: any[], options: Partial<{ storage: MessagesStorage, - isScheduled: true + isScheduled: true, + isOutgoing: true }> = {}) { let groups: Map; messages.forEach((message) => { @@ -1807,15 +1897,20 @@ export class AppMessagesManager { // defineNotNumerableProperties(message, ['rReply', 'mid', 'savedFrom', 'fwdFromId', 'fromId', 'peerId', 'reply_to_mid', 'viaBotId']); const peerId = this.getMessagePeer(message); + const storage = options.storage || this.getMessagesStorage(peerId); const isChannel = message.peer_id._ == 'peerChannel'; const channelId = isChannel ? -peerId : 0; const isBroadcast = isChannel && appChatsManager.isBroadcast(channelId); - const mid = message.id; if(options.isScheduled) { message.pFlags.is_scheduled = true; } + + if(options.isOutgoing) { + message.pFlags.is_outgoing = true; + } + const mid = this.generateMessageId(message.id); message.mid = mid; if(message.grouped_id) { @@ -1834,14 +1929,16 @@ export class AppMessagesManager { // this.log(dT(), 'msg unread', mid, apiMessage.pFlags.out, dialog && dialog[apiMessage.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) if(message.reply_to && message.reply_to.reply_to_msg_id) { - message.reply_to_mid = message.reply_to.reply_to_msg_id; + //message.reply_to_mid = message.reply_to.reply_to_msg_id; + message.reply_to_mid = this.generateMessageId(message.reply_to.reply_to_msg_id); } const overwriting = !!message.peerId; if(!overwriting) { message.date -= serverTimeManager.serverTimeOffset; } - + + //storage.generateIndex(message); const myId = appUsersManager.getSelf().id; message.peerId = peerId; @@ -1856,7 +1953,8 @@ export class AppMessagesManager { //if(peerId == myID) { if(fwdHeader.saved_from_peer && fwdHeader.saved_from_msg_id) { const savedFromPeerId = appPeersManager.getPeerId(fwdHeader.saved_from_peer); - const savedFromMid = fwdHeader.saved_from_msg_id; + //const savedFromMid = fwdHeader.saved_from_msg_id; + const savedFromMid = this.generateMessageId(fwdHeader.saved_from_msg_id); message.savedFrom = savedFromPeerId + '_' + savedFromMid; } @@ -2019,10 +2117,9 @@ export class AppMessagesManager { if(message.message && message.message.length && !message.totalEntities) { const myEntities = RichTextProcessor.parseEntities(message.message); const apiEntities = message.entities || []; - message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities, !message.pending); // ! only in this order, otherwise bold and emoji formatting won't work + message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work } - const storage = options.storage || this.getMessagesStorage(peerId); storage[mid] = message; }); @@ -2417,10 +2514,10 @@ export class AppMessagesManager { let mid: number, message; if(dialog.top_message) { - mid = dialog.top_message; + mid = this.generateMessageId(dialog.top_message);//dialog.top_message; message = this.getMessageByPeer(peerId, mid); } else { - mid = this.tempId--; + mid = this.generateTempMessageId(peerId); message = { _: 'message', id: mid, @@ -2432,7 +2529,7 @@ export class AppMessagesManager { date: 0, message: '' }; - this.saveMessages([message]); + this.saveMessages([message], {isOutgoing: true}); } if(!message?.pFlags) { @@ -2450,6 +2547,8 @@ export class AppMessagesManager { } dialog.top_message = mid; + dialog.read_inbox_max_id = this.generateMessageId(dialog.read_inbox_max_id); + dialog.read_outbox_max_id = this.generateMessageId(dialog.read_outbox_max_id); if(!dialog.hasOwnProperty('folder_id')) { if(dialog._ == 'dialog') { @@ -2536,7 +2635,7 @@ export class AppMessagesManager { if(lastReplyMarkup) { if(lastReplyMarkup.pFlags.single_use && !lastReplyMarkup.pFlags.hidden && - (message.mid > lastReplyMarkup.mid || message.mid < 0) && + (message.mid > lastReplyMarkup.mid || message.pFlags.is_outgoing) && message.message) { lastReplyMarkup.pFlags.hidden = true; // this.log('set', historyStorage.reply_markup) @@ -2757,7 +2856,7 @@ export class AppMessagesManager { min_date: 0, max_date: 0, limit, - offset_id: maxId || 0, + offset_id: this.getLocalMessageId(maxId) || 0, add_offset: backLimit ? -backLimit : 0, max_id: 0, min_id: 0, @@ -2873,17 +2972,19 @@ export class AppMessagesManager { } } - public deleteMessages(peerId: number, msgIds: number[], revoke: boolean) { + public deleteMessages(peerId: number, mids: number[], revoke: boolean) { let promise: Promise; + mids = mids.map(mid => this.getLocalMessageId(mid)); + if(peerId < 0 && appPeersManager.isChannel(-peerId)) { const channelId = -peerId; const channel = appChatsManager.getChat(channelId); if(!channel.pFlags.creator && !(channel.pFlags.editor && channel.pFlags.megagroup)) { const goodMsgIds: number[] = []; if (channel.pFlags.editor || channel.pFlags.megagroup) { - msgIds.forEach((msgId, i) => { - const message = this.getMessageByPeer(peerId, msgIds[i]); + mids.forEach((msgId, i) => { + const message = this.getMessageByPeer(peerId, mids[i]); if(message.pFlags.out) { goodMsgIds.push(msgId); } @@ -2894,19 +2995,19 @@ export class AppMessagesManager { return; } - msgIds = goodMsgIds; + mids = goodMsgIds; } promise = apiManager.invokeApi('channels.deleteMessages', { channel: appChatsManager.getChannelInput(channelId), - id: msgIds + id: mids }).then((affectedMessages) => { apiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateDeleteChannelMessages', channel_id: channelId, - messages: msgIds, + messages: mids, pts: affectedMessages.pts, pts_count: affectedMessages.pts_count } @@ -2915,13 +3016,13 @@ export class AppMessagesManager { } else { promise = apiManager.invokeApi('messages.deleteMessages', { revoke: revoke || undefined, - id: msgIds + id: mids }).then((affectedMessages) => { apiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateDeleteMessages', - messages: msgIds, + messages: mids, pts: affectedMessages.pts, pts_count: affectedMessages.pts_count } @@ -3064,7 +3165,8 @@ export class AppMessagesManager { //this.log('AMM updateMessageID:', update, pendingData); if(pendingData) { const {peerId, tempId, storage} = pendingData; - const mid = update.id; + //const mid = update.id; + const mid = this.generateMessageId(update.id); const message = this.getMessageFromStorage(storage, mid); if(!message.deleted) { const historyStorage = this.getHistoryStorage(peerId); @@ -3150,16 +3252,10 @@ export class AppMessagesManager { } const inboxUnread = !message.pFlags.out && message.pFlags.unread; - dialog.top_message = message.mid; + this.setDialogTopMessage(message); if(inboxUnread) { dialog.unread_count++; } - if(!dialog.pFlags.pinned || !dialog.index) { - dialog.index = this.dialogsStorage.generateDialogIndex(message.date); - } - - this.newDialogsToHandle[peerId] = dialog; - this.scheduleHandleNewDialogs(); break; } @@ -3331,7 +3427,8 @@ export class AppMessagesManager { case 'updateEditChannelMessage': { const message = update.message as MyMessage; const peerId = this.getMessagePeer(message); - const mid = message.id; + //const mid = message.id; + const mid = this.generateMessageId(message.id); const storage = this.getMessagesStorage(peerId); if(storage[mid] === undefined) { break; @@ -3388,7 +3485,8 @@ export class AppMessagesManager { case 'updateReadChannelInbox': case 'updateReadChannelOutbox': { const channelId: number = (update as Update.updateReadChannelInbox).channel_id; - const maxId = update.max_id; + //const maxId = update.max_id; + const maxId = this.generateMessageId(update.max_id); const peerId = channelId ? -channelId : appPeersManager.getPeerId((update as Update.updateReadHistoryInbox).peer); const isOut = update._ == 'updateReadHistoryOutbox' || update._ == 'updateReadChannelOutbox' ? true : undefined; const foundDialog = this.getDialogByPeerId(peerId)[0]; @@ -3470,7 +3568,7 @@ export class AppMessagesManager { } } - rootScope.broadcast('messages_media_read', {peerId, mids: messages}); + rootScope.broadcast('messages_media_read', {peerId, mids: messages.map(id => this.generateMessageId(id))}); break; } @@ -3493,7 +3591,8 @@ export class AppMessagesManager { case 'updateDeleteMessages': case 'updateDeleteChannelMessages': { const channelId: number = (update as Update.updateDeleteChannelMessages).channel_id; - const messages = (update as any as Update.updateDeleteChannelMessages).messages; + //const messages = (update as any as Update.updateDeleteChannelMessages).messages; + const messages = (update as any as Update.updateDeleteChannelMessages).messages.map(id => this.generateMessageId(id)); const peerId = channelId ? -channelId : this.getMessageById(messages[0]).peerId; if(!peerId) { @@ -3588,7 +3687,8 @@ export class AppMessagesManager { case 'updateChannelMessageViews': { const views = update.views; - const mid = update.id; + //const mid = update.id; + const mid = this.generateMessageId(update.id); const message = this.getMessageByPeer(-update.channel_id, mid); if(!message.deleted && message.views && message.views < views) { message.views = views; @@ -3601,7 +3701,7 @@ export class AppMessagesManager { this.log('updateServiceNotification', update); const fromId = 777000; const peerId = fromId; - const messageId = this.tempId--; + const messageId = this.generateTempMessageId(peerId); const message: any = { _: 'message', id: messageId, @@ -3623,7 +3723,7 @@ export class AppMessagesManager { phone: '42777' }]); } - this.saveMessages([message]); + this.saveMessages([message], {isOutgoing: true}); if(update.inbox_date) { this.pendingTopMsgs[peerId] = messageId; @@ -3710,7 +3810,7 @@ export class AppMessagesManager { const storage = this.scheduledMessagesStorage[peerId]; if(storage) { - const mid = message.id; + const mid = this.generateMessageId(message.id); const oldMessage = this.getMessageFromStorage(storage, mid); this.saveMessages([message], {storage, isScheduled: true}); @@ -3735,9 +3835,10 @@ export class AppMessagesManager { const storage = this.scheduledMessagesStorage[peerId]; if(storage) { - this.handleDeletedMessages(peerId, storage, update.messages); + const mids = update.messages.map(id => this.generateMessageId(id)); + this.handleDeletedMessages(peerId, storage, mids); - rootScope.broadcast('scheduled_delete', {peerId, mids: update.messages}); + rootScope.broadcast('scheduled_delete', {peerId, mids}); } break; @@ -3836,6 +3937,7 @@ export class AppMessagesManager { const message = this.getMessageFromStorage(storage, tempId); if(!message.deleted) { + delete message.pFlags.is_outgoing; delete message.pending; delete message.error; delete message.random_id; @@ -3913,7 +4015,7 @@ export class AppMessagesManager { } public getScheduledMessagesStorage(peerId: number) { - return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = {}); + return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = this.createMessageStorage()); } public getScheduledMessages(peerId: number): Promise { @@ -4158,7 +4260,7 @@ export class AppMessagesManager { const promise = apiManager.invokeApi('messages.getHistory', { peer: appPeersManager.getInputPeerById(peerId), - offset_id: maxId || 0, + offset_id: this.getLocalMessageId(maxId) || 0, offset_date: offsetDate, add_offset: offset, limit: limit, diff --git a/src/lib/appManagers/appPollsManager.ts b/src/lib/appManagers/appPollsManager.ts index 8054c555..a3327cfa 100644 --- a/src/lib/appManagers/appPollsManager.ts +++ b/src/lib/appManagers/appPollsManager.ts @@ -170,7 +170,7 @@ export class AppPollsManager { const peerId = message.peerId; const inputPeer = appPeersManager.getInputPeerById(peerId); - if(messageId < 0) { + if(message.pFlags.is_outgoing) { return appMessagesManager.invokeAfterMessageIsSent(messageId, 'sendVote', (message) => { this.log('invoke sendVote callback'); return this.sendVote(message, optionIds); diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 79b01a3e..9713e605 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -513,7 +513,8 @@ export class ApiFileManager { (r.value as Promise).then(process); }; - const maxRequests = Infinity; + //const maxRequests = Infinity; + const maxRequests = 10; /* for(let i = 0; i < 10; ++i) { process(); } */ diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts index c9f8d493..d424c72e 100644 --- a/src/lib/richtextprocessor.ts +++ b/src/lib/richtextprocessor.ts @@ -364,7 +364,7 @@ namespace RichTextProcessor { return totalEntities; } */ - export function mergeEntities(currentEntities: MessageEntity[], newEntities: MessageEntity[], fromApi?: boolean) { + export function mergeEntities(currentEntities: MessageEntity[], newEntities: MessageEntity[]) { currentEntities = currentEntities.slice(); const filtered = newEntities.filter(e => !currentEntities.find(_e => e._ == _e._ && e.offset == _e.offset && e.length == _e.length)); currentEntities.push(...filtered);