Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
885 lines
30 KiB
885 lines
30 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
* |
|
* Originally from: |
|
* https://github.com/zhukov/webogram |
|
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com> |
|
* https://github.com/zhukov/webogram/blob/master/LICENSE |
|
*/ |
|
|
|
import type { Chat, DialogPeer, Message, MessagesPeerDialogs, Update } from "../../layer"; |
|
import type { AppChatsManager } from "../appManagers/appChatsManager"; |
|
import type { AppMessagesManager, Dialog, MyMessage } from "../appManagers/appMessagesManager"; |
|
import type { AppPeersManager } from "../appManagers/appPeersManager"; |
|
import type { AppUsersManager } from "../appManagers/appUsersManager"; |
|
import type { AppDraftsManager } from "../appManagers/appDraftsManager"; |
|
import type { AppNotificationsManager } from "../appManagers/appNotificationsManager"; |
|
import type { ApiUpdatesManager } from "../appManagers/apiUpdatesManager"; |
|
import type { ServerTimeManager } from "../mtproto/serverTimeManager"; |
|
import type { AppMessagesIdsManager } from "../appManagers/appMessagesIdsManager"; |
|
import { tsNow } from "../../helpers/date"; |
|
import apiManager from "../mtproto/mtprotoworker"; |
|
import SearchIndex from "../searchIndex"; |
|
import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array"; |
|
import rootScope from "../rootScope"; |
|
import { safeReplaceObject } from "../../helpers/object"; |
|
import { AppStateManager } from "../appManagers/appStateManager"; |
|
import { SliceEnd } from "../../helpers/slicedArray"; |
|
import { MyDialogFilter } from "./filters"; |
|
|
|
export type FolderDialog = { |
|
dialog: Dialog, |
|
index: number |
|
}; |
|
|
|
export type Folder = { |
|
dialogs: FolderDialog[], |
|
count?: number |
|
}; |
|
|
|
export default class DialogsStorage { |
|
private storage: AppStateManager['storages']['dialogs']; |
|
|
|
private dialogs: {[peerId: string]: Dialog}; |
|
public byFolders: {[folderId: number]: Dialog[]}; |
|
|
|
// public folders: {[folderId: number]: Folder} = {}; |
|
|
|
private allDialogsLoaded: {[folder_id: number]: boolean}; |
|
private dialogsOffsetDate: {[folder_id: number]: number}; |
|
private pinnedOrders: {[folder_id: number]: number[]}; |
|
private dialogsNum: number; |
|
|
|
private dialogsIndex: SearchIndex<number>; |
|
|
|
private cachedResults: { |
|
query: string, |
|
count: number, |
|
dialogs: Dialog[], |
|
folderId: number |
|
}; |
|
|
|
constructor(private appMessagesManager: AppMessagesManager, |
|
private appChatsManager: AppChatsManager, |
|
private appPeersManager: AppPeersManager, |
|
private appUsersManager: AppUsersManager, |
|
private appDraftsManager: AppDraftsManager, |
|
private appNotificationsManager: AppNotificationsManager, |
|
private appStateManager: AppStateManager, |
|
private apiUpdatesManager: ApiUpdatesManager, |
|
private serverTimeManager: ServerTimeManager, |
|
private appMessagesIdsManager: AppMessagesIdsManager |
|
) { |
|
this.storage = this.appStateManager.storages.dialogs; |
|
this.dialogs = this.storage.getCache(); |
|
this.clear(true); |
|
|
|
rootScope.addEventListener('language_change', (e) => { |
|
const peerId = appUsersManager.getSelf().id; |
|
const dialog = this.getDialogOnly(peerId); |
|
if(dialog) { |
|
const peerText = appPeersManager.getPeerSearchText(peerId); |
|
this.dialogsIndex.indexObject(peerId, peerText); |
|
} |
|
}); |
|
|
|
// to set new indexes |
|
rootScope.addEventListener('filter_order', () => { |
|
// ! MUST BE REFACTORED ! |
|
for(let id in this.appMessagesManager.filtersStorage.filters) { |
|
this.getFolder(+id, false); |
|
} |
|
}); |
|
|
|
rootScope.addEventListener('chat_update', (chatId) => { |
|
const chat: Chat.chat = this.appChatsManager.getChat(chatId); |
|
|
|
const peerId = -chatId; |
|
if(chat.pFlags.left && this.getDialogOnly(peerId)) { |
|
this.dropDialogWithEvent(peerId); |
|
} |
|
}); |
|
|
|
rootScope.addMultipleEventsListeners({ |
|
updateFolderPeers: this.onUpdateFolderPeers, |
|
|
|
updateDialogPinned: this.onUpdateDialogPinned, |
|
|
|
updatePinnedDialogs: this.onUpdatePinnedDialogs, |
|
}); |
|
|
|
appStateManager.getState().then((state) => { |
|
this.pinnedOrders = state.pinnedOrders || {}; |
|
if(!this.pinnedOrders[0]) this.pinnedOrders[0] = []; |
|
if(!this.pinnedOrders[1]) this.pinnedOrders[1] = []; |
|
|
|
const dialogs = appStateManager.storagesResults.dialogs; |
|
if(dialogs.length) { |
|
for(let i = 0, length = dialogs.length; i < length; ++i) { |
|
const dialog = dialogs[i]; |
|
if(dialog) { |
|
// if(dialog.peerId !== SERVICE_PEER_ID) { |
|
dialog.top_message = this.appMessagesIdsManager.getServerMessageId(dialog.top_message); // * fix outgoing message to avoid copying dialog |
|
// } |
|
|
|
if(dialog.topMessage) { |
|
this.appMessagesManager.saveMessages([dialog.topMessage]); |
|
} |
|
|
|
this.saveDialog(dialog, undefined, true); |
|
|
|
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись |
|
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message); |
|
if(message.deleted) { |
|
this.appMessagesManager.reloadConversation(dialog.peerId); |
|
} |
|
} |
|
} |
|
} |
|
|
|
this.allDialogsLoaded = state.allDialogsLoaded || {}; |
|
}); |
|
} |
|
|
|
public isDialogsLoaded(folderId: number) { |
|
return !!this.allDialogsLoaded[folderId]; |
|
} |
|
|
|
public setDialogsLoaded(folderId: number, loaded: boolean) { |
|
this.allDialogsLoaded[folderId] = loaded; |
|
this.appStateManager.pushToState('allDialogsLoaded', this.allDialogsLoaded); |
|
} |
|
|
|
public clear(init = false) { |
|
if(!init) { |
|
const dialogs = this.appStateManager.storagesResults.dialogs; |
|
dialogs.length = 0; |
|
this.storage.clear(); |
|
} |
|
|
|
this.byFolders = {}; |
|
this.allDialogsLoaded = {}; |
|
this.dialogsOffsetDate = {}; |
|
this.pinnedOrders = { |
|
0: [], |
|
1: [] |
|
}; |
|
this.dialogsNum = 0; |
|
this.dialogsIndex = new SearchIndex<number>({ |
|
clearBadChars: true, |
|
ignoreCase: true, |
|
latinize: true, |
|
includeTag: true |
|
}); |
|
this.cachedResults = { |
|
query: '', |
|
count: 0, |
|
dialogs: [], |
|
folderId: 0 |
|
}; |
|
} |
|
|
|
public resetPinnedOrder(folderId: number) { |
|
this.pinnedOrders[folderId] = []; |
|
} |
|
|
|
public getPinnedOrders(folderId: number) { |
|
return this.pinnedOrders[folderId]; |
|
} |
|
|
|
public getOffsetDate(folderId: number) { |
|
return this.dialogsOffsetDate[folderId] || 0; |
|
} |
|
|
|
public getFolder(id: number, skipMigrated = true) { |
|
if(id <= 1) { |
|
const arr = this.byFolders[id] ?? (this.byFolders[id] = []); |
|
return skipMigrated ? arr.filter(dialog => dialog.migratedTo === undefined) : arr; |
|
} |
|
|
|
// const dialogs: {dialog: Dialog, index: number}[] = []; |
|
const dialogs: Dialog[] = []; |
|
const filter = this.appMessagesManager.filtersStorage.getFilter(id); |
|
|
|
const indexStr = this.getDialogIndexKey(id); |
|
for(const peerId in this.dialogs) { |
|
const dialog = this.dialogs[peerId]; |
|
if(this.setDialogIndexInFilter(dialog, indexStr, filter) && (!skipMigrated || dialog.migratedTo === undefined)) { |
|
insertInDescendSortedArray(dialogs, dialog, indexStr, -1); |
|
} |
|
} |
|
|
|
return dialogs; |
|
|
|
// dialogs.sort((a, b) => b.index - a.index); |
|
// return dialogs.map(d => d.dialog); |
|
} |
|
|
|
private setDialogIndexInFilter(dialog: Dialog, indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>, filter: MyDialogFilter) { |
|
let index: number; |
|
|
|
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) { |
|
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerId); |
|
if(pinnedIndex !== -1) { |
|
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex), true); |
|
} else if(dialog.pFlags?.pinned) { |
|
index = this.generateIndexForDialog(dialog, true); |
|
} else { |
|
index = dialog.index; |
|
} |
|
} |
|
|
|
return dialog[indexKey] = index; |
|
} |
|
|
|
public getDialog(peerId: number, folderId?: number, skipMigrated = true): [Dialog, number] | [] { |
|
const folders: Dialog[][] = []; |
|
|
|
if(folderId === undefined) { |
|
const dialogs = this.byFolders; |
|
for(const folderId in dialogs) { |
|
folders.push(dialogs[folderId]); |
|
} |
|
} else { |
|
folders.push(this.getFolder(folderId, skipMigrated)); |
|
} |
|
|
|
for(let folder of folders) { |
|
let i = 0, skipped = 0; |
|
for(let length = folder.length; i < length; ++i) { |
|
const dialog = folder[i]; |
|
if(dialog.peerId === peerId) { |
|
return [dialog, i - skipped]; |
|
} else if(skipMigrated && dialog.migratedTo !== undefined) { |
|
++skipped; |
|
} |
|
} |
|
} |
|
|
|
return []; |
|
} |
|
|
|
public getDialogOnly(peerId: number) { |
|
return this.dialogs[peerId]; |
|
} |
|
|
|
/* |
|
var date = Date.now() / 1000 | 0; |
|
var m = date * 0x10000; |
|
|
|
var k = (date + 1) * 0x10000; |
|
k - m; |
|
65536 |
|
*/ |
|
public generateDialogIndex(date?: number, isPinned?: boolean) { |
|
if(date === undefined) { |
|
date = tsNow(true) + this.serverTimeManager.serverTimeOffset; |
|
} |
|
|
|
return (date * 0x10000) + (isPinned ? 0 : ((++this.dialogsNum) & 0xFFFF)); |
|
} |
|
|
|
public generateIndexForDialog(dialog: Dialog, justReturn = false, message?: MyMessage) { |
|
let topDate = 0, isPinned: boolean; |
|
if(dialog.pFlags.pinned && !justReturn) { |
|
topDate = this.generateDialogPinnedDate(dialog); |
|
isPinned = true; |
|
} else { |
|
if(!message) { |
|
message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message); |
|
} |
|
|
|
topDate = (message as Message.message).date || topDate; |
|
|
|
const channelId = this.appPeersManager.isChannel(dialog.peerId) ? -dialog.peerId : 0; |
|
if(channelId) { |
|
const channel: Chat.channel = this.appChatsManager.getChat(channelId); |
|
if(!topDate || (channel.date && channel.date > topDate)) { |
|
topDate = channel.date; |
|
} |
|
} |
|
|
|
if(dialog.draft?._ === 'draftMessage' && dialog.draft.date > topDate) { |
|
topDate = dialog.draft.date; |
|
} |
|
} |
|
|
|
if(!topDate) { |
|
topDate = tsNow(true); |
|
} |
|
|
|
const index = this.generateDialogIndex(topDate, isPinned); |
|
if(justReturn) { |
|
return index; |
|
} |
|
|
|
dialog.index = index; |
|
|
|
// ! MUST BE REFACTORED ! |
|
for(let id in this.appMessagesManager.filtersStorage.filters) { |
|
const filter = this.appMessagesManager.filtersStorage.filters[id]; |
|
this.setDialogIndexInFilter(dialog, this.getDialogIndexKey(+id), filter); |
|
} |
|
} |
|
|
|
public generateDialogPinnedDateByIndex(pinnedIndex: number) { |
|
return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов |
|
} |
|
|
|
public generateDialogPinnedDate(dialog: Dialog) { |
|
const order = this.pinnedOrders[dialog.folder_id]; |
|
|
|
const foundIndex = order.indexOf(dialog.peerId); |
|
let pinnedIndex = foundIndex; |
|
if(foundIndex === -1) { |
|
pinnedIndex = order.push(dialog.peerId) - 1; |
|
this.appStateManager.pushToState('pinnedOrders', this.pinnedOrders); |
|
} |
|
|
|
return this.generateDialogPinnedDateByIndex(pinnedIndex); |
|
} |
|
|
|
/* public generateDialog(peerId: number) { |
|
const dialog: Dialog = { |
|
_: 'dialog', |
|
pFlags: {}, |
|
peer: this.appPeersManager.getOutputPeer(peerId), |
|
top_message: 0, |
|
read_inbox_max_id: 0, |
|
read_outbox_max_id: 0, |
|
unread_count: 0, |
|
unread_mentions_count: 0, |
|
notify_settings: { |
|
_: 'peerNotifySettings', |
|
}, |
|
}; |
|
|
|
return dialog; |
|
} */ |
|
|
|
public setDialogToState(dialog: Dialog) { |
|
const {peerId, pts} = dialog; |
|
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId); |
|
const messagesStorage = this.appMessagesManager.getMessagesStorage(peerId); |
|
const history = historyStorage.history.slice; |
|
let incomingMessage: MyMessage; |
|
for(let i = 0, length = history.length; i < length; ++i) { |
|
const mid = history[i]; |
|
const message: MyMessage = this.appMessagesManager.getMessageFromStorage(messagesStorage, mid); |
|
if(!message.pFlags.is_outgoing/* || peerId === SERVICE_PEER_ID */) { |
|
incomingMessage = message; |
|
|
|
const fromId = message.viaBotId || message.fromId; |
|
if(fromId !== peerId) { |
|
this.appStateManager.requestPeer(fromId, 'topMessage_' + peerId, 1); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
|
|
dialog.topMessage = incomingMessage; |
|
|
|
// DO NOT TOUCH THESE LINES, SOME REAL MAGIC HERE. |
|
// * Read service chat when refreshing page with outgoing & getting new service outgoing message |
|
if(incomingMessage && dialog.read_inbox_max_id >= dialog.top_message) { |
|
dialog.unread_count = 0; |
|
} |
|
|
|
dialog.read_inbox_max_id = this.appMessagesIdsManager.clearMessageId(dialog.read_inbox_max_id); |
|
dialog.read_outbox_max_id = this.appMessagesIdsManager.clearMessageId(dialog.read_outbox_max_id); |
|
// CAN TOUCH NOW |
|
|
|
if(peerId < 0 && pts) { |
|
const newPts = this.apiUpdatesManager.getChannelState(-peerId, pts).pts; |
|
dialog.pts = newPts; |
|
} |
|
|
|
this.storage.set({ |
|
[peerId]: dialog |
|
}); |
|
|
|
this.appStateManager.requestPeer(peerId, 'dialog_' + peerId, 1); |
|
|
|
/* for(let id in this.appMessagesManager.filtersStorage.filters) { |
|
const filter = this.appMessagesManager.filtersStorage.filters[id]; |
|
|
|
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) { |
|
|
|
} |
|
} */ |
|
} |
|
|
|
public pushDialog(dialog: Dialog, offsetDate?: number) { |
|
const dialogs = this.getFolder(dialog.folder_id, false); |
|
const pos = dialogs.findIndex(d => d.peerId === dialog.peerId); |
|
if(pos !== -1) { |
|
dialogs.splice(pos, 1); |
|
} |
|
|
|
//if(!this.dialogs[dialog.peerId]) { |
|
this.dialogs[dialog.peerId] = dialog; |
|
|
|
this.setDialogToState(dialog); |
|
//} |
|
|
|
// let pos: number; |
|
if(offsetDate && |
|
!dialog.pFlags.pinned && |
|
(!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { |
|
if(pos !== -1) { // So the dialog jumped to the last position |
|
// dialogs.splice(pos, 1); |
|
return false; |
|
} |
|
|
|
this.dialogsOffsetDate[dialog.folder_id] = offsetDate; |
|
} |
|
|
|
/* const newPos = */insertInDescendSortedArray(dialogs, dialog, 'index', pos); |
|
/* if(pos !== -1 && pos !== newPos) { |
|
rootScope.dispatchEvent('dialog_order', {dialog, pos: newPos}); |
|
} */ |
|
} |
|
|
|
public dropDialog(peerId: number): [Dialog, number] | [] { |
|
const foundDialog = this.getDialog(peerId, undefined, false); |
|
if(foundDialog[0]) { |
|
this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1); |
|
this.pinnedOrders[foundDialog[0].folder_id].findAndSplice(_peerId => peerId === _peerId); |
|
this.dialogsIndex.indexObject(peerId, ''); |
|
delete this.dialogs[peerId]; |
|
|
|
// clear from state |
|
this.appStateManager.keepPeerSingle(0, 'topMessage_' + peerId); |
|
this.appStateManager.keepPeerSingle(0, 'dialog_' + peerId); |
|
this.storage.delete(peerId); |
|
} |
|
|
|
return foundDialog; |
|
} |
|
|
|
public dropDialogWithEvent(peerId: number) { |
|
const dropped = this.dropDialog(peerId); |
|
if(dropped.length) { |
|
rootScope.dispatchEvent('dialog_drop', {peerId, dialog: dropped[0]}); |
|
} |
|
} |
|
|
|
public applyDialogs(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) { |
|
// * В эту функцию попадут только те диалоги, в которых есть read_inbox_max_id и read_outbox_max_id, в отличие от тех, что будут в getTopMessages |
|
|
|
// ! fix 'dialogFolder', maybe there is better way to do it, this only can happen by 'messages.getPinnedDialogs' by folder_id: 0 |
|
forEachReverse(dialogsResult.dialogs, (dialog, idx) => { |
|
if(dialog._ === 'dialogFolder') { |
|
dialogsResult.dialogs.splice(idx, 1); |
|
} |
|
}); |
|
|
|
this.appUsersManager.saveApiUsers(dialogsResult.users); |
|
this.appChatsManager.saveApiChats(dialogsResult.chats); |
|
this.appMessagesManager.saveMessages(dialogsResult.messages); |
|
|
|
// this.appMessagesManager.log('applyConversation', dialogsResult); |
|
|
|
const updatedDialogs: {[peerId: number]: Dialog} = {}; |
|
(dialogsResult.dialogs as Dialog[]).forEach((dialog) => { |
|
const peerId = this.appPeersManager.getPeerId(dialog.peer); |
|
let topMessage = dialog.top_message; |
|
|
|
const topPendingMessage = this.appMessagesManager.pendingTopMsgs[peerId]; |
|
if(topPendingMessage) { |
|
if(!topMessage |
|
|| (this.appMessagesManager.getMessageByPeer(peerId, topPendingMessage) as MyMessage).date > (this.appMessagesManager.getMessageByPeer(peerId, topMessage) as MyMessage).date) { |
|
dialog.top_message = topMessage = topPendingMessage; |
|
this.appMessagesManager.getHistoryStorage(peerId).maxId = topPendingMessage; |
|
} |
|
} |
|
|
|
/* const d = Object.assign({}, dialog); |
|
if(peerId === 239602833) { |
|
this.log.error('applyConversation lun', dialog, d); |
|
} */ |
|
|
|
if(topMessage || (dialog.draft && dialog.draft._ === 'draftMessage')) { |
|
this.saveDialog(dialog); |
|
updatedDialogs[peerId] = dialog; |
|
} else { |
|
this.dropDialogWithEvent(peerId); |
|
} |
|
|
|
const updates = this.appMessagesManager.newUpdatesAfterReloadToHandle[peerId]; |
|
if(updates !== undefined) { |
|
for(const update of updates) { |
|
this.apiUpdatesManager.saveUpdate(update); |
|
} |
|
|
|
delete this.appMessagesManager.newUpdatesAfterReloadToHandle[peerId]; |
|
} |
|
}); |
|
|
|
if(Object.keys(updatedDialogs).length) { |
|
rootScope.dispatchEvent('dialogs_multiupdate', updatedDialogs); |
|
} |
|
} |
|
|
|
/** |
|
* Won't save migrated from peer, forbidden peers, left and kicked |
|
*/ |
|
public saveDialog(dialog: Dialog, folderId = 0, saveOffset = false) { |
|
const peerId = this.appPeersManager.getPeerId(dialog.peer); |
|
if(!peerId) { |
|
console.error('saveConversation no peerId???', dialog, folderId); |
|
return; |
|
} |
|
|
|
if(dialog._ !== 'dialog'/* || peerId === 239602833 */) { |
|
console.error('saveConversation not regular dialog', dialog, Object.assign({}, dialog)); |
|
} |
|
|
|
const channelId = this.appPeersManager.isChannel(peerId) ? -peerId : 0; |
|
|
|
if(peerId < 0) { |
|
const chat: Chat = this.appChatsManager.getChat(-peerId); |
|
// ! chatForbidden stays for chat where you're kicked |
|
if(chat._ === 'channelForbidden' /* || chat._ === 'chatForbidden' */ || (chat as Chat.chat).pFlags.left || (chat as Chat.chat).pFlags.kicked) { |
|
return; |
|
} |
|
} |
|
|
|
const peerText = this.appPeersManager.getPeerSearchText(peerId); |
|
this.dialogsIndex.indexObject(peerId, peerText); |
|
|
|
const wasDialogBefore = this.getDialogOnly(peerId); |
|
|
|
let mid: number, message: MyMessage; |
|
if(dialog.top_message) { |
|
if(wasDialogBefore?.top_message && !this.appMessagesManager.getMessageByPeer(peerId, wasDialogBefore.top_message).deleted) { |
|
mid = wasDialogBefore.top_message; |
|
} else { |
|
mid = this.appMessagesIdsManager.generateMessageId(dialog.top_message);//dialog.top_message; |
|
} |
|
|
|
message = this.appMessagesManager.getMessageByPeer(peerId, mid); |
|
} else { |
|
mid = this.appMessagesManager.generateTempMessageId(peerId); |
|
message = { |
|
_: 'message', |
|
id: mid, |
|
mid, |
|
from_id: this.appPeersManager.getOutputPeer(this.appUsersManager.getSelf().id), |
|
peer_id: this.appPeersManager.getOutputPeer(peerId), |
|
deleted: true, |
|
pFlags: {out: true}, |
|
date: 0, |
|
message: '' |
|
}; |
|
this.appMessagesManager.saveMessages([message], {isOutgoing: true}); |
|
} |
|
|
|
if(!message?.pFlags) { |
|
this.appMessagesManager.log.error('saveConversation no message:', dialog, message); |
|
} |
|
|
|
if(!channelId && peerId < 0) { |
|
const chat = this.appChatsManager.getChat(-peerId); |
|
if(chat && chat.migrated_to && chat.pFlags.deactivated) { |
|
const migratedToPeer = this.appPeersManager.getPeerId(chat.migrated_to); |
|
this.appMessagesManager.migratedFromTo[peerId] = migratedToPeer; |
|
this.appMessagesManager.migratedToFrom[migratedToPeer] = peerId; |
|
dialog.migratedTo = migratedToPeer; |
|
//return; |
|
} |
|
} |
|
|
|
dialog.top_message = mid; |
|
dialog.unread_count = wasDialogBefore && dialog.read_inbox_max_id === this.appMessagesIdsManager.getServerMessageId(wasDialogBefore.read_inbox_max_id) ? wasDialogBefore.unread_count : dialog.unread_count; |
|
dialog.read_inbox_max_id = this.appMessagesIdsManager.generateMessageId(wasDialogBefore && !dialog.read_inbox_max_id ? wasDialogBefore.read_inbox_max_id : dialog.read_inbox_max_id); |
|
dialog.read_outbox_max_id = this.appMessagesIdsManager.generateMessageId(wasDialogBefore && !dialog.read_outbox_max_id ? wasDialogBefore.read_outbox_max_id : dialog.read_outbox_max_id); |
|
|
|
if(!dialog.hasOwnProperty('folder_id')) { |
|
if(dialog._ === 'dialog') { |
|
// ! СЛОЖНО ! СМОТРИ В getTopMessages |
|
dialog.folder_id = wasDialogBefore ? wasDialogBefore.folder_id : folderId; |
|
}/* else if(dialog._ === 'dialogFolder') { |
|
dialog.folder_id = dialog.folder.id; |
|
} */ |
|
} |
|
|
|
dialog.draft = this.appDraftsManager.saveDraft(peerId, 0, dialog.draft); |
|
dialog.peerId = peerId; |
|
|
|
// Because we saved message without dialog present |
|
if(message.pFlags.is_outgoing) { |
|
if(mid > dialog[message.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) message.pFlags.unread = true; |
|
else delete message.pFlags.unread; |
|
} |
|
|
|
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId); |
|
const slice = historyStorage.history.slice; |
|
/* if(historyStorage === undefined) { // warning |
|
historyStorage.history.push(mid); |
|
} else */if(!slice.length) { |
|
historyStorage.history.unshift(mid); |
|
if(this.appMessagesManager.mergeReplyKeyboard(historyStorage, message)) { |
|
rootScope.dispatchEvent('history_reply_markup', {peerId}); |
|
} |
|
} else if(!slice.isEnd(SliceEnd.Bottom)) { // * this will probably never happen, however, if it does, then it will fix slice with top_message |
|
const slice = historyStorage.history.insertSlice([mid]); |
|
slice.setEnd(SliceEnd.Bottom); |
|
if(this.appMessagesManager.mergeReplyKeyboard(historyStorage, message)) { |
|
rootScope.dispatchEvent('history_reply_markup', {peerId}); |
|
} |
|
} |
|
|
|
historyStorage.maxId = mid; |
|
historyStorage.readMaxId = dialog.read_inbox_max_id; |
|
historyStorage.readOutboxMaxId = dialog.read_outbox_max_id; |
|
|
|
this.appNotificationsManager.savePeerSettings(peerId, dialog.notify_settings); |
|
|
|
if(channelId && dialog.pts) { |
|
this.apiUpdatesManager.addChannelState(channelId, dialog.pts); |
|
} |
|
|
|
this.generateIndexForDialog(dialog); |
|
|
|
if(wasDialogBefore) { |
|
safeReplaceObject(wasDialogBefore, dialog); |
|
} |
|
|
|
this.pushDialog(dialog, saveOffset && message.date); |
|
} |
|
|
|
public getDialogIndexKey(filterId: number) { |
|
const indexStr = filterId > 1 ? |
|
`index_${this.appMessagesManager.filtersStorage.getFilter(filterId).orderIndex}` as const : |
|
'index' as const; |
|
|
|
return indexStr; |
|
} |
|
|
|
public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false): { |
|
cached: boolean, |
|
promise: Promise<{ |
|
dialogs: Dialog[], |
|
count: number, |
|
isTopEnd: boolean, |
|
isEnd: boolean |
|
}> |
|
} { |
|
if(folderId > 1) { |
|
const fillContactsResult = this.appUsersManager.fillContacts(); |
|
if(!fillContactsResult.cached) { |
|
return { |
|
cached: false, |
|
promise: fillContactsResult.promise.then(() => { |
|
return this.getDialogs(query, offsetIndex, limit, folderId, skipMigrated).promise; |
|
}) |
|
}; |
|
} |
|
} |
|
|
|
const realFolderId = folderId > 1 ? 0 : folderId; |
|
let curDialogStorage = this.getFolder(folderId, skipMigrated); |
|
|
|
const indexStr = this.getDialogIndexKey(folderId); |
|
|
|
if(query) { |
|
if(!limit || this.cachedResults.query !== query || this.cachedResults.folderId !== folderId) { |
|
this.cachedResults.query = query; |
|
this.cachedResults.folderId = folderId; |
|
|
|
const results = this.dialogsIndex.search(query); |
|
|
|
const dialogs: Dialog[] = []; |
|
for(const peerId in this.dialogs) { |
|
const dialog = this.dialogs[peerId]; |
|
if(results.has(dialog.peerId) && dialog.folder_id === folderId) { |
|
dialogs.push(dialog); |
|
} |
|
} |
|
|
|
dialogs.sort((d1, d2) => d2[indexStr] - d1[indexStr]); |
|
this.cachedResults.dialogs = dialogs; |
|
this.cachedResults.count = dialogs.length; |
|
} |
|
|
|
curDialogStorage = this.cachedResults.dialogs; |
|
} else { |
|
this.cachedResults.query = ''; |
|
} |
|
|
|
let offset = 0; |
|
if(offsetIndex > 0) { |
|
for(let length = curDialogStorage.length; offset < length; ++offset) { |
|
if(offsetIndex > curDialogStorage[offset][indexStr]) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
const loadedAll = this.isDialogsLoaded(realFolderId); |
|
if(query || loadedAll || curDialogStorage.length >= (offset + limit)) { |
|
const dialogs = curDialogStorage.slice(offset, offset + limit); |
|
return { |
|
cached: true, |
|
promise: Promise.resolve({ |
|
dialogs, |
|
count: loadedAll ? curDialogStorage.length : null, |
|
isTopEnd: curDialogStorage.length && ((dialogs[0] && dialogs[0] === curDialogStorage[0]) || curDialogStorage[0][indexStr] < offsetIndex), |
|
isEnd: loadedAll && (offset + limit) >= curDialogStorage.length |
|
}) |
|
}; |
|
} |
|
|
|
return { |
|
cached: false, |
|
promise: this.appMessagesManager.getTopMessages(limit, realFolderId).then(result => { |
|
//const curDialogStorage = this[folderId]; |
|
if(skipMigrated) { |
|
curDialogStorage = this.getFolder(folderId, skipMigrated); |
|
} |
|
|
|
offset = 0; |
|
if(offsetIndex > 0) { |
|
for(let length = curDialogStorage.length; offset < length; ++offset) { |
|
if(offsetIndex > curDialogStorage[offset][indexStr]) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length); |
|
|
|
const dialogs = curDialogStorage.slice(offset, offset + limit); |
|
return { |
|
dialogs, |
|
count: result.count === undefined ? curDialogStorage.length : result.count, |
|
isTopEnd: curDialogStorage.length && ((dialogs[0] && dialogs[0] === curDialogStorage[0]) || curDialogStorage[0][indexStr] < offsetIndex), |
|
// isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length |
|
isEnd: result.isEnd |
|
}; |
|
}) |
|
}; |
|
} |
|
|
|
// only 0 and 1 folders |
|
private onUpdateFolderPeers = (update: Update.updateFolderPeers) => { |
|
//this.log('updateFolderPeers', update); |
|
const peers = update.folder_peers; |
|
|
|
peers.forEach((folderPeer) => { |
|
const {folder_id, peer} = folderPeer; |
|
|
|
const peerId = this.appPeersManager.getPeerId(peer); |
|
const dialog = this.dropDialog(peerId)[0]; |
|
if(dialog) { |
|
if(dialog.pFlags?.pinned) { |
|
delete dialog.pFlags.pinned; |
|
this.pinnedOrders[folder_id].findAndSplice(p => p === dialog.peerId); |
|
this.appStateManager.pushToState('pinnedOrders', this.pinnedOrders); |
|
} |
|
|
|
dialog.folder_id = folder_id; |
|
this.generateIndexForDialog(dialog); |
|
this.pushDialog(dialog); // need for simultaneously updatePinnedDialogs |
|
} |
|
|
|
this.appMessagesManager.scheduleHandleNewDialogs(peerId, dialog); |
|
}); |
|
}; |
|
|
|
private onUpdateDialogPinned = (update: Update.updateDialogPinned) => { |
|
const folderId = update.folder_id ?? 0; |
|
//this.log('updateDialogPinned', update); |
|
const peerId = this.appPeersManager.getPeerId((update.peer as DialogPeer.dialogPeer).peer); |
|
const dialog = this.getDialogOnly(peerId); |
|
|
|
// этот код внизу никогда не сработает, в папках за пиннед отвечает updateDialogFilter |
|
/* if(update.folder_id > 1) { |
|
const filter = this.filtersStorage.filters[update.folder_id]; |
|
if(update.pFlags.pinned) { |
|
filter.pinned_peers.unshift(peerId); |
|
} else { |
|
filter.pinned_peers.findAndSplice(p => p === peerId); |
|
} |
|
} */ |
|
|
|
if(dialog) { |
|
if(!update.pFlags.pinned) { |
|
delete dialog.pFlags.pinned; |
|
this.pinnedOrders[folderId].findAndSplice(p => p === dialog.peerId); |
|
this.appStateManager.pushToState('pinnedOrders', this.pinnedOrders); |
|
} else { // means set |
|
dialog.pFlags.pinned = true; |
|
} |
|
|
|
this.generateIndexForDialog(dialog); |
|
} |
|
|
|
this.appMessagesManager.scheduleHandleNewDialogs(peerId, dialog); |
|
}; |
|
|
|
private onUpdatePinnedDialogs = (update: Update.updatePinnedDialogs) => { |
|
const folderId = update.folder_id ?? 0; |
|
|
|
const handleOrder = (order: number[]) => { |
|
this.pinnedOrders[folderId].length = 0; |
|
order.reverse(); // index must be higher |
|
order.forEach((peerId) => { |
|
newPinned[peerId] = true; |
|
|
|
const dialog = this.getDialogOnly(peerId); |
|
this.appMessagesManager.scheduleHandleNewDialogs(peerId, dialog); |
|
if(!dialog) { |
|
return; |
|
} |
|
|
|
dialog.pFlags.pinned = true; |
|
this.generateIndexForDialog(dialog); |
|
}); |
|
|
|
this.getFolder(folderId, false).forEach(dialog => { |
|
const peerId = dialog.peerId; |
|
if(dialog.pFlags.pinned && !newPinned[peerId]) { |
|
this.appMessagesManager.scheduleHandleNewDialogs(peerId); |
|
} |
|
}); |
|
}; |
|
|
|
//this.log('updatePinnedDialogs', update); |
|
const newPinned: {[peerId: number]: true} = {}; |
|
if(!update.order) { |
|
apiManager.invokeApi('messages.getPinnedDialogs', { |
|
folder_id: folderId |
|
}).then((dialogsResult) => { |
|
// * for test reordering and rendering |
|
// dialogsResult.dialogs.reverse(); |
|
|
|
this.applyDialogs(dialogsResult); |
|
|
|
handleOrder(dialogsResult.dialogs.map(d => d.peerId)); |
|
|
|
/* dialogsResult.dialogs.forEach((dialog) => { |
|
newPinned[dialog.peerId] = true; |
|
}); |
|
|
|
this.dialogsStorage.getFolder(folderId).forEach((dialog) => { |
|
const peerId = dialog.peerId; |
|
if(dialog.pFlags.pinned && !newPinned[peerId]) { |
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
this.scheduleHandleNewDialogs(); |
|
} |
|
}); */ |
|
}); |
|
|
|
return; |
|
} |
|
|
|
//this.log('before order:', this.dialogsStorage[0].map(d => d.peerId)); |
|
|
|
handleOrder(update.order.map(peer => this.appPeersManager.getPeerId((peer as DialogPeer.dialogPeer).peer))); |
|
}; |
|
}
|
|
|