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.
5310 lines
174 KiB
5310 lines
174 KiB
import { LazyLoadQueueBase } from "../../components/lazyLoadQueue"; |
|
import ProgressivePreloader from "../../components/preloader"; |
|
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; |
|
import { tsNow } from "../../helpers/date"; |
|
import { createPosterForVideo } from "../../helpers/files"; |
|
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object"; |
|
import { randomLong } from "../../helpers/random"; |
|
import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string"; |
|
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update } from "../../layer"; |
|
import { InvokeApiOptions } from "../../types"; |
|
import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack"; |
|
import { logger, LogLevels } from "../logger"; |
|
import type { ApiFileManager } from '../mtproto/apiFileManager'; |
|
//import apiManager from '../mtproto/apiManager'; |
|
import apiManager from '../mtproto/mtprotoworker'; |
|
import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase"; |
|
import serverTimeManager from "../mtproto/serverTimeManager"; |
|
import { RichTextProcessor } from "../richtextprocessor"; |
|
import rootScope from "../rootScope"; |
|
import searchIndexManager from '../searchIndexManager'; |
|
import DialogsStorage from "../storages/dialogs"; |
|
import FiltersStorage from "../storages/filters"; |
|
//import { telegramMeWebService } from "../mtproto/mtproto"; |
|
import apiUpdatesManager from "./apiUpdatesManager"; |
|
import appChatsManager from "./appChatsManager"; |
|
import appDocsManager, { MyDocument } from "./appDocsManager"; |
|
import appDownloadManager from "./appDownloadManager"; |
|
import appPeersManager from "./appPeersManager"; |
|
import appPhotosManager, { MyPhoto } from "./appPhotosManager"; |
|
import appPollsManager from "./appPollsManager"; |
|
import appStateManager from "./appStateManager"; |
|
import appUsersManager from "./appUsersManager"; |
|
import appWebPagesManager from "./appWebPagesManager"; |
|
import appDraftsManager from "./appDraftsManager"; |
|
import pushHeavyTask from "../../helpers/heavyQueue"; |
|
import { getFileNameByLocation } from "../../helpers/fileName"; |
|
import appProfileManager from "./appProfileManager"; |
|
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; |
|
import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray"; |
|
import appNotificationsManager, { NotifyOptions } from "./appNotificationsManager"; |
|
import PeerTitle from "../../components/peerTitle"; |
|
import { forEachReverse } from "../../helpers/array"; |
|
import { htmlToDocumentFragment, htmlToSpan } from "../../helpers/dom"; |
|
|
|
//console.trace('include'); |
|
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет |
|
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках |
|
|
|
const APITIMEOUT = 0; |
|
|
|
export type HistoryStorage = { |
|
count: number | null, |
|
history: SlicedArray, |
|
|
|
maxId?: number, |
|
readPromise?: Promise<void>, |
|
readMaxId?: number, |
|
readOutboxMaxId?: number, |
|
|
|
maxOutId?: number, |
|
reply_markup?: any |
|
}; |
|
|
|
export type HistoryResult = { |
|
count: number, |
|
history: Slice, |
|
offsetIdOffset?: number, |
|
}; |
|
|
|
export type Dialog = MTDialog.dialog; |
|
|
|
export type MyMessage = Message.message | Message.messageService; |
|
export type MyInputMessagesFilter = 'inputMessagesFilterEmpty' |
|
| 'inputMessagesFilterPhotos' |
|
| 'inputMessagesFilterPhotoVideo' |
|
| 'inputMessagesFilterVideo' |
|
| 'inputMessagesFilterDocument' |
|
| 'inputMessagesFilterVoice' |
|
| 'inputMessagesFilterRoundVoice' |
|
| 'inputMessagesFilterRoundVideo' |
|
| 'inputMessagesFilterMusic' |
|
| 'inputMessagesFilterUrl' |
|
| 'inputMessagesFilterMyMentions' |
|
| 'inputMessagesFilterChatPhotos' |
|
| 'inputMessagesFilterPinned'; |
|
|
|
export type PinnedStorage = Partial<{ |
|
promise: Promise<PinnedStorage>, |
|
count: number, |
|
maxId: number |
|
}>; |
|
export type MessagesStorage = { |
|
//generateIndex: (message: any) => void |
|
[mid: string]: any |
|
}; |
|
|
|
export type MyMessageActionType = Message.messageService['action']['_']; |
|
|
|
export class AppMessagesManager { |
|
public static MESSAGE_ID_INCREMENT = 0x10000; |
|
public static MESSAGE_ID_OFFSET = 0xFFFFFFFF; |
|
|
|
public messagesStorageByPeerId: {[peerId: string]: MessagesStorage} = {}; |
|
public groupedMessagesStorage: {[groupId: string]: MessagesStorage} = {}; // will be used for albums |
|
public scheduledMessagesStorage: {[peerId: string]: MessagesStorage} = {}; |
|
public historiesStorage: { |
|
[peerId: string]: HistoryStorage |
|
} = {}; |
|
public threadsStorage: { |
|
[peerId: string]: { |
|
[threadId: string]: HistoryStorage |
|
} |
|
} = {}; |
|
public searchesStorage: { |
|
[peerId: string]: Partial<{ |
|
[inputFilter in MyInputMessagesFilter]: { |
|
count?: number, |
|
history: number[] |
|
} |
|
}> |
|
} = {}; |
|
public pinnedMessages: {[peerId: string]: PinnedStorage} = {}; |
|
|
|
public threadsServiceMessagesIdsStorage: {[peerId_threadId: string]: number} = {}; |
|
public threadsToReplies: { |
|
[peerId_threadId: string]: string; |
|
} = {}; |
|
|
|
public pendingByRandomId: { |
|
[randomId: string]: { |
|
peerId: number, |
|
tempId: number, |
|
storage: MessagesStorage |
|
} |
|
} = {}; |
|
public pendingByMessageId: {[mid: string]: string} = {}; |
|
public pendingAfterMsgs: any = {}; |
|
public pendingTopMsgs: {[peerId: string]: number} = {}; |
|
public sendFilePromise: CancellablePromise<void> = Promise.resolve(); |
|
public tempNum = 0; |
|
public tempFinalizeCallbacks: { |
|
[tempId: string]: { |
|
[callbackName: string]: Partial<{ |
|
deferred: CancellablePromise<void>, |
|
callback: (message: any) => Promise<any> |
|
}> |
|
} |
|
} = {}; |
|
|
|
public sendSmthLazyLoadQueue = new LazyLoadQueueBase(1); |
|
|
|
public needSingleMessages: {[peerId: string]: number[]} = {}; |
|
private fetchSingleMessagesPromise: Promise<void> = null; |
|
|
|
public maxSeenId = 0; |
|
|
|
public migratedFromTo: {[peerId: number]: number} = {}; |
|
public migratedToFrom: {[peerId: number]: number} = {}; |
|
|
|
public newMessagesHandlePromise = 0; |
|
public newMessagesToHandle: {[peerId: string]: number[]} = {}; |
|
public newDialogsHandlePromise = 0; |
|
public newDialogsToHandle: {[peerId: string]: {reload: true} | Dialog} = {}; |
|
public newUpdatesAfterReloadToHandle: {[peerId: string]: Set<any>} = {}; |
|
|
|
private notificationsHandlePromise = 0; |
|
private notificationsToHandle: {[peerId: string]: { |
|
fwdCount: number, |
|
fromId: number, |
|
topMessage?: MyMessage |
|
}} = {}; |
|
|
|
private reloadConversationsPromise: Promise<void>; |
|
private reloadConversationsPeers: number[] = []; |
|
|
|
private dialogsIndex = searchIndexManager.createIndex(); |
|
private cachedResults: { |
|
query: string, |
|
count: number, |
|
dialogs: Dialog[], |
|
folderId: number |
|
} = { |
|
query: '', |
|
count: 0, |
|
dialogs: [], |
|
folderId: 0 |
|
}; |
|
|
|
private log = logger('MESSAGES', LogLevels.error | LogLevels.debug | LogLevels.log | LogLevels.warn); |
|
|
|
public dialogsStorage: DialogsStorage; |
|
public filtersStorage: FiltersStorage; |
|
|
|
private groupedTempId = 0; |
|
|
|
constructor() { |
|
this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, serverTimeManager); |
|
this.filtersStorage = new FiltersStorage(appPeersManager, appUsersManager, /* apiManager, */ rootScope); |
|
|
|
rootScope.on('apiUpdate', (e) => { |
|
this.handleUpdate(e); |
|
}); |
|
|
|
// ! Invalidate notify settings, can optimize though |
|
rootScope.on('notify_peer_type_settings', ({key, settings}) => { |
|
this.getConversationsAll().then(dialogs => { |
|
let filterFunc: (dialog: Dialog) => boolean; |
|
if(key === 'notifyUsers') filterFunc = (dialog) => dialog.peerId > 0; |
|
else if(key === 'notifyBroadcasts') filterFunc = (dialog) => appChatsManager.isBroadcast(-dialog.peerId); |
|
else filterFunc = (dialog) => appPeersManager.isAnyGroup(dialog.peerId); |
|
|
|
dialogs |
|
.filter(filterFunc) |
|
.forEach(dialog => { |
|
rootScope.broadcast('dialog_notify_settings', dialog); |
|
}); |
|
}); |
|
}); |
|
|
|
rootScope.on('webpage_updated', (e) => { |
|
const eventData = e; |
|
eventData.msgs.forEach((mid) => { |
|
const message = this.getMessageById(mid) as Message.message; |
|
if(!message) return; |
|
message.media = { |
|
_: 'messageMediaWebPage', |
|
webpage: appWebPagesManager.getWebPage(eventData.id) |
|
}; |
|
|
|
const peerId = this.getMessagePeer(message); |
|
const storage = this.getMessagesStorage(peerId); |
|
rootScope.broadcast('message_edit', { |
|
storage, |
|
peerId, |
|
mid |
|
}); |
|
}); |
|
}); |
|
|
|
rootScope.on('draft_updated', (e) => { |
|
const {peerId, threadId, draft} = e; |
|
|
|
if(threadId) return; |
|
|
|
const dialog = this.getDialogByPeerId(peerId)[0]; |
|
if(dialog && !threadId) { |
|
dialog.draft = draft; |
|
this.dialogsStorage.generateIndexForDialog(dialog); |
|
this.dialogsStorage.pushDialog(dialog); |
|
|
|
rootScope.broadcast('dialog_draft', { |
|
peerId, |
|
draft, |
|
index: dialog.index |
|
}); |
|
} else { |
|
this.reloadConversation(peerId); |
|
} |
|
}); |
|
|
|
appStateManager.getState().then(state => { |
|
if(state.maxSeenMsgId) { |
|
this.maxSeenId = state.maxSeenMsgId; |
|
} |
|
|
|
const messages = state.messages; |
|
if(messages) { |
|
/* let tempId = this.tempId; |
|
|
|
for(let message of messages) { |
|
if(message.id < tempId) { |
|
tempId = message.id; |
|
} |
|
} |
|
|
|
if(tempId !== this.tempId) { |
|
this.log('Set tempId to:', tempId); |
|
this.tempId = tempId; |
|
} */ |
|
|
|
this.saveMessages(messages); |
|
} |
|
|
|
if(!state.dialogs || !Object.keys(state.dialogs).length) { |
|
state.allDialogsLoaded = {}; |
|
} |
|
|
|
if(state.allDialogsLoaded) { |
|
this.dialogsStorage.allDialogsLoaded = state.allDialogsLoaded; |
|
} |
|
|
|
if(state.filters) { |
|
for(const filterId in state.filters) { |
|
this.filtersStorage.saveDialogFilter(state.filters[filterId], false); |
|
} |
|
} |
|
|
|
if(state.dialogs) { |
|
forEachReverse(state.dialogs, dialog => { |
|
dialog.top_message = this.getServerMessageId(dialog.top_message); // * fix outgoing message to avoid copying dialog |
|
|
|
this.saveConversation(dialog); |
|
|
|
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись |
|
const message = this.getMessageByPeer(dialog.peerId, dialog.top_message); |
|
if(message.deleted) { |
|
this.reloadConversation(dialog.peerId); |
|
} |
|
}); |
|
} |
|
|
|
appStateManager.addEventListener('save', this.saveState); |
|
}); |
|
|
|
appNotificationsManager.start(); |
|
} |
|
|
|
private saveState = () => { |
|
const messages: any[] = []; |
|
const dialogs: Dialog[] = []; |
|
const items: any[] = []; |
|
|
|
const processDialog = (dialog: MTDialog.dialog) => { |
|
const historyStorage = this.getHistoryStorage(dialog.peerId); |
|
const history = [].concat(historyStorage.history.slice); |
|
//dialog = copy(dialog); |
|
let removeUnread = 0; |
|
for(const mid of history) { |
|
const message = this.getMessageByPeer(dialog.peerId, mid); |
|
if(/* message._ !== 'messageEmpty' && */!message.pFlags.is_outgoing) { |
|
messages.push(message); |
|
|
|
if(message.fromId !== dialog.peerId) { |
|
appStateManager.setPeer(message.fromId, appPeersManager.getPeer(message.fromId)); |
|
} |
|
|
|
/* dialog.top_message = message.mid; |
|
this.dialogsStorage.generateIndexForDialog(dialog, false, message); */ |
|
|
|
break; |
|
} else if(message.pFlags && message.pFlags.unread) { |
|
++removeUnread; |
|
} |
|
} |
|
|
|
if(removeUnread && dialog.unread_count) dialog.unread_count -= removeUnread; |
|
|
|
if(dialog.peerId < 0 && dialog.pts) { |
|
const newPts = apiUpdatesManager.channelStates[-dialog.peerId].pts; |
|
dialog.pts = newPts; |
|
} |
|
|
|
dialog.unread_count = Math.max(0, dialog.unread_count); |
|
dialogs.push(dialog); |
|
|
|
appStateManager.setPeer(dialog.peerId, appPeersManager.getPeer(dialog.peerId)); |
|
}; |
|
|
|
for(const folderId in this.dialogsStorage.byFolders) { |
|
const folder = this.dialogsStorage.getFolder(+folderId); |
|
|
|
for(let dialog of folder) { |
|
items.push([dialog]); |
|
} |
|
} |
|
|
|
return pushHeavyTask({ |
|
items, |
|
process: processDialog, |
|
context: this |
|
}).then(() => { |
|
appStateManager.pushToState('dialogs', dialogs); |
|
appStateManager.pushToState('messages', messages); |
|
appStateManager.pushToState('filters', this.filtersStorage.filters); |
|
appStateManager.pushToState('allDialogsLoaded', this.dialogsStorage.allDialogsLoaded); |
|
appStateManager.pushToState('maxSeenMsgId', this.maxSeenId); |
|
}); |
|
}; |
|
|
|
public getInputEntities(entities: MessageEntity[]) { |
|
var sendEntites = copy(entities); |
|
sendEntites.forEach((entity: any) => { |
|
if(entity._ === 'messageEntityMentionName') { |
|
entity._ = 'inputMessageEntityMentionName'; |
|
entity.user_id = appUsersManager.getUserInput(entity.user_id); |
|
} |
|
}); |
|
return sendEntites; |
|
} |
|
|
|
public invokeAfterMessageIsSent(tempId: number, callbackName: string, callback: (message: any) => Promise<any>) { |
|
const finalize = this.tempFinalizeCallbacks[tempId] ?? (this.tempFinalizeCallbacks[tempId] = {}); |
|
const obj = finalize[callbackName] ?? (finalize[callbackName] = {deferred: deferredPromise<void>()}); |
|
|
|
obj.callback = callback; |
|
|
|
return obj.deferred; |
|
} |
|
|
|
public editMessage(message: any, text: string, options: Partial<{ |
|
noWebPage: true, |
|
newMedia: any, |
|
scheduleDate: number, |
|
entities: MessageEntity[] |
|
}> = {}): Promise<void> { |
|
/* if(!this.canEditMessage(messageId)) { |
|
return Promise.reject({type: 'MESSAGE_EDIT_FORBIDDEN'}); |
|
} */ |
|
|
|
const {mid, peerId} = message; |
|
|
|
if(message.pFlags.is_outgoing) { |
|
return this.invokeAfterMessageIsSent(mid, 'edit', (message) => { |
|
//this.log('invoke editMessage callback', message); |
|
return this.editMessage(message, text, options); |
|
}); |
|
} |
|
|
|
let entities = options.entities || []; |
|
if(text) { |
|
text = RichTextProcessor.parseMarkdown(text, entities); |
|
} |
|
|
|
const schedule_date = options.scheduleDate || (message.pFlags.is_scheduled ? message.date : undefined); |
|
return apiManager.invokeApi('messages.editMessage', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
id: message.id, |
|
message: text, |
|
media: options.newMedia, |
|
entities: entities.length ? this.getInputEntities(entities) : undefined, |
|
no_webpage: options.noWebPage, |
|
schedule_date |
|
}).then((updates) => { |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}, (error) => { |
|
this.log.error('editMessage error:', error); |
|
|
|
if(error && error.type === 'MESSAGE_NOT_MODIFIED') { |
|
error.handled = true; |
|
return; |
|
} |
|
if(error && error.type === 'MESSAGE_EMPTY') { |
|
error.handled = true; |
|
} |
|
return Promise.reject(error); |
|
}); |
|
} |
|
|
|
public sendText(peerId: number, text: string, options: Partial<{ |
|
entities: any[], |
|
replyToMsgId: number, |
|
threadId: number, |
|
viaBotId: number, |
|
queryId: string, |
|
resultId: string, |
|
noWebPage: true, |
|
reply_markup: any, |
|
clearDraft: true, |
|
webPage: any, |
|
scheduleDate: number, |
|
silent: true |
|
}> = {}) { |
|
if(typeof(text) !== 'string' || !text.length) { |
|
return; |
|
} |
|
|
|
//this.checkSendOptions(options); |
|
|
|
if(options.threadId && !options.replyToMsgId) { |
|
options.replyToMsgId = options.threadId; |
|
} |
|
|
|
const MAX_LENGTH = 4096; |
|
if(text.length > MAX_LENGTH) { |
|
const splitted = splitStringByLength(text, MAX_LENGTH); |
|
text = splitted[0]; |
|
|
|
if(splitted.length > 1) { |
|
delete options.webPage; |
|
} |
|
|
|
for(let i = 1; i < splitted.length; ++i) { |
|
setTimeout(() => { |
|
this.sendText(peerId, splitted[i], options); |
|
}, i); |
|
} |
|
} |
|
|
|
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; |
|
|
|
let entities = options.entities || []; |
|
if(!options.viaBotId) { |
|
text = RichTextProcessor.parseMarkdown(text, entities); |
|
} |
|
|
|
let sendEntites = this.getInputEntities(entities); |
|
if(!sendEntites.length) { |
|
sendEntites = undefined; |
|
} |
|
|
|
const message = this.generateOutgoingMessage(peerId, options); |
|
message.entities = entities; |
|
message.message = text; |
|
|
|
const replyToMsgId = options.replyToMsgId ? this.getServerMessageId(options.replyToMsgId) : undefined; |
|
const isChannel = appPeersManager.isChannel(peerId); |
|
|
|
if(options.webPage) { |
|
message.media = { |
|
_: 'messageMediaWebPage', |
|
webpage: options.webPage |
|
}; |
|
} |
|
|
|
const toggleError = (on: any) => { |
|
if(on) { |
|
message.error = true; |
|
} else { |
|
delete message.error; |
|
} |
|
rootScope.broadcast('messages_pending'); |
|
}; |
|
|
|
message.send = () => { |
|
toggleError(false); |
|
const sentRequestOptions: InvokeApiOptions = {}; |
|
if(this.pendingAfterMsgs[peerId]) { |
|
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId; |
|
} |
|
|
|
let apiPromise: any; |
|
if(options.viaBotId) { |
|
apiPromise = apiManager.invokeApiAfter('messages.sendInlineBotResult', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
random_id: message.random_id, |
|
reply_to_msg_id: replyToMsgId || undefined, |
|
query_id: options.queryId, |
|
id: options.resultId, |
|
clear_draft: options.clearDraft |
|
}, sentRequestOptions); |
|
} else { |
|
apiPromise = apiManager.invokeApiAfter('messages.sendMessage', { |
|
no_webpage: options.noWebPage, |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
message: text, |
|
random_id: message.random_id, |
|
reply_to_msg_id: replyToMsgId || undefined, |
|
entities: sendEntites, |
|
clear_draft: options.clearDraft, |
|
schedule_date: options.scheduleDate || undefined, |
|
silent: options.silent |
|
}, sentRequestOptions); |
|
} |
|
|
|
//this.log('sendText', message.mid); |
|
apiPromise.then((updates: any) => { |
|
//this.log('sendText sent', message.mid); |
|
if(updates._ === 'updateShortSentMessage') { |
|
message.date = updates.date; |
|
message.id = updates.id; |
|
message.media = updates.media; |
|
message.entities = updates.entities; |
|
|
|
// * override with new updates |
|
updates = { |
|
_: 'updates', |
|
users: [], |
|
chats: [], |
|
seq: 0, |
|
updates: [{ |
|
_: 'updateMessageID', |
|
random_id: message.random_id, |
|
id: updates.id |
|
}, { |
|
_: options.scheduleDate ? 'updateNewScheduledMessage' : (isChannel ? 'updateNewChannelMessage' : 'updateNewMessage'), |
|
message: message, |
|
pts: updates.pts, |
|
pts_count: updates.pts_count |
|
}] |
|
}; |
|
} else if(updates.updates) { |
|
updates.updates.forEach((update: any) => { |
|
if(update._ === 'updateDraftMessage') { |
|
update.local = true; |
|
} |
|
}); |
|
} |
|
// Testing bad situations |
|
// var upd = angular.copy(updates) |
|
// updates.updates.splice(0, 1) |
|
|
|
apiUpdatesManager.processUpdateMessage(updates); |
|
|
|
// $timeout(function () { |
|
// ApiUpdatesManager.processUpdateMessage(upd) |
|
// }, 5000) |
|
}, (/* error: any */) => { |
|
toggleError(true); |
|
}).finally(() => { |
|
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) { |
|
delete this.pendingAfterMsgs[peerId]; |
|
} |
|
}); |
|
|
|
this.pendingAfterMsgs[peerId] = sentRequestOptions; |
|
} |
|
|
|
this.beforeMessageSending(message, { |
|
isScheduled: !!options.scheduleDate || undefined, |
|
threadId: options.threadId, |
|
clearDraft: options.clearDraft |
|
}); |
|
} |
|
|
|
public sendFile(peerId: number, file: File | Blob | MyDocument, options: Partial<{ |
|
isRoundMessage: true, |
|
isVoiceMessage: true, |
|
isGroupedItem: true, |
|
isMedia: true, |
|
|
|
replyToMsgId: number, |
|
threadId: number, |
|
groupId: string, |
|
caption: string, |
|
entities: MessageEntity[], |
|
width: number, |
|
height: number, |
|
objectURL: string, |
|
thumbBlob: Blob, |
|
thumbURL: string, |
|
duration: number, |
|
background: true, |
|
silent: true, |
|
clearDraft: true, |
|
scheduleDate: number, |
|
|
|
waveform: Uint8Array, |
|
}> = {}) { |
|
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; |
|
|
|
//this.checkSendOptions(options); |
|
|
|
const message = this.generateOutgoingMessage(peerId, options); |
|
const replyToMsgId = options.replyToMsgId ? this.getServerMessageId(options.replyToMsgId) : undefined; |
|
|
|
let attachType: string, apiFileName: string; |
|
|
|
const fileType = 'mime_type' in file ? file.mime_type : file.type; |
|
const fileName = file instanceof File ? file.name : ''; |
|
const isDocument = !(file instanceof File) && !(file instanceof Blob); |
|
let caption = options.caption || ''; |
|
|
|
this.log('sendFile', file, fileType); |
|
|
|
const entities = options.entities || []; |
|
if(caption) { |
|
caption = RichTextProcessor.parseMarkdown(caption, entities); |
|
} |
|
|
|
const attributes: DocumentAttribute[] = []; |
|
|
|
const isPhoto = ['image/jpeg', 'image/png', 'image/bmp'].indexOf(fileType) >= 0; |
|
|
|
let photo: MyPhoto, document: MyDocument; |
|
|
|
let actionName = ''; |
|
if(isDocument) { // maybe it's a sticker or gif |
|
attachType = 'document'; |
|
apiFileName = ''; |
|
} else if(fileType.indexOf('audio/') === 0 || ['video/ogg'].indexOf(fileType) >= 0) { |
|
attachType = 'audio'; |
|
apiFileName = 'audio.' + (fileType.split('/')[1] === 'ogg' ? 'ogg' : 'mp3'); |
|
actionName = 'sendMessageUploadAudioAction'; |
|
|
|
if(options.isVoiceMessage) { |
|
attachType = 'voice'; |
|
message.pFlags.media_unread = true; |
|
} |
|
|
|
let attribute: DocumentAttribute.documentAttributeAudio = { |
|
_: 'documentAttributeAudio', |
|
pFlags: { |
|
voice: options.isVoiceMessage |
|
}, |
|
waveform: options.waveform, |
|
duration: options.duration || 0 |
|
}; |
|
|
|
attributes.push(attribute); |
|
} else if(!options.isMedia) { |
|
attachType = 'document'; |
|
apiFileName = 'document.' + fileType.split('/')[1]; |
|
actionName = 'sendMessageUploadDocumentAction'; |
|
} else if(isPhoto) { |
|
attachType = 'photo'; |
|
apiFileName = 'photo.' + fileType.split('/')[1]; |
|
actionName = 'sendMessageUploadPhotoAction'; |
|
|
|
photo = { |
|
_: 'photo', |
|
id: '' + message.id, |
|
sizes: [{ |
|
_: 'photoSize', |
|
w: options.width, |
|
h: options.height, |
|
type: 'full', |
|
location: null, |
|
size: file.size |
|
}], |
|
w: options.width, |
|
h: options.height |
|
} as any; |
|
|
|
defineNotNumerableProperties(photo, ['downloaded', 'url']); |
|
photo.downloaded = file.size; |
|
photo.url = options.objectURL || ''; |
|
|
|
appPhotosManager.savePhoto(photo); |
|
} else if(fileType.indexOf('video/') === 0) { |
|
attachType = 'video'; |
|
apiFileName = 'video.mp4'; |
|
actionName = 'sendMessageUploadVideoAction'; |
|
|
|
let videoAttribute: DocumentAttribute.documentAttributeVideo = { |
|
_: 'documentAttributeVideo', |
|
pFlags: { |
|
round_message: options.isRoundMessage |
|
}, |
|
duration: options.duration, |
|
w: options.width, |
|
h: options.height |
|
}; |
|
|
|
attributes.push(videoAttribute); |
|
} else { |
|
attachType = 'document'; |
|
apiFileName = 'document.' + fileType.split('/')[1]; |
|
actionName = 'sendMessageUploadDocumentAction'; |
|
} |
|
|
|
attributes.push({_: 'documentAttributeFilename', file_name: fileName || apiFileName}); |
|
|
|
if(['document', 'video', 'audio', 'voice'].indexOf(attachType) !== -1 && !isDocument) { |
|
const thumbs: PhotoSize[] = []; |
|
document = { |
|
_: 'document', |
|
id: '' + message.id, |
|
duration: options.duration, |
|
attributes, |
|
w: options.width, |
|
h: options.height, |
|
thumbs, |
|
mime_type: fileType, |
|
size: file.size |
|
} as any; |
|
|
|
defineNotNumerableProperties(document, ['downloaded', 'url']); |
|
// @ts-ignore |
|
document.downloaded = file.size; |
|
document.url = options.objectURL || ''; |
|
|
|
if(isPhoto) { |
|
attributes.push({ |
|
_: 'documentAttributeImageSize', |
|
w: options.width, |
|
h: options.height |
|
}); |
|
|
|
thumbs.push({ |
|
_: 'photoSize', |
|
w: options.width, |
|
h: options.height, |
|
type: 'full', |
|
location: null, |
|
size: file.size, |
|
url: options.objectURL |
|
}); |
|
} else if(attachType === 'video') { |
|
if(options.thumbURL) { |
|
thumbs.push({ |
|
_: 'photoSize', |
|
w: options.width, |
|
h: options.height, |
|
type: 'full', |
|
location: null, |
|
size: options.thumbBlob.size, |
|
url: options.thumbURL |
|
}); |
|
} |
|
|
|
const thumb = thumbs[0] as PhotoSize.photoSize; |
|
const docThumb = appPhotosManager.getDocumentCachedThumb(document.id); |
|
docThumb.downloaded = thumb.size; |
|
docThumb.url = thumb.url; |
|
} |
|
|
|
/* if(thumbs.length) { |
|
const thumb = thumbs[0] as PhotoSize.photoSize; |
|
const docThumb = appPhotosManager.getDocumentCachedThumb(document.id); |
|
docThumb.downloaded = thumb.size; |
|
docThumb.url = thumb.url; |
|
} */ |
|
|
|
appDocsManager.saveDoc(document); |
|
} |
|
|
|
this.log('sendFile', attachType, apiFileName, file.type, options); |
|
|
|
const preloader = isDocument ? undefined : new ProgressivePreloader({ |
|
attachMethod: 'prepend', |
|
tryAgainOnFail: false, |
|
isUpload: true |
|
}); |
|
|
|
const sentDeferred = deferredPromise<InputMedia>(); |
|
|
|
if(preloader) { |
|
preloader.attachPromise(sentDeferred); |
|
sentDeferred.cancel = () => { |
|
const error = new Error('Download canceled'); |
|
error.name = 'AbortError'; |
|
sentDeferred.reject(error); |
|
}; |
|
|
|
sentDeferred.catch(err => { |
|
if(err.name === 'AbortError' && !uploaded) { |
|
this.log('cancelling upload', media); |
|
|
|
sentDeferred.reject(err); |
|
this.cancelPendingMessage(message.random_id); |
|
this.setTyping(peerId, 'sendMessageCancelAction'); |
|
|
|
if(uploadPromise?.cancel) { |
|
uploadPromise.cancel(); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
const media = isDocument ? undefined : { |
|
_: photo ? 'messageMediaPhoto' : 'messageMediaDocument', |
|
pFlags: {}, |
|
preloader, |
|
photo, |
|
document, |
|
promise: sentDeferred |
|
}; |
|
|
|
message.entities = entities; |
|
message.message = caption; |
|
message.media = isDocument ? { |
|
_: 'messageMediaDocument', |
|
pFlags: {}, |
|
document: file |
|
} : media; |
|
|
|
const toggleError = (on: boolean) => { |
|
if(on) { |
|
message.error = true; |
|
} else { |
|
delete message.error; |
|
} |
|
|
|
rootScope.broadcast('messages_pending'); |
|
}; |
|
|
|
let uploaded = false, |
|
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null; |
|
|
|
message.send = () => { |
|
if(isDocument) { |
|
const {id, access_hash, file_reference} = file as MyDocument; |
|
|
|
const inputMedia: InputMedia = { |
|
_: 'inputMediaDocument', |
|
id: { |
|
_: 'inputDocument', |
|
id, |
|
access_hash, |
|
file_reference |
|
} |
|
}; |
|
|
|
sentDeferred.resolve(inputMedia); |
|
} else if(file instanceof File || file instanceof Blob) { |
|
const load = () => { |
|
if(!uploaded || message.error) { |
|
uploaded = false; |
|
uploadPromise = appDownloadManager.upload(file); |
|
sentDeferred.notifyAll({done: 0, total: file.size}); |
|
} |
|
|
|
let thumbUploadPromise: typeof uploadPromise; |
|
if(attachType === 'video' && options.objectURL) { |
|
thumbUploadPromise = new Promise((resolve, reject) => { |
|
const blobPromise = options.thumbBlob ? Promise.resolve(options.thumbBlob) : createPosterForVideo(options.objectURL); |
|
blobPromise.then(blob => { |
|
if(!blob) { |
|
resolve(null); |
|
} else { |
|
appDownloadManager.upload(blob).then(resolve, reject); |
|
} |
|
}, reject); |
|
}); |
|
} |
|
|
|
uploadPromise && uploadPromise.then(async(inputFile) => { |
|
/* if(DEBUG) { |
|
this.log('appMessagesManager: sendFile uploaded:', inputFile); |
|
} */ |
|
|
|
delete message.media.preloader; |
|
|
|
inputFile.name = apiFileName; |
|
uploaded = true; |
|
let inputMedia: InputMedia; |
|
switch(attachType) { |
|
case 'photo': |
|
inputMedia = { |
|
_: 'inputMediaUploadedPhoto', |
|
file: inputFile |
|
}; |
|
break; |
|
|
|
default: |
|
inputMedia = { |
|
_: 'inputMediaUploadedDocument', |
|
file: inputFile, |
|
mime_type: fileType, |
|
attributes |
|
}; |
|
} |
|
|
|
if(thumbUploadPromise) { |
|
try { |
|
const inputFile = await thumbUploadPromise; |
|
(inputMedia as InputMedia.inputMediaUploadedDocument).thumb = inputFile; |
|
} catch(err) { |
|
this.log.error('sendFile thumb upload error:', err); |
|
} |
|
} |
|
|
|
sentDeferred.resolve(inputMedia); |
|
}, (/* error */) => { |
|
toggleError(true); |
|
}); |
|
|
|
uploadPromise.addNotifyListener((progress: {done: number, total: number}) => { |
|
/* if(DEBUG) { |
|
this.log('upload progress', progress); |
|
} */ |
|
|
|
const percents = Math.max(1, Math.floor(100 * progress.done / progress.total)); |
|
this.setTyping(peerId, {_: actionName, progress: percents | 0}); |
|
sentDeferred.notifyAll(progress); |
|
}); |
|
|
|
return sentDeferred; |
|
}; |
|
|
|
if(options.isGroupedItem) { |
|
load(); |
|
} else { |
|
this.sendSmthLazyLoadQueue.push({ |
|
load |
|
}); |
|
} |
|
} |
|
|
|
return sentDeferred; |
|
}; |
|
|
|
this.beforeMessageSending(message, { |
|
isGroupedItem: options.isGroupedItem, |
|
isScheduled: !!options.scheduleDate || undefined, |
|
threadId: options.threadId, |
|
clearDraft: options.clearDraft |
|
}); |
|
|
|
if(!options.isGroupedItem) { |
|
sentDeferred.then(inputMedia => { |
|
this.setTyping(peerId, 'sendMessageCancelAction'); |
|
|
|
return apiManager.invokeApi('messages.sendMedia', { |
|
background: options.background, |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
media: inputMedia, |
|
message: caption, |
|
random_id: message.random_id, |
|
reply_to_msg_id: replyToMsgId, |
|
schedule_date: options.scheduleDate, |
|
silent: options.silent, |
|
entities, |
|
clear_draft: options.clearDraft |
|
}).then((updates) => { |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}, (error) => { |
|
if(attachType === 'photo' && |
|
error.code === 400 && |
|
(error.type === 'PHOTO_INVALID_DIMENSIONS' || |
|
error.type === 'PHOTO_SAVE_FILE_INVALID')) { |
|
error.handled = true; |
|
attachType = 'document'; |
|
message.send(); |
|
return; |
|
} |
|
|
|
toggleError(true); |
|
}); |
|
}); |
|
} |
|
|
|
return {message, promise: sentDeferred}; |
|
} |
|
|
|
public async sendAlbum(peerId: number, files: File[], options: Partial<{ |
|
isMedia: true, |
|
entities: MessageEntity[], |
|
replyToMsgId: number, |
|
threadId: number, |
|
caption: string, |
|
sendFileDetails: Partial<{ |
|
duration: number, |
|
width: number, |
|
height: number, |
|
objectURL: string, |
|
thumbBlob: Blob, |
|
thumbURL: string |
|
}>[], |
|
silent: true, |
|
clearDraft: true, |
|
scheduleDate: number |
|
}> = {}) { |
|
//this.checkSendOptions(options); |
|
|
|
if(options.threadId && !options.replyToMsgId) { |
|
options.replyToMsgId = options.threadId; |
|
} |
|
|
|
if(files.length === 1) { |
|
return this.sendFile(peerId, files[0], {...options, ...options.sendFileDetails[0]}); |
|
} |
|
|
|
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; |
|
const replyToMsgId = options.replyToMsgId ? this.getServerMessageId(options.replyToMsgId) : undefined; |
|
|
|
let caption = options.caption || ''; |
|
let entities = options.entities || []; |
|
if(caption) { |
|
caption = RichTextProcessor.parseMarkdown(caption, entities); |
|
} |
|
|
|
this.log('sendAlbum', files, options); |
|
|
|
const groupId = '' + ++this.groupedTempId; |
|
|
|
const messages = files.map((file, idx) => { |
|
const details = options.sendFileDetails[idx]; |
|
const o: any = { |
|
isGroupedItem: true, |
|
isMedia: options.isMedia, |
|
scheduleDate: options.scheduleDate, |
|
silent: options.silent, |
|
replyToMsgId, |
|
threadId: options.threadId, |
|
groupId, |
|
...details |
|
}; |
|
|
|
if(idx === 0) { |
|
o.caption = caption; |
|
o.entities = entities; |
|
//o.replyToMsgId = replyToMsgId; |
|
} |
|
|
|
return this.sendFile(peerId, file, o).message; |
|
}); |
|
|
|
if(options.clearDraft) { |
|
appDraftsManager.syncDraft(peerId, options.threadId); |
|
} |
|
|
|
// * test pending |
|
//return; |
|
|
|
const toggleError = (message: any, on: boolean) => { |
|
if(on) { |
|
message.error = true; |
|
} else { |
|
delete message.error; |
|
} |
|
|
|
rootScope.broadcast('messages_pending'); |
|
}; |
|
|
|
const inputPeer = appPeersManager.getInputPeerById(peerId); |
|
const invoke = (multiMedia: any[]) => { |
|
this.setTyping(peerId, 'sendMessageCancelAction'); |
|
|
|
this.sendSmthLazyLoadQueue.push({ |
|
load: () => { |
|
return apiManager.invokeApi('messages.sendMultiMedia', { |
|
peer: inputPeer, |
|
multi_media: multiMedia, |
|
reply_to_msg_id: replyToMsgId, |
|
schedule_date: options.scheduleDate, |
|
silent: options.silent, |
|
clear_draft: options.clearDraft |
|
}).then((updates) => { |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}, (error) => { |
|
messages.forEach(message => toggleError(message, true)); |
|
}); |
|
} |
|
}); |
|
}; |
|
|
|
const promises: Promise<InputSingleMedia>[] = messages.map((message, idx) => { |
|
return (message.send() as Promise<InputMedia>).then((inputMedia: InputMedia) => { |
|
return apiManager.invokeApi('messages.uploadMedia', { |
|
peer: inputPeer, |
|
media: inputMedia |
|
}); |
|
}) |
|
.then(messageMedia => { |
|
let inputMedia: any; |
|
if(messageMedia._ === 'messageMediaPhoto') { |
|
const photo = appPhotosManager.savePhoto(messageMedia.photo); |
|
inputMedia = appPhotosManager.getInput(photo); |
|
} else if(messageMedia._ === 'messageMediaDocument') { |
|
const doc = appDocsManager.saveDoc(messageMedia.document); |
|
inputMedia = appDocsManager.getMediaInput(doc); |
|
} |
|
|
|
const inputSingleMedia: InputSingleMedia = { |
|
_: 'inputSingleMedia', |
|
media: inputMedia, |
|
random_id: message.random_id, |
|
message: caption, |
|
entities |
|
}; |
|
|
|
// * only 1 caption for all inputs |
|
if(caption) { |
|
caption = ''; |
|
entities = []; |
|
} |
|
|
|
return inputSingleMedia; |
|
}).catch((err: any) => { |
|
if(err.name === 'AbortError') { |
|
return null; |
|
} |
|
|
|
this.log.error('sendAlbum upload item error:', err, message); |
|
toggleError(message, true); |
|
throw err; |
|
}); |
|
}); |
|
|
|
Promise.all(promises).then(inputs => { |
|
invoke(inputs.filter(Boolean)); |
|
}); |
|
} |
|
|
|
public sendOther(peerId: number, inputMedia: any, options: Partial<{ |
|
replyToMsgId: number, |
|
threadId: number, |
|
viaBotId: number, |
|
reply_markup: any, |
|
clearDraft: true, |
|
queryId: string |
|
resultId: string, |
|
scheduleDate: number, |
|
silent: true |
|
}> = {}) { |
|
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; |
|
|
|
//this.checkSendOptions(options); |
|
const message = this.generateOutgoingMessage(peerId, options); |
|
const replyToMsgId = options.replyToMsgId ? this.getServerMessageId(options.replyToMsgId) : undefined; |
|
|
|
let media; |
|
switch(inputMedia._) { |
|
case 'inputMediaPoll': { |
|
inputMedia.poll.id = message.id; |
|
appPollsManager.savePoll(inputMedia.poll, { |
|
_: 'pollResults', |
|
flags: 4, |
|
total_voters: 0, |
|
pFlags: {}, |
|
}); |
|
|
|
const {poll, results} = appPollsManager.getPoll('' + message.id); |
|
media = { |
|
_: 'messageMediaPoll', |
|
poll, |
|
results |
|
}; |
|
|
|
break; |
|
} |
|
/* case 'inputMediaPhoto': |
|
media = { |
|
_: 'messageMediaPhoto', |
|
photo: appPhotosManager.getPhoto(inputMedia.id.id), |
|
caption: inputMedia.caption || '' |
|
}; |
|
break; |
|
|
|
case 'inputMediaDocument': |
|
var doc = appDocsManager.getDoc(inputMedia.id.id); |
|
if(doc.sticker && doc.stickerSetInput) { |
|
appStickersManager.pushPopularSticker(doc.id); |
|
} |
|
media = { |
|
_: 'messageMediaDocument', |
|
'document': doc, |
|
caption: inputMedia.caption || '' |
|
}; |
|
break; |
|
|
|
case 'inputMediaContact': |
|
media = { |
|
_: 'messageMediaContact', |
|
phone_number: inputMedia.phone_number, |
|
first_name: inputMedia.first_name, |
|
last_name: inputMedia.last_name, |
|
user_id: 0 |
|
}; |
|
break; |
|
|
|
case 'inputMediaGeoPoint': |
|
media = { |
|
_: 'messageMediaGeo', |
|
geo: { |
|
_: 'geoPoint', |
|
'lat': inputMedia.geo_point['lat'], |
|
'long': inputMedia.geo_point['long'] |
|
} |
|
}; |
|
break; |
|
|
|
case 'inputMediaVenue': |
|
media = { |
|
_: 'messageMediaVenue', |
|
geo: { |
|
_: 'geoPoint', |
|
'lat': inputMedia.geo_point['lat'], |
|
'long': inputMedia.geo_point['long'] |
|
}, |
|
title: inputMedia.title, |
|
address: inputMedia.address, |
|
provider: inputMedia.provider, |
|
venue_id: inputMedia.venue_id |
|
}; |
|
break; |
|
|
|
case 'messageMediaPending': |
|
media = inputMedia; |
|
break; */ |
|
} |
|
|
|
message.media = media; |
|
|
|
let toggleError = (on: boolean) => { |
|
/* const historyMessage = this.messagesForHistory[messageId]; |
|
if (on) { |
|
message.error = true |
|
if (historyMessage) { |
|
historyMessage.error = true |
|
} |
|
} else { |
|
delete message.error |
|
if (historyMessage) { |
|
delete historyMessage.error |
|
} |
|
} */ |
|
rootScope.broadcast('messages_pending'); |
|
}; |
|
|
|
message.send = () => { |
|
const sentRequestOptions: any = {}; |
|
if(this.pendingAfterMsgs[peerId]) { |
|
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId; |
|
} |
|
|
|
let apiPromise: Promise<any>; |
|
if(options.viaBotId) { |
|
apiPromise = apiManager.invokeApiAfter('messages.sendInlineBotResult', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
random_id: message.random_id, |
|
reply_to_msg_id: replyToMsgId || undefined, |
|
query_id: options.queryId, |
|
id: options.resultId, |
|
clear_draft: options.clearDraft |
|
}, sentRequestOptions); |
|
} else { |
|
apiPromise = apiManager.invokeApiAfter('messages.sendMedia', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
media: inputMedia, |
|
random_id: message.random_id, |
|
reply_to_msg_id: replyToMsgId || undefined, |
|
message: '', |
|
clear_draft: options.clearDraft, |
|
schedule_date: options.scheduleDate, |
|
silent: options.silent |
|
}, sentRequestOptions); |
|
} |
|
|
|
apiPromise.then((updates) => { |
|
if(updates.updates) { |
|
updates.updates.forEach((update: any) => { |
|
if(update._ === 'updateDraftMessage') { |
|
update.local = true |
|
} |
|
}); |
|
} |
|
|
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}, (error) => { |
|
toggleError(true); |
|
}).finally(() => { |
|
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) { |
|
delete this.pendingAfterMsgs[peerId]; |
|
} |
|
}); |
|
this.pendingAfterMsgs[peerId] = sentRequestOptions; |
|
} |
|
|
|
this.beforeMessageSending(message, { |
|
isScheduled: !!options.scheduleDate || undefined, |
|
threadId: options.threadId, |
|
clearDraft: options.clearDraft |
|
}); |
|
} |
|
|
|
/* private checkSendOptions(options: Partial<{ |
|
scheduleDate: number |
|
}>) { |
|
if(options.scheduleDate) { |
|
const minTimestamp = (Date.now() / 1000 | 0) + 10; |
|
if(options.scheduleDate <= minTimestamp) { |
|
delete options.scheduleDate; |
|
} |
|
} |
|
} */ |
|
|
|
private beforeMessageSending(message: any, options: Partial<{ |
|
isGroupedItem: true, |
|
isScheduled: true, |
|
threadId: number, |
|
clearDraft: true |
|
}> = {}) { |
|
const messageId = message.id; |
|
const peerId = this.getMessagePeer(message); |
|
const storage = options.isScheduled ? this.getScheduledMessagesStorage(peerId) : this.getMessagesStorage(peerId); |
|
|
|
if(options.isScheduled) { |
|
//if(!options.isGroupedItem) { |
|
this.saveMessages([message], {storage, isScheduled: true, isOutgoing: true}); |
|
setTimeout(() => { |
|
rootScope.broadcast('scheduled_new', {peerId, mid: messageId}); |
|
}, 0); |
|
} else { |
|
if(options.threadId && this.threadsStorage[peerId]) { |
|
delete this.threadsStorage[peerId][options.threadId]; |
|
} |
|
//if(options.threadId) { |
|
const historyStorage = this.getHistoryStorage(peerId/* , options.threadId */); |
|
historyStorage.history.unshift(messageId); |
|
//} |
|
|
|
/* const historyStorage = this.getHistoryStorage(peerId); |
|
historyStorage.history.unshift(messageId); */ |
|
|
|
//if(!options.isGroupedItem) { |
|
this.saveMessages([message], {storage, isOutgoing: true}); |
|
setTimeout(() => { |
|
this.setDialogTopMessage(message); |
|
rootScope.broadcast('history_append', {peerId, messageId, my: true}); |
|
}, 0); |
|
} |
|
|
|
if(!options.isGroupedItem && options.clearDraft && !options.threadId) { |
|
appDraftsManager.syncDraft(peerId, options.threadId); |
|
} |
|
|
|
this.pendingByRandomId[message.random_id] = {peerId, tempId: messageId, storage}; |
|
|
|
if(!options.isGroupedItem && message.send) { |
|
setTimeout(message.send, 0); |
|
//setTimeout(message.send, 4000); |
|
//setTimeout(message.send, 7000); |
|
} |
|
} |
|
|
|
private generateOutgoingMessage(peerId: number, options: Partial<{ |
|
scheduleDate: number, |
|
replyToMsgId: number, |
|
threadId: number, |
|
viaBotId: number, |
|
groupId: string, |
|
reply_markup: any, |
|
}>) { |
|
if(options.threadId && !options.replyToMsgId) { |
|
options.replyToMsgId = options.threadId; |
|
} |
|
|
|
const message: any = { |
|
_: 'message', |
|
id: this.generateTempMessageId(peerId), |
|
from_id: this.generateFromId(peerId), |
|
peer_id: appPeersManager.getOutputPeer(peerId), |
|
pFlags: this.generateFlags(peerId), |
|
date: options.scheduleDate || (tsNow(true) + serverTimeManager.serverTimeOffset), |
|
message: '', |
|
grouped_id: options.groupId, |
|
random_id: randomLong(), |
|
reply_to: this.generateReplyHeader(options.replyToMsgId, options.threadId), |
|
via_bot_id: options.viaBotId, |
|
reply_markup: options.reply_markup, |
|
replies: this.generateReplies(peerId), |
|
views: appPeersManager.isBroadcast(peerId) && 1, |
|
pending: true, |
|
}; |
|
|
|
return message; |
|
} |
|
|
|
private generateReplyHeader(replyToMsgId: number, replyToTopId?: number) { |
|
const header = { |
|
_: 'messageReplyHeader', |
|
reply_to_msg_id: replyToMsgId || replyToTopId, |
|
} as MessageReplyHeader; |
|
|
|
if(replyToTopId && header.reply_to_msg_id !== replyToTopId) { |
|
header.reply_to_top_id = replyToTopId; |
|
} |
|
|
|
return header; |
|
} |
|
|
|
private generateReplies(peerId: number) { |
|
let replies: MessageReplies.messageReplies; |
|
if(appPeersManager.isBroadcast(peerId)) { |
|
const channelFull = appProfileManager.chatsFull[-peerId] as ChatFull.channelFull; |
|
if(channelFull?.linked_chat_id) { |
|
replies = { |
|
_: 'messageReplies', |
|
flags: 1, |
|
pFlags: { |
|
comments: true |
|
}, |
|
channel_id: channelFull.linked_chat_id, |
|
replies: 0, |
|
replies_pts: 0 |
|
}; |
|
} |
|
} |
|
|
|
return replies; |
|
} |
|
|
|
/** |
|
* Generate correct from_id according to anonymous or broadcast |
|
*/ |
|
private generateFromId(peerId: number) { |
|
if(peerId < 0 && (appPeersManager.isBroadcast(peerId) || appPeersManager.getPeer(peerId).admin_rights?.pFlags?.anonymous)) { |
|
return undefined; |
|
} else { |
|
return appPeersManager.getOutputPeer(appUsersManager.getSelf().id); |
|
} |
|
} |
|
|
|
private generateFlags(peerId: number) { |
|
const pFlags: any = {}; |
|
const fromId = appUsersManager.getSelf().id; |
|
if(peerId !== fromId) { |
|
pFlags.out = true; |
|
|
|
if(!appPeersManager.isChannel(peerId) && !appUsersManager.isBot(peerId)) { |
|
pFlags.unread = true; |
|
} |
|
} |
|
|
|
if(appPeersManager.isBroadcast(peerId)) { |
|
pFlags.post = true; |
|
} |
|
|
|
return pFlags; |
|
} |
|
|
|
private generateForwardHeader(peerId: number, originalMessage: Message.message) { |
|
const myId = appUsersManager.getSelf().id; |
|
if(originalMessage.fromId === myId && originalMessage.peerId === myId && !originalMessage.fwd_from) { |
|
return; |
|
} |
|
|
|
const fwdHeader: MessageFwdHeader.messageFwdHeader = { |
|
_: 'messageFwdHeader', |
|
flags: 0, |
|
date: originalMessage.date |
|
}; |
|
|
|
if(originalMessage.fwd_from) { |
|
fwdHeader.from_id = originalMessage.fwd_from.from_id; |
|
fwdHeader.from_name = originalMessage.fwd_from.from_name; |
|
fwdHeader.post_author = originalMessage.fwd_from.post_author; |
|
} else { |
|
fwdHeader.from_id = appPeersManager.getOutputPeer(originalMessage.fromId); |
|
fwdHeader.post_author = originalMessage.post_author; |
|
} |
|
|
|
if(appPeersManager.isBroadcast(originalMessage.peerId)) { |
|
if(originalMessage.post_author) { |
|
fwdHeader.post_author = originalMessage.post_author; |
|
} |
|
|
|
fwdHeader.channel_post = originalMessage.id; |
|
} |
|
|
|
// * there is no way to detect whether user profile is hidden |
|
if(peerId === myId) { |
|
fwdHeader.saved_from_msg_id = originalMessage.id; |
|
fwdHeader.saved_from_peer = appPeersManager.getOutputPeer(originalMessage.peerId); |
|
} |
|
|
|
return fwdHeader; |
|
} |
|
|
|
public setDialogTopMessage(message: MyMessage, dialog: MTDialog.dialog = this.getDialogByPeerId(message.peerId)[0]) { |
|
if(dialog) { |
|
dialog.top_message = message.mid; |
|
|
|
const historyStorage = this.getHistoryStorage(message.peerId); |
|
historyStorage.maxId = message.mid; |
|
|
|
this.dialogsStorage.generateIndexForDialog(dialog, false, message); |
|
|
|
this.newDialogsToHandle[message.peerId] = dialog; |
|
this.scheduleHandleNewDialogs(); |
|
} |
|
} |
|
|
|
public cancelPendingMessage(randomId: string) { |
|
const pendingData = this.pendingByRandomId[randomId]; |
|
|
|
/* if(DEBUG) { |
|
this.log('cancelPendingMessage', randomId, pendingData); |
|
} */ |
|
|
|
if(pendingData) { |
|
const {peerId, tempId, storage} = pendingData; |
|
const historyStorage = this.getHistoryStorage(peerId); |
|
const pos = historyStorage.history.findSlice(tempId); |
|
|
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateDeleteMessages', |
|
messages: [tempId] |
|
} |
|
}); |
|
|
|
if(pos) { |
|
pos.slice.splice(pos.index, 1); |
|
} |
|
|
|
delete this.pendingByRandomId[randomId]; |
|
delete storage[tempId]; |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async refreshConversations() { |
|
const limit = 100, outDialogs: Dialog[] = []; |
|
for(let folderId = 0; folderId < 2; ++folderId) { |
|
let offsetDate = 0; |
|
for(;;) { |
|
const {dialogs} = await appMessagesManager.getTopMessages(limit, folderId, offsetDate); |
|
|
|
if(dialogs.length) { |
|
outDialogs.push(...dialogs as Dialog[]); |
|
const dialog = dialogs[dialogs.length - 1]; |
|
|
|
// * get peerId and mid manually, because dialog can be migrated peer and it won't be saved |
|
const peerId = appPeersManager.getPeerId(dialog.peer); |
|
const mid = this.generateMessageId(dialog.top_message); |
|
offsetDate = this.getMessageByPeer(peerId, mid).date; |
|
|
|
if(!offsetDate) { |
|
console.error('refreshConversations: got no offsetDate', dialog); |
|
break; |
|
} |
|
} else { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
let obj: {[peerId: string]: Dialog} = {}; |
|
outDialogs.forEach(dialog => { |
|
obj[dialog.peerId] = dialog; |
|
}); |
|
rootScope.broadcast('dialogs_multiupdate', obj); |
|
|
|
return outDialogs; |
|
} |
|
|
|
public async getConversationsAll(query = '', folderId = 0) { |
|
const limit = 100, outDialogs: Dialog[] = []; |
|
for(; folderId < 2; ++folderId) { |
|
let offsetIndex = 0; |
|
for(;;) { |
|
const {dialogs} = await appMessagesManager.getConversations(query, offsetIndex, limit, folderId); |
|
|
|
if(dialogs.length) { |
|
outDialogs.push(...dialogs); |
|
offsetIndex = dialogs[dialogs.length - 1].index || 0; |
|
} else { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return outDialogs; |
|
} |
|
|
|
public getConversations(query = '', offsetIndex?: number, limit = 20, folderId = 0) { |
|
const realFolderId = folderId > 1 ? 0 : folderId; |
|
let curDialogStorage = this.dialogsStorage.getFolder(folderId); |
|
|
|
if(query) { |
|
if(!limit || this.cachedResults.query !== query || this.cachedResults.folderId !== folderId) { |
|
this.cachedResults.query = query; |
|
this.cachedResults.folderId = folderId; |
|
|
|
const results = searchIndexManager.search(query, this.dialogsIndex); |
|
|
|
this.cachedResults.dialogs = []; |
|
|
|
for(const peerId in this.dialogsStorage.dialogs) { |
|
const dialog = this.dialogsStorage.dialogs[peerId]; |
|
if(results[dialog.peerId] && dialog.folder_id === folderId) { |
|
this.cachedResults.dialogs.push(dialog); |
|
} |
|
} |
|
|
|
this.cachedResults.dialogs.sort((d1, d2) => d2.index - d1.index); |
|
|
|
this.cachedResults.count = this.cachedResults.dialogs.length; |
|
} |
|
|
|
curDialogStorage = this.cachedResults.dialogs; |
|
} else { |
|
this.cachedResults.query = ''; |
|
} |
|
|
|
let offset = 0; |
|
if(offsetIndex > 0) { |
|
for(; offset < curDialogStorage.length; offset++) { |
|
if(offsetIndex > curDialogStorage[offset].index) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if(query || this.dialogsStorage.allDialogsLoaded[realFolderId] || curDialogStorage.length >= offset + limit) { |
|
return Promise.resolve({ |
|
dialogs: curDialogStorage.slice(offset, offset + limit), |
|
count: this.dialogsStorage.allDialogsLoaded[realFolderId] ? curDialogStorage.length : null, |
|
isEnd: this.dialogsStorage.allDialogsLoaded[realFolderId] && (offset + limit) >= curDialogStorage.length |
|
}); |
|
} |
|
|
|
return this.getTopMessages(limit, realFolderId).then(messagesDialogs => { |
|
//const curDialogStorage = this.dialogsStorage[folderId]; |
|
|
|
offset = 0; |
|
if(offsetIndex > 0) { |
|
for(; offset < curDialogStorage.length; offset++) { |
|
if(offsetIndex > curDialogStorage[offset].index) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogsStorage.dialogs.length); |
|
|
|
return { |
|
dialogs: curDialogStorage.slice(offset, offset + limit), |
|
count: messagesDialogs._ === 'messages.dialogs' ? messagesDialogs.dialogs.length : messagesDialogs.count, |
|
isEnd: this.dialogsStorage.allDialogsLoaded[realFolderId] && (offset + limit) >= curDialogStorage.length |
|
}; |
|
}); |
|
} |
|
|
|
public getReadMaxIdIfUnread(peerId: number, threadId?: number) { |
|
const historyStorage = this.getHistoryStorage(peerId, threadId); |
|
if(threadId) { |
|
const chatHistoryStorage = this.getHistoryStorage(peerId); |
|
const readMaxId = Math.max(chatHistoryStorage.readMaxId, historyStorage.readMaxId); |
|
const message = this.getMessageByPeer(peerId, historyStorage.maxId); |
|
return !message.pFlags.out && readMaxId < historyStorage.maxId ? readMaxId : 0; |
|
} else { |
|
const message = this.getMessageByPeer(peerId, historyStorage.maxId); |
|
const maxId = peerId > 0 ? Math.max(historyStorage.readMaxId, historyStorage.readOutboxMaxId) : historyStorage.readMaxId; |
|
return !message.pFlags.out && maxId < historyStorage.maxId ? maxId : 0; |
|
} |
|
} |
|
|
|
public getTopMessages(limit: number, folderId: number, offsetDate?: number) { |
|
const dialogs = this.dialogsStorage.getFolder(folderId); |
|
let offsetId = 0; |
|
let offsetPeerId = 0; |
|
let offsetIndex = 0; |
|
|
|
if(offsetDate === undefined) { |
|
offsetDate = this.dialogsStorage.getOffsetDate(folderId); |
|
} |
|
|
|
if(offsetDate) { |
|
offsetIndex = offsetDate * 0x10000; |
|
offsetDate += serverTimeManager.serverTimeOffset; |
|
} |
|
|
|
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА: |
|
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ??? |
|
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0 |
|
return apiManager.invokeApi('messages.getDialogs', { |
|
folder_id: folderId, |
|
offset_date: offsetDate, |
|
offset_id: offsetId, |
|
offset_peer: appPeersManager.getInputPeerById(offsetPeerId), |
|
limit, |
|
hash: 0 |
|
}, { |
|
//timeout: APITIMEOUT, |
|
noErrorBox: true |
|
}).then((dialogsResult) => { |
|
if(dialogsResult._ === 'messages.dialogsNotModified') return null; |
|
|
|
if(DEBUG) { |
|
this.log('messages.getDialogs result:', dialogsResult.dialogs, {...dialogsResult.dialogs[0]}); |
|
} |
|
|
|
/* if(!offsetDate) { |
|
telegramMeWebService.setAuthorized(true); |
|
} */ |
|
|
|
appUsersManager.saveApiUsers(dialogsResult.users); |
|
appChatsManager.saveApiChats(dialogsResult.chats); |
|
this.saveMessages(dialogsResult.messages); |
|
|
|
let maxSeenIdIncremented = offsetDate ? true : false; |
|
let hasPrepend = false; |
|
const noIdsDialogs: {[peerId: number]: Dialog} = {}; |
|
forEachReverse((dialogsResult.dialogs as Dialog[]), dialog => { |
|
//const d = Object.assign({}, dialog); |
|
// ! нужно передавать folderId, так как по папке !== 0 нет свойства folder_id |
|
this.saveConversation(dialog, dialog.folder_id ?? folderId); |
|
|
|
if(dialog.peerId === undefined) { |
|
return; |
|
} |
|
|
|
/* if(dialog.peerId === -1213511294) { |
|
this.log.error('lun bot', folderId, d); |
|
} */ |
|
|
|
if(offsetIndex && dialog.index > offsetIndex) { |
|
this.newDialogsToHandle[dialog.peerId] = dialog; |
|
hasPrepend = true; |
|
} |
|
|
|
// ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет |
|
// ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога |
|
if(!this.getServerMessageId(dialog.read_inbox_max_id) && !this.getServerMessageId(dialog.read_outbox_max_id)) { |
|
noIdsDialogs[dialog.peerId] = dialog; |
|
|
|
this.log.error('noIdsDialogs', dialog); |
|
|
|
/* if(dialog.peerId === -1213511294) { |
|
this.log.error('lun bot', folderId); |
|
} */ |
|
} |
|
|
|
if(!maxSeenIdIncremented && |
|
!appPeersManager.isChannel(appPeersManager.getPeerId(dialog.peer))) { |
|
this.incrementMaxSeenId(dialog.top_message); |
|
maxSeenIdIncremented = true; |
|
} |
|
}); |
|
|
|
if(Object.keys(noIdsDialogs).length) { |
|
//setTimeout(() => { // test bad situation |
|
this.reloadConversation(Object.keys(noIdsDialogs).map(id => +id)).then(() => { |
|
rootScope.broadcast('dialogs_multiupdate', noIdsDialogs); |
|
|
|
for(let peerId in noIdsDialogs) { |
|
rootScope.broadcast('dialog_unread', {peerId: +peerId}); |
|
} |
|
}); |
|
//}, 10e3); |
|
} |
|
|
|
const count = (dialogsResult as MessagesDialogs.messagesDialogsSlice).count; |
|
|
|
if(!dialogsResult.dialogs.length || |
|
!count || |
|
dialogs.length >= count) { |
|
this.dialogsStorage.allDialogsLoaded[folderId] = true; |
|
} |
|
|
|
if(hasPrepend) { |
|
this.scheduleHandleNewDialogs(); |
|
} else { |
|
rootScope.broadcast('dialogs_multiupdate', {}); |
|
} |
|
|
|
return dialogsResult; |
|
}); |
|
} |
|
|
|
public forwardMessages(peerId: number, fromPeerId: number, mids: number[], options: Partial<{ |
|
withMyScore: true, |
|
silent: true, |
|
scheduleDate: number |
|
}> = {}) { |
|
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; |
|
mids = mids.slice().sort((a, b) => a - b); |
|
|
|
const groups: { |
|
[groupId: string]: { |
|
tempId: string, |
|
messages: any[] |
|
} |
|
} = {}; |
|
|
|
const newMessages = mids.map(mid => { |
|
const originalMessage = this.getMessageByPeer(fromPeerId, mid); |
|
const message = this.generateOutgoingMessage(peerId, options); |
|
message.fwd_from = this.generateForwardHeader(peerId, originalMessage); |
|
|
|
(['entities', 'forwards', 'message', 'media', 'reply_markup', 'views'] as any as Array<keyof MyMessage>).forEach(key => { |
|
message[key] = originalMessage[key]; |
|
}); |
|
|
|
if(originalMessage.grouped_id) { |
|
const group = groups[originalMessage.grouped_id] ?? (groups[originalMessage.grouped_id] = {tempId: '' + ++this.groupedTempId, messages: []}); |
|
group.messages.push(message); |
|
} |
|
|
|
return message; |
|
}); |
|
|
|
for(const groupId in groups) { |
|
const group = groups[groupId]; |
|
if(group.messages.length > 1) { |
|
group.messages.forEach(message => { |
|
message.grouped_id = group.tempId; |
|
}); |
|
} |
|
} |
|
|
|
newMessages.forEach(message => { |
|
this.beforeMessageSending(message, { |
|
isScheduled: !!options.scheduleDate || undefined |
|
}); |
|
}); |
|
|
|
const sentRequestOptions: InvokeApiOptions = {}; |
|
if(this.pendingAfterMsgs[peerId]) { |
|
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId; |
|
} |
|
|
|
const promise = /* true ? Promise.resolve() : */apiManager.invokeApiAfter('messages.forwardMessages', { |
|
from_peer: appPeersManager.getInputPeerById(fromPeerId), |
|
id: mids.map(mid => this.getServerMessageId(mid)), |
|
random_id: newMessages.map(message => message.random_id), |
|
to_peer: appPeersManager.getInputPeerById(peerId), |
|
with_my_score: options.withMyScore, |
|
silent: options.silent, |
|
schedule_date: options.scheduleDate |
|
}, sentRequestOptions).then((updates) => { |
|
this.log('forwardMessages updates:', updates); |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}).finally(() => { |
|
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) { |
|
delete this.pendingAfterMsgs[peerId]; |
|
} |
|
}); |
|
|
|
this.pendingAfterMsgs[peerId] = sentRequestOptions; |
|
return promise; |
|
} |
|
|
|
public getMessageFromStorage(storage: MessagesStorage, messageId: number) { |
|
return storage && storage[messageId] || { |
|
_: 'messageEmpty', |
|
id: messageId, |
|
deleted: true, |
|
pFlags: {} |
|
}; |
|
} |
|
|
|
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] = this.createMessageStorage()); |
|
} |
|
|
|
public getMessageById(messageId: number) { |
|
for(const peerId in this.messagesStorageByPeerId) { |
|
if(appPeersManager.isChannel(+peerId)) { |
|
continue; |
|
} |
|
|
|
const message = this.messagesStorageByPeerId[peerId][messageId]; |
|
if(message) { |
|
return message; |
|
} |
|
} |
|
|
|
return this.getMessageFromStorage(null, messageId); |
|
} |
|
|
|
public getMessageByPeer(peerId: number, messageId: number) { |
|
if(!peerId) { |
|
return this.getMessageById(messageId); |
|
} |
|
|
|
return this.getMessageFromStorage(this.getMessagesStorage(peerId), messageId); |
|
} |
|
|
|
public getMessagePeer(message: any): number { |
|
const toId = message.peer_id && appPeersManager.getPeerId(message.peer_id) || 0; |
|
|
|
return toId; |
|
} |
|
|
|
public getDialogByPeerId(peerId: number): [Dialog, number] | [] { |
|
return this.dialogsStorage.getDialog(peerId); |
|
} |
|
|
|
public reloadConversation(peerId: number | number[]) { |
|
[].concat(peerId).forEach(peerId => { |
|
if(!this.reloadConversationsPeers.includes(peerId)) { |
|
this.reloadConversationsPeers.push(peerId); |
|
//this.log('will reloadConversation', peerId); |
|
} |
|
}); |
|
|
|
if(this.reloadConversationsPromise) return this.reloadConversationsPromise; |
|
return this.reloadConversationsPromise = new Promise((resolve, reject) => { |
|
setTimeout(() => { |
|
const peers = this.reloadConversationsPeers.map(peerId => appPeersManager.getInputDialogPeerById(peerId)); |
|
this.reloadConversationsPeers.length = 0; |
|
|
|
apiManager.invokeApi('messages.getPeerDialogs', {peers}).then((result) => { |
|
this.applyConversations(result); |
|
resolve(); |
|
}, reject).finally(() => { |
|
this.reloadConversationsPromise = null; |
|
}); |
|
}, 0); |
|
}); |
|
} |
|
|
|
private doFlushHistory(inputPeer: any, justClear?: boolean, revoke?: boolean): Promise<true> { |
|
return apiManager.invokeApi('messages.deleteHistory', { |
|
just_clear: justClear, |
|
revoke: revoke, |
|
peer: inputPeer, |
|
max_id: 0 |
|
}).then((affectedHistory) => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updatePts', |
|
pts: affectedHistory.pts, |
|
pts_count: affectedHistory.pts_count |
|
} |
|
}); |
|
|
|
if(!affectedHistory.offset) { |
|
return true; |
|
} |
|
|
|
return this.doFlushHistory(inputPeer, justClear); |
|
}) |
|
} |
|
|
|
public async flushHistory(peerId: number, justClear?: boolean, revoke?: boolean) { |
|
if(appPeersManager.isChannel(peerId)) { |
|
const promise = this.getHistory(peerId, 0, 1); |
|
|
|
const historyResult = promise instanceof Promise ? await promise : promise; |
|
|
|
const channelId = -peerId; |
|
const maxId = historyResult.history[0] || 0; |
|
return apiManager.invokeApi('channels.deleteHistory', { |
|
channel: appChatsManager.getChannelInput(channelId), |
|
max_id: maxId |
|
}).then(() => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateChannelAvailableMessages', |
|
channel_id: channelId, |
|
available_min_id: maxId |
|
} |
|
}); |
|
|
|
return true; |
|
}); |
|
} |
|
|
|
return this.doFlushHistory(appPeersManager.getInputPeerById(peerId), justClear, revoke).then(() => { |
|
delete this.historiesStorage[peerId]; |
|
delete this.messagesStorageByPeerId[peerId]; |
|
|
|
if(justClear) { |
|
rootScope.broadcast('dialog_flush', {peerId}); |
|
} else { |
|
this.dialogsStorage.dropDialog(peerId); |
|
|
|
rootScope.broadcast('dialog_drop', {peerId}); |
|
} |
|
}); |
|
} |
|
|
|
public hidePinnedMessages(peerId: number) { |
|
return Promise.all([ |
|
appStateManager.getState(), |
|
this.getPinnedMessage(peerId) |
|
]) |
|
.then(([state, pinned]) => { |
|
state.hiddenPinnedMessages[peerId] = pinned.maxId; |
|
rootScope.broadcast('peer_pinned_hidden', {peerId, maxId: pinned.maxId}); |
|
}); |
|
} |
|
|
|
public getPinnedMessage(peerId: number) { |
|
const p = this.pinnedMessages[peerId] ?? (this.pinnedMessages[peerId] = {}); |
|
if(p.promise) return p.promise; |
|
else if(p.maxId) return Promise.resolve(p); |
|
|
|
return p.promise = this.getSearch({ |
|
peerId, |
|
inputFilter: {_: 'inputMessagesFilterPinned'}, |
|
maxId: 0, |
|
limit: 1 |
|
}).then(result => { |
|
p.count = result.count; |
|
p.maxId = result.history[0]?.mid; |
|
return p; |
|
}).finally(() => { |
|
delete p.promise; |
|
}); |
|
} |
|
|
|
public updatePinnedMessage(peerId: number, mid: number, unpin?: true, silent?: true, oneSide?: true) { |
|
return apiManager.invokeApi('messages.updatePinnedMessage', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
unpin, |
|
silent, |
|
pm_oneside: oneSide, |
|
id: this.getServerMessageId(mid) |
|
}).then(updates => { |
|
//this.log('pinned updates:', updates); |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}); |
|
} |
|
|
|
public unpinAllMessages(peerId: number): Promise<boolean> { |
|
return apiManager.invokeApi('messages.unpinAllMessages', { |
|
peer: appPeersManager.getInputPeerById(peerId) |
|
}).then(affectedHistory => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updatePts', |
|
pts: affectedHistory.pts, |
|
pts_count: affectedHistory.pts_count |
|
} |
|
}); |
|
|
|
if(!affectedHistory.offset) { |
|
const storage = this.getMessagesStorage(peerId); |
|
for(const mid in storage) { |
|
const message = storage[mid]; |
|
if(message.pFlags.pinned) { |
|
delete message.pFlags.pinned; |
|
} |
|
} |
|
|
|
rootScope.broadcast('peer_pinned_messages', {peerId, unpinAll: true}); |
|
delete this.pinnedMessages[peerId]; |
|
|
|
return true; |
|
} |
|
|
|
return this.unpinAllMessages(peerId); |
|
}); |
|
} |
|
|
|
public getAlbumText(grouped_id: string) { |
|
const group = this.groupedMessagesStorage[grouped_id]; |
|
let foundMessages = 0, message: string, totalEntities: MessageEntity[], entities: MessageEntity[]; |
|
for(const i in group) { |
|
const m = group[i]; |
|
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}; |
|
} |
|
|
|
public getMidsByAlbum(grouped_id: string) { |
|
return getObjectKeysAndSort(this.groupedMessagesStorage[grouped_id], 'asc'); |
|
//return Object.keys(this.groupedMessagesStorage[grouped_id]).map(id => +id).sort((a, b) => a - b); |
|
} |
|
|
|
public getMidsByMessage(message: any) { |
|
if(message?.grouped_id) return this.getMidsByAlbum(message.grouped_id); |
|
else return [message.mid]; |
|
} |
|
|
|
public filterMessages(message: any, verify: (message: MyMessage) => boolean) { |
|
const out: MyMessage[] = []; |
|
if(message.grouped_id) { |
|
const storage = this.groupedMessagesStorage[message.grouped_id]; |
|
for(const mid in storage) { |
|
const message = storage[mid]; |
|
if(verify(message)) { |
|
out.push(message); |
|
} |
|
} |
|
} else { |
|
if(verify(message)) { |
|
out.push(message); |
|
} |
|
} |
|
|
|
return out; |
|
} |
|
|
|
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 = AppMessagesManager.MESSAGE_ID_OFFSET; |
|
const num = temp ? ++this.tempNum : 0; |
|
if(messageId >= q) { |
|
if(temp) { |
|
return messageId + (num & (AppMessagesManager.MESSAGE_ID_INCREMENT - 1)); |
|
} |
|
|
|
return messageId; |
|
} |
|
|
|
return q + (messageId * AppMessagesManager.MESSAGE_ID_INCREMENT + (num & (AppMessagesManager.MESSAGE_ID_INCREMENT - 1))); |
|
} |
|
|
|
/** |
|
* * will ignore outgoing offset |
|
*/ |
|
public getServerMessageId(messageId: number) { |
|
const q = AppMessagesManager.MESSAGE_ID_OFFSET; |
|
if(messageId < q) { // id 0 -> mid 0xFFFFFFFF, so 0xFFFFFFFF must convert to 0 |
|
return messageId; |
|
} |
|
|
|
const l = AppMessagesManager.MESSAGE_ID_INCREMENT - 1; |
|
const used = messageId & l; |
|
if(used !== l) { |
|
messageId -= used + 1; |
|
} |
|
|
|
return (messageId - q) / AppMessagesManager.MESSAGE_ID_INCREMENT; |
|
} |
|
|
|
public incrementMessageId(messageId: number, increment: number) { |
|
return this.generateMessageId(this.getServerMessageId(messageId) + increment); |
|
} |
|
|
|
public saveMessages(messages: any[], options: Partial<{ |
|
storage: MessagesStorage, |
|
isScheduled: true, |
|
isOutgoing: true |
|
}> = {}) { |
|
//let groups: Set<string>; |
|
messages.forEach((message) => { |
|
if(message.pFlags === undefined) { |
|
message.pFlags = {}; |
|
} |
|
|
|
if(message._ === 'messageEmpty') { |
|
return; |
|
} |
|
|
|
// * exclude from state |
|
// 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); |
|
|
|
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) { |
|
const storage = this.groupedMessagesStorage[message.grouped_id] ?? (this.groupedMessagesStorage[message.grouped_id] = {}); |
|
storage[mid] = message; |
|
} |
|
|
|
const dialog = this.getDialogByPeerId(peerId)[0]; |
|
if(dialog && mid) { |
|
if(mid > dialog[message.pFlags.out |
|
? 'read_outbox_max_id' |
|
: 'read_inbox_max_id']) { |
|
message.pFlags.unread = true; |
|
} |
|
} |
|
// 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) { |
|
if(message.reply_to.reply_to_msg_id) { |
|
message.reply_to.reply_to_msg_id = message.reply_to_mid = this.generateMessageId(message.reply_to.reply_to_msg_id); |
|
} |
|
|
|
if(message.reply_to.reply_to_top_id) message.reply_to.reply_to_top_id = this.generateMessageId(message.reply_to.reply_to_top_id); |
|
} |
|
|
|
if(message.replies) { |
|
if(message.replies.max_id) message.replies.max_id = this.generateMessageId(message.replies.max_id); |
|
if(message.replies.read_max_id) message.replies.read_max_id = this.generateMessageId(message.replies.read_max_id); |
|
} |
|
|
|
const overwriting = !!message.peerId; |
|
if(!overwriting) { |
|
message.date -= serverTimeManager.serverTimeOffset; |
|
} |
|
|
|
//storage.generateIndex(message); |
|
const myId = appUsersManager.getSelf().id; |
|
|
|
message.peerId = peerId; |
|
if(message.peerId === myId/* && !message.from_id && !message.fwd_from */) { |
|
message.fromId = message.fwd_from ? (message.fwd_from.from_id ? appPeersManager.getPeerId(message.fwd_from.from_id) : 0) : myId; |
|
} else { |
|
//message.fromId = message.pFlags.post || (!message.pFlags.out && !message.from_id) ? peerId : appPeersManager.getPeerId(message.from_id); |
|
message.fromId = message.pFlags.post || !message.from_id ? peerId : appPeersManager.getPeerId(message.from_id); |
|
} |
|
|
|
const fwdHeader = message.fwd_from as MessageFwdHeader; |
|
if(fwdHeader) { |
|
//if(peerId === myID) { |
|
if(fwdHeader.saved_from_msg_id) fwdHeader.saved_from_msg_id = this.generateMessageId(fwdHeader.saved_from_msg_id); |
|
if(fwdHeader.channel_post) fwdHeader.channel_post = this.generateMessageId(fwdHeader.channel_post); |
|
|
|
const peer = fwdHeader.saved_from_peer || fwdHeader.from_id; |
|
const msgId = fwdHeader.saved_from_msg_id || fwdHeader.channel_post; |
|
if(peer && msgId) { |
|
const savedFromPeerId = appPeersManager.getPeerId(peer); |
|
const savedFromMid = this.generateMessageId(msgId); |
|
message.savedFrom = savedFromPeerId + '_' + savedFromMid; |
|
} |
|
|
|
/* if(peerId < 0 || peerId === myID) { |
|
message.fromId = appPeersManager.getPeerID(!message.from_id || deepEqual(message.from_id, fwdHeader.from_id) ? fwdHeader.from_id : message.from_id); |
|
} */ |
|
/* } else { |
|
apiMessage.fwdPostID = fwdHeader.channel_post; |
|
} */ |
|
|
|
message.fwdFromId = appPeersManager.getPeerId(fwdHeader.from_id); |
|
|
|
if(!overwriting) { |
|
fwdHeader.date -= serverTimeManager.serverTimeOffset; |
|
} |
|
} |
|
|
|
if(message.via_bot_id > 0) { |
|
message.viaBotId = message.via_bot_id; |
|
} |
|
|
|
const mediaContext: ReferenceContext = { |
|
type: 'message', |
|
peerId, |
|
messageId: mid |
|
}; |
|
|
|
if(message.media) { |
|
switch(message.media._) { |
|
case 'messageMediaEmpty': |
|
delete message.media; |
|
break; |
|
case 'messageMediaPhoto': |
|
if(message.media.ttl_seconds) { |
|
message.media = {_: 'messageMediaUnsupportedWeb'}; |
|
} else { |
|
message.media.photo = appPhotosManager.savePhoto(message.media.photo, mediaContext); |
|
} |
|
|
|
if(!message.media.photo) { // * found this bug on test DC |
|
delete message.media; |
|
} |
|
|
|
break; |
|
case 'messageMediaPoll': |
|
message.media.poll = appPollsManager.savePoll(message.media.poll, message.media.results); |
|
break; |
|
case 'messageMediaDocument': |
|
if(message.media.ttl_seconds) { |
|
message.media = {_: 'messageMediaUnsupportedWeb'}; |
|
} else { |
|
message.media.document = appDocsManager.saveDoc(message.media.document, mediaContext); // 11.04.2020 warning |
|
} |
|
|
|
break; |
|
case 'messageMediaWebPage': |
|
message.media.webpage = appWebPagesManager.saveWebPage(message.media.webpage, message.mid, mediaContext); |
|
break; |
|
/*case 'messageMediaGame': |
|
AppGamesManager.saveGame(apiMessage.media.game, apiMessage.mid, mediaContext); |
|
apiMessage.media.handleMessage = true; |
|
break; */ |
|
case 'messageMediaInvoice': |
|
message.media = {_: 'messageMediaUnsupportedWeb'}; |
|
break; |
|
} |
|
} |
|
|
|
if(message.action) { |
|
let migrateFrom: number; |
|
let migrateTo: number; |
|
switch(message.action._) { |
|
//case 'messageActionChannelEditPhoto': |
|
case 'messageActionChatEditPhoto': |
|
message.action.photo = appPhotosManager.savePhoto(message.action.photo, mediaContext); |
|
if(message.action.photo.video_sizes) { |
|
message.action._ = isBroadcast ? 'messageActionChannelEditVideo' : 'messageActionChatEditVideo'; |
|
} else { |
|
if(isBroadcast) { // ! messageActionChannelEditPhoto не существует в принципе, это используется для перевода. |
|
message.action._ = 'messageActionChannelEditPhoto'; |
|
} |
|
} |
|
break; |
|
|
|
case 'messageActionChatEditTitle': |
|
if(isBroadcast) { |
|
message.action._ = 'messageActionChannelEditTitle'; |
|
} |
|
break; |
|
|
|
case 'messageActionChatDeletePhoto': |
|
if(isBroadcast) { |
|
message.action._ = 'messageActionChannelDeletePhoto'; |
|
} |
|
break; |
|
|
|
case 'messageActionChatAddUser': |
|
if(message.action.users.length === 1) { |
|
message.action.user_id = message.action.users[0]; |
|
if(message.fromId === message.action.user_id) { |
|
let suffix = message.fromId === appUsersManager.getSelf().id ? 'You' : ''; |
|
if(isChannel) { |
|
message.action._ = 'messageActionChatJoined' + suffix; |
|
} else { |
|
message.action._ = 'messageActionChatReturn' + suffix; |
|
} |
|
} |
|
} else if(message.action.users.length > 1) { |
|
message.action._ = 'messageActionChatAddUsers'; |
|
} |
|
break; |
|
|
|
case 'messageActionChatDeleteUser': |
|
if(message.fromId === message.action.user_id) { |
|
message.action._ = 'messageActionChatLeave'; |
|
} |
|
break; |
|
|
|
case 'messageActionChannelMigrateFrom': |
|
migrateFrom = -message.action.chat_id; |
|
migrateTo = -channelId; |
|
break |
|
|
|
case 'messageActionChatMigrateTo': |
|
migrateFrom = -channelId; |
|
migrateTo = -message.action.channel_id; |
|
break; |
|
|
|
case 'messageActionHistoryClear': |
|
//apiMessage.deleted = true; |
|
message.clear_history = true; |
|
delete message.pFlags.out; |
|
delete message.pFlags.unread; |
|
break; |
|
|
|
case 'messageActionPhoneCall': |
|
delete message.fromId; |
|
message.action.type = |
|
(message.pFlags.out ? 'out_' : 'in_') + |
|
( |
|
message.action.reason._ === 'phoneCallDiscardReasonMissed' || |
|
message.action.reason._ === 'phoneCallDiscardReasonBusy' |
|
? 'missed' |
|
: 'ok' |
|
); |
|
break; |
|
} |
|
|
|
if(migrateFrom && |
|
migrateTo && |
|
!this.migratedFromTo[migrateFrom] && |
|
!this.migratedToFrom[migrateTo]) { |
|
this.migrateChecks(migrateFrom, migrateTo); |
|
} |
|
} |
|
|
|
/* if(message.grouped_id) { |
|
if(!groups) { |
|
groups = new Set(); |
|
} |
|
|
|
groups.add(message.grouped_id); |
|
} else { |
|
message.rReply = this.getRichReplyText(message); |
|
} */ |
|
|
|
if(message.message && message.message.length && !message.totalEntities) { |
|
const myEntities = RichTextProcessor.parseEntities(message.message); |
|
const apiEntities = message.entities || []; |
|
message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work |
|
} |
|
|
|
storage[mid] = message; |
|
}); |
|
|
|
/* if(groups) { |
|
for(const groupId of groups) { |
|
const mids = this.groupedMessagesStorage[groupId]; |
|
for(const mid in mids) { |
|
const message = this.groupedMessagesStorage[groupId][mid]; |
|
message.rReply = this.getRichReplyText(message); |
|
} |
|
} |
|
} */ |
|
} |
|
|
|
public wrapMessageForReply(message: any, text: string, usingMids: number[], plain: true, highlightWord?: string): string; |
|
public wrapMessageForReply(message: any, text?: string, usingMids?: number[], plain?: false, highlightWord?: string): DocumentFragment; |
|
public wrapMessageForReply(message: any, text: string = message.message, usingMids?: number[], plain?: boolean, highlightWord?: string): DocumentFragment | string { |
|
const parts: (HTMLElement | string)[] = []; |
|
|
|
const addPart = (langKey: LangPackKey, part?: string | HTMLElement, text?: string) => { |
|
if(langKey) { |
|
part = plain ? I18n.format(langKey, true) : i18n(langKey); |
|
} |
|
|
|
if(plain) { |
|
parts.push(part); |
|
} else { |
|
const el = document.createElement('i'); |
|
if(typeof(part) === 'string') el.innerHTML = part; |
|
else el.append(part); |
|
parts.push(el); |
|
} |
|
|
|
if(text) { |
|
parts.push(', '); |
|
} |
|
}; |
|
|
|
if(message.media) { |
|
let usingFullAlbum = true; |
|
if(message.grouped_id) { |
|
if(usingMids) { |
|
const mids = this.getMidsByMessage(message); |
|
if(usingMids.length === mids.length) { |
|
for(const mid of mids) { |
|
if(!usingMids.includes(mid)) { |
|
usingFullAlbum = false; |
|
break; |
|
} |
|
} |
|
} else { |
|
usingFullAlbum = false; |
|
} |
|
} |
|
|
|
if(usingFullAlbum) { |
|
text = this.getAlbumText(message.grouped_id).message; |
|
addPart('AttachAlbum', undefined, text); |
|
} |
|
} else { |
|
usingFullAlbum = false; |
|
} |
|
|
|
if(!usingFullAlbum) { |
|
const media = message.media; |
|
switch(media._) { |
|
case 'messageMediaPhoto': |
|
addPart('AttachPhoto', undefined, message.message); |
|
break; |
|
case 'messageMediaDice': |
|
addPart(undefined, plain ? media.emoticon : RichTextProcessor.wrapEmojiText(media.emoticon)); |
|
break; |
|
case 'messageMediaVenue': { |
|
const text = plain ? media.title : RichTextProcessor.wrapEmojiText(media.title); |
|
addPart('AttachLocation', undefined, text); |
|
parts.push(htmlToDocumentFragment(text) as any); |
|
break; |
|
} |
|
case 'messageMediaGeo': |
|
addPart('AttachLocation'); |
|
break; |
|
case 'messageMediaGeoLive': |
|
addPart('AttachLiveLocation'); |
|
break; |
|
case 'messageMediaPoll': |
|
addPart(undefined, plain ? '📊' + ' ' + (media.poll.question || 'poll') : media.poll.rReply); |
|
break; |
|
case 'messageMediaContact': |
|
addPart('AttachContact'); |
|
break; |
|
case 'messageMediaGame': { |
|
const prefix = '🎮' + ' '; |
|
addPart(undefined, plain ? prefix + media.game.title : RichTextProcessor.wrapEmojiText(prefix + media.game.title)); |
|
break; |
|
} |
|
case 'messageMediaDocument': |
|
let document = media.document; |
|
|
|
if(document.type === 'video') { |
|
addPart('AttachVideo', undefined, message.message); |
|
} else if(document.type === 'voice') { |
|
addPart('AttachAudio', undefined, message.message); |
|
} else if(document.type === 'gif') { |
|
addPart('AttachGif', undefined, message.message); |
|
} else if(document.type === 'round') { |
|
addPart('AttachRound', undefined, message.message); |
|
} else if(document.type === 'sticker') { |
|
addPart(undefined, ((plain ? document.stickerEmojiRaw : document.stickerEmoji) || '') + 'Sticker'); |
|
text = ''; |
|
} else { |
|
addPart(document.file_name, undefined, message.message); |
|
} |
|
|
|
break; |
|
|
|
default: |
|
//messageText += media._; |
|
///////this.log.warn('Got unknown media type!', message); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if(message.action) { |
|
const actionWrapped = this.wrapMessageActionTextNew(message, plain); |
|
if(actionWrapped) { |
|
addPart(undefined, actionWrapped); |
|
} |
|
} |
|
|
|
if(text) { |
|
text = limitSymbols(text, 100); |
|
|
|
if(plain) { |
|
parts.push(text); |
|
} else { |
|
let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' ')); |
|
|
|
if(highlightWord) { |
|
if(!entities) entities = []; |
|
let found = false; |
|
let match: any; |
|
let regExp = new RegExp(escapeRegExp(highlightWord), 'gi'); |
|
while((match = regExp.exec(text)) !== null) { |
|
entities.push({_: 'messageEntityHighlight', length: highlightWord.length, offset: match.index}); |
|
found = true; |
|
} |
|
|
|
if(found) { |
|
entities.sort((a, b) => a.offset - b.offset); |
|
} |
|
} |
|
|
|
const messageWrapped = RichTextProcessor.wrapRichText(text, { |
|
noLinebreaks: true, |
|
entities, |
|
noLinks: true, |
|
noTextFormat: true |
|
}); |
|
|
|
parts.push(htmlToDocumentFragment(messageWrapped) as any); |
|
} |
|
} |
|
|
|
if(plain) { |
|
return parts.join(''); |
|
} else { |
|
const fragment = document.createDocumentFragment(); |
|
fragment.append(...parts); |
|
return fragment; |
|
} |
|
} |
|
|
|
public getSenderToPeerText(message: MyMessage) { |
|
let senderTitle = '', peerTitle: string; |
|
|
|
senderTitle = message.pFlags.out ? 'You' : appPeersManager.getPeerTitle(message.fromId, false, false); |
|
peerTitle = appPeersManager.isAnyGroup(message.peerId) || (message.pFlags.out && message.peerId !== rootScope.myId) ? |
|
appPeersManager.getPeerTitle(message.peerId, false, false) : |
|
''; |
|
|
|
if(peerTitle) { |
|
senderTitle += ' ➝ ' + peerTitle; |
|
} |
|
|
|
return senderTitle; |
|
} |
|
|
|
public wrapMessageActionTextNew(message: any, plain: true): string; |
|
public wrapMessageActionTextNew(message: any, plain?: false): HTMLElement; |
|
public wrapMessageActionTextNew(message: any, plain: boolean): HTMLElement | string; |
|
public wrapMessageActionTextNew(message: any, plain?: boolean): HTMLElement | string { |
|
const element: HTMLElement = plain ? undefined : document.createElement('span'); |
|
const action = message.action as MessageAction; |
|
|
|
// this.log('message action:', action); |
|
|
|
if((action as MessageAction.messageActionCustomAction).message) { |
|
if(plain) { |
|
return RichTextProcessor.wrapPlainText(message.message); |
|
} else { |
|
element.innerHTML = RichTextProcessor.wrapRichText((action as MessageAction.messageActionCustomAction).message, {noLinebreaks: true}); |
|
return element; |
|
} |
|
} else { |
|
let _ = action._; |
|
//let suffix = ''; |
|
let langPackKey: LangPackKey = ''; |
|
let args: any[]; |
|
|
|
const getNameDivHTML = (peerId: number, plain: boolean) => { |
|
return plain ? appPeersManager.getPeerTitle(peerId, plain) + ' ' : (new PeerTitle({peerId})).element; |
|
}; |
|
|
|
switch(action._) { |
|
case "messageActionPhoneCall": { |
|
_ += '.' + (action as any).type; |
|
|
|
const duration = action.duration || 1; |
|
const d: string[] = []; |
|
|
|
d.push(duration % 60 + ' s'); |
|
if(duration >= 60) d.push((duration / 60 | 0) + ' min'); |
|
//if(duration >= 3600) d.push((duration / 3600 | 0) + ' h'); |
|
|
|
langPackKey = langPack[_]; |
|
args = [d.reverse().join(' ')]; |
|
break; |
|
} |
|
|
|
case 'messageActionPinMessage': |
|
case 'messageActionContactSignUp': |
|
case 'messageActionChatReturn': |
|
case 'messageActionChatLeave': |
|
case 'messageActionChatJoined': |
|
case 'messageActionChatCreate': |
|
case 'messageActionChatEditPhoto': |
|
case 'messageActionChatDeletePhoto': |
|
case 'messageActionChatEditVideo': |
|
case 'messageActionChatJoinedByLink': |
|
case 'messageActionChannelEditVideo': |
|
case 'messageActionChannelDeletePhoto': { |
|
langPackKey = langPack[_]; |
|
args = [getNameDivHTML(message.fromId, plain)]; |
|
break; |
|
} |
|
|
|
case 'messageActionChannelEditTitle': |
|
case 'messageActionChatEditTitle': { |
|
langPackKey = langPack[_]; |
|
|
|
args = []; |
|
if(action._ === 'messageActionChatEditTitle') { |
|
args.push(getNameDivHTML(message.fromId, plain)); |
|
} |
|
|
|
args.push(plain ? action.title : htmlToSpan(RichTextProcessor.wrapEmojiText(action.title))); |
|
break; |
|
} |
|
|
|
case 'messageActionChatDeleteUser': |
|
case 'messageActionChatAddUsers': |
|
case 'messageActionChatAddUser': { |
|
const users: number[] = (action as MessageAction.messageActionChatAddUser).users |
|
|| [(action as MessageAction.messageActionChatDeleteUser).user_id]; |
|
|
|
langPackKey = langPack[_]; |
|
args = [getNameDivHTML(message.fromId, plain)]; |
|
|
|
if(users.length > 1) { |
|
if(plain) { |
|
args.push(...users.map((userId: number) => (getNameDivHTML(userId, true) as string).trim()).join(', ')); |
|
} else { |
|
const fragment = document.createElement('span'); |
|
fragment.append( |
|
...join( |
|
users.map((userId: number) => getNameDivHTML(userId, false)) as HTMLElement[], |
|
false |
|
) |
|
); |
|
args.push(fragment); |
|
} |
|
} else { |
|
args.push(getNameDivHTML(users[0], plain)); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'messageActionBotAllowed': { |
|
const anchorHTML = RichTextProcessor.wrapRichText(action.domain, { |
|
entities: [{ |
|
_: 'messageEntityUrl', |
|
length: action.domain.length, |
|
offset: 0 |
|
}] |
|
}); |
|
|
|
const node = htmlToSpan(anchorHTML); |
|
|
|
langPackKey = langPack[_]; |
|
args = [node]; |
|
break; |
|
} |
|
|
|
default: |
|
langPackKey = langPack[_] || `[${action._}]`; |
|
break; |
|
} |
|
|
|
if(!langPackKey) { |
|
langPackKey = langPack[_]; |
|
if(langPackKey === undefined) { |
|
langPackKey = '[' + _ + ']'; |
|
} |
|
} |
|
|
|
if(plain) { |
|
return I18n.format(langPackKey, true, args); |
|
} else { |
|
return _i18n(element, langPackKey, args); |
|
} |
|
|
|
//str = !langPackKey || langPackKey[0].toUpperCase() === langPackKey[0] ? langPackKey : getNameDivHTML(message.fromId) + langPackKey + (suffix ? ' ' : ''); |
|
} |
|
} |
|
|
|
public editPeerFolders(peerIds: number[], folderId: number) { |
|
apiManager.invokeApi('folders.editPeerFolders', { |
|
folder_peers: peerIds.map(peerId => { |
|
return { |
|
_: 'inputFolderPeer', |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
folder_id: folderId |
|
}; |
|
}) |
|
}).then(updates => { |
|
//this.log('editPeerFolders updates:', updates); |
|
apiUpdatesManager.processUpdateMessage(updates); // WARNING! возможно тут нужно добавлять channelId, и вызывать апдейт для каждого канала отдельно |
|
}); |
|
} |
|
|
|
public toggleDialogPin(peerId: number, filterId?: number) { |
|
if(filterId > 1) { |
|
this.filtersStorage.toggleDialogPin(peerId, filterId); |
|
return; |
|
} |
|
|
|
const dialog = this.getDialogByPeerId(peerId)[0]; |
|
if(!dialog) return Promise.reject(); |
|
|
|
const pinned = dialog.pFlags?.pinned ? undefined : true; |
|
return apiManager.invokeApi('messages.toggleDialogPin', { |
|
peer: appPeersManager.getInputDialogPeerById(peerId), |
|
pinned |
|
}).then(bool => { |
|
if(bool) { |
|
const pFlags: Update.updateDialogPinned['pFlags'] = pinned ? {pinned} : {}; |
|
this.handleUpdate({ |
|
_: 'updateDialogPinned', |
|
peer: appPeersManager.getDialogPeer(peerId), |
|
folder_id: filterId, |
|
pFlags |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
public markDialogUnread(peerId: number, read?: true) { |
|
const dialog = this.getDialogByPeerId(peerId)[0]; |
|
if(!dialog) return Promise.reject(); |
|
|
|
const unread = read || dialog.pFlags?.unread_mark ? undefined : true; |
|
return apiManager.invokeApi('messages.markDialogUnread', { |
|
peer: appPeersManager.getInputDialogPeerById(peerId), |
|
unread |
|
}).then(bool => { |
|
if(bool) { |
|
const pFlags: Update.updateDialogUnreadMark['pFlags'] = unread ? {unread} : {}; |
|
this.handleUpdate({ |
|
_: 'updateDialogUnreadMark', |
|
peer: appPeersManager.getDialogPeer(peerId), |
|
pFlags |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
public migrateChecks(migrateFrom: number, migrateTo: number) { |
|
if(!this.migratedFromTo[migrateFrom] && |
|
!this.migratedToFrom[migrateTo] && |
|
appChatsManager.hasChat(-migrateTo)) { |
|
const fromChat = appChatsManager.getChat(-migrateFrom); |
|
if(fromChat && |
|
fromChat.migrated_to && |
|
fromChat.migrated_to.channel_id === -migrateTo) { |
|
this.migratedFromTo[migrateFrom] = migrateTo; |
|
this.migratedToFrom[migrateTo] = migrateFrom; |
|
|
|
//setTimeout(() => { |
|
rootScope.broadcast('dialog_migrate', {migrateFrom, migrateTo}); |
|
|
|
const dropped = this.dialogsStorage.dropDialog(migrateFrom); |
|
if(dropped.length) { |
|
rootScope.broadcast('dialog_drop', {peerId: migrateFrom, dialog: dropped[0]}); |
|
} |
|
//}, 100); |
|
} |
|
} |
|
} |
|
|
|
public canMessageBeEdited(message: any, kind: 'text' | 'poll') { |
|
const goodMedias = [ |
|
'messageMediaPhoto', |
|
'messageMediaDocument', |
|
'messageMediaWebPage' |
|
]; |
|
|
|
if(kind === 'poll') { |
|
goodMedias.push('messageMediaPoll'); |
|
} |
|
|
|
if(message._ !== 'message' || |
|
message.deleted || |
|
message.fwd_from || |
|
message.via_bot_id || |
|
message.media && goodMedias.indexOf(message.media._) === -1 || |
|
message.fromId && appUsersManager.isBot(message.fromId)) { |
|
return false; |
|
} |
|
|
|
if(message.media && |
|
message.media._ === 'messageMediaDocument' && |
|
(message.media.document.sticker || message.media.document.type === 'round')) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
public canEditMessage(message: any, kind: 'text' | 'poll' = 'text') { |
|
if(!message || !this.canMessageBeEdited(message, kind)) { |
|
return false; |
|
} |
|
|
|
// * second rule for saved messages, because there is no 'out' flag |
|
if(message.pFlags.out || this.getMessagePeer(message) === appUsersManager.getSelf().id) { |
|
return true; |
|
} |
|
|
|
if((message.date < tsNow(true) - (2 * 86400) && message.media?._ !== 'messageMediaPoll') || !message.pFlags.out) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
public canDeleteMessage(message: any) { |
|
return message && ( |
|
message.peerId > 0 |
|
|| message.fromId === rootScope.myId |
|
|| appChatsManager.getChat(message.peerId)._ === 'chat' |
|
|| appChatsManager.hasRights(message.peerId, 'delete_messages') |
|
); |
|
} |
|
|
|
public applyConversations(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); |
|
} |
|
}); |
|
|
|
appUsersManager.saveApiUsers(dialogsResult.users); |
|
appChatsManager.saveApiChats(dialogsResult.chats); |
|
this.saveMessages(dialogsResult.messages); |
|
|
|
this.log('applyConversation', dialogsResult); |
|
|
|
const updatedDialogs: {[peerId: number]: Dialog} = {}; |
|
(dialogsResult.dialogs as Dialog[]).forEach((dialog) => { |
|
const peerId = appPeersManager.getPeerId(dialog.peer); |
|
let topMessage = dialog.top_message; |
|
|
|
const topPendingMessage = this.pendingTopMsgs[peerId]; |
|
if(topPendingMessage) { |
|
if(!topMessage |
|
|| (this.getMessageByPeer(peerId, topPendingMessage) as MyMessage).date > (this.getMessageByPeer(peerId, topMessage) as MyMessage).date) { |
|
dialog.top_message = topMessage = topPendingMessage; |
|
this.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.saveConversation(dialog); |
|
updatedDialogs[peerId] = dialog; |
|
} else { |
|
const dropped = this.dialogsStorage.dropDialog(peerId); |
|
if(dropped.length) { |
|
rootScope.broadcast('dialog_drop', {peerId, dialog: dropped[0]}); |
|
} |
|
} |
|
|
|
if(this.newUpdatesAfterReloadToHandle[peerId] !== undefined) { |
|
for(const update of this.newUpdatesAfterReloadToHandle[peerId]) { |
|
this.handleUpdate(update); |
|
} |
|
|
|
delete this.newUpdatesAfterReloadToHandle[peerId]; |
|
} |
|
}); |
|
|
|
if(Object.keys(updatedDialogs).length) { |
|
rootScope.broadcast('dialogs_multiupdate', updatedDialogs); |
|
} |
|
} |
|
|
|
public generateDialog(peerId: number) { |
|
const dialog: Dialog = { |
|
_: 'dialog', |
|
pFlags: {}, |
|
peer: 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; |
|
} |
|
|
|
/** |
|
* Won't save migrated from peer, forbidden peers, left and kicked |
|
*/ |
|
public saveConversation(dialog: Dialog, folderId = 0) { |
|
const peerId = appPeersManager.getPeerId(dialog.peer); |
|
if(!peerId) { |
|
console.error('saveConversation no peerId???', dialog, folderId); |
|
return false; |
|
} |
|
|
|
if(dialog._ !== 'dialog'/* || peerId === 239602833 */) { |
|
console.error('saveConversation not regular dialog', dialog, Object.assign({}, dialog)); |
|
} |
|
|
|
const channelId = appPeersManager.isChannel(peerId) ? -peerId : 0; |
|
|
|
if(peerId < 0) { |
|
const chat: Chat = appChatsManager.getChat(-peerId); |
|
if(chat._ === 'channelForbidden' || chat._ === 'chatForbidden' || (chat as Chat.chat).pFlags.left || (chat as Chat.chat).pFlags.kicked) { |
|
return false; |
|
} |
|
} |
|
|
|
const peerText = appPeersManager.getPeerSearchText(peerId); |
|
searchIndexManager.indexObject(peerId, peerText, this.dialogsIndex); |
|
|
|
let mid: number, message; |
|
if(dialog.top_message) { |
|
mid = this.generateMessageId(dialog.top_message);//dialog.top_message; |
|
message = this.getMessageByPeer(peerId, mid); |
|
} else { |
|
mid = this.generateTempMessageId(peerId); |
|
message = { |
|
_: 'message', |
|
id: mid, |
|
mid, |
|
from_id: appPeersManager.getOutputPeer(appUsersManager.getSelf().id), |
|
peer_id: appPeersManager.getOutputPeer(peerId), |
|
deleted: true, |
|
pFlags: {out: true}, |
|
date: 0, |
|
message: '' |
|
}; |
|
this.saveMessages([message], {isOutgoing: true}); |
|
} |
|
|
|
if(!message?.pFlags) { |
|
this.log.error('saveConversation no message:', dialog, message); |
|
} |
|
|
|
if(!channelId && peerId < 0) { |
|
const chat = appChatsManager.getChat(-peerId); |
|
if(chat && chat.migrated_to && chat.pFlags.deactivated) { |
|
const migratedToPeer = appPeersManager.getPeerId(chat.migrated_to); |
|
this.migratedFromTo[peerId] = migratedToPeer; |
|
this.migratedToFrom[migratedToPeer] = peerId; |
|
return; |
|
} |
|
} |
|
|
|
const wasDialogBefore = this.getDialogByPeerId(peerId)[0]; |
|
|
|
dialog.top_message = mid; |
|
dialog.read_inbox_max_id = this.generateMessageId(wasDialogBefore && !dialog.read_inbox_max_id ? wasDialogBefore.read_inbox_max_id : dialog.read_inbox_max_id); |
|
dialog.read_outbox_max_id = this.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 = 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; |
|
} |
|
|
|
let historyStorage = this.getHistoryStorage(peerId); |
|
/* if(historyStorage === undefined) { // warning |
|
historyStorage.history.push(mid); |
|
if(this.mergeReplyKeyboard(historyStorage, message)) { |
|
rootScope.broadcast('history_reply_markup', {peerId}); |
|
} |
|
} else */if(!historyStorage.history.slice.length) { |
|
historyStorage.history.unshift(mid); |
|
} |
|
|
|
historyStorage.maxId = mid; |
|
historyStorage.readMaxId = dialog.read_inbox_max_id; |
|
historyStorage.readOutboxMaxId = dialog.read_outbox_max_id; |
|
|
|
appNotificationsManager.savePeerSettings(peerId, dialog.notify_settings) |
|
|
|
if(channelId && dialog.pts) { |
|
apiUpdatesManager.addChannelState(channelId, dialog.pts); |
|
} |
|
|
|
this.dialogsStorage.generateIndexForDialog(dialog); |
|
this.dialogsStorage.pushDialog(dialog, message.date); |
|
} |
|
|
|
public mergeReplyKeyboard(historyStorage: HistoryStorage, message: any) { |
|
// this.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup) |
|
if(!message.reply_markup && |
|
!message.pFlags?.out && |
|
!message.action) { |
|
return false; |
|
} |
|
if(message.reply_markup && |
|
message.reply_markup._ === 'replyInlineMarkup') { |
|
return false; |
|
} |
|
var messageReplyMarkup = message.reply_markup; |
|
var lastReplyMarkup = historyStorage.reply_markup; |
|
if(messageReplyMarkup) { |
|
if(lastReplyMarkup && lastReplyMarkup.mid >= message.mid) { |
|
return false; |
|
} |
|
|
|
if(messageReplyMarkup.pFlags.selective) { |
|
return false; |
|
} |
|
|
|
if(historyStorage.maxOutId && |
|
message.mid < historyStorage.maxOutId && |
|
messageReplyMarkup.pFlags.single_use) { |
|
messageReplyMarkup.pFlags.hidden = true; |
|
} |
|
messageReplyMarkup = Object.assign({ |
|
mid: message.mid |
|
}, messageReplyMarkup); |
|
if(messageReplyMarkup._ !== 'replyKeyboardHide') { |
|
messageReplyMarkup.fromId = appPeersManager.getPeerId(message.from_id); |
|
} |
|
historyStorage.reply_markup = messageReplyMarkup; |
|
// this.log('set', historyStorage.reply_markup) |
|
return true; |
|
} |
|
|
|
if(message.pFlags.out) { |
|
if(lastReplyMarkup) { |
|
if(lastReplyMarkup.pFlags.single_use && |
|
!lastReplyMarkup.pFlags.hidden && |
|
(message.mid > lastReplyMarkup.mid || message.pFlags.is_outgoing) && |
|
message.message) { |
|
lastReplyMarkup.pFlags.hidden = true; |
|
// this.log('set', historyStorage.reply_markup) |
|
return true; |
|
} |
|
} else if(!historyStorage.maxOutId || |
|
message.mid > historyStorage.maxOutId) { |
|
historyStorage.maxOutId = message.mid; |
|
} |
|
} |
|
|
|
if(message.action && |
|
message.action._ === 'messageActionChatDeleteUser' && |
|
(lastReplyMarkup |
|
? message.action.user_id === lastReplyMarkup.fromId |
|
: appUsersManager.isBot(message.action.user_id) |
|
) |
|
) { |
|
historyStorage.reply_markup = { |
|
_: 'replyKeyboardHide', |
|
mid: message.mid, |
|
pFlags: {} |
|
}; |
|
// this.log('set', historyStorage.reply_markup) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public getSearchStorage(peerId: number, inputFilter: MyInputMessagesFilter) { |
|
if(!this.searchesStorage[peerId]) this.searchesStorage[peerId] = {}; |
|
if(!this.searchesStorage[peerId][inputFilter]) this.searchesStorage[peerId][inputFilter] = {history: []}; |
|
return this.searchesStorage[peerId][inputFilter]; |
|
} |
|
|
|
public getSearchCounters(peerId: number, filters: MessagesFilter[]) { |
|
return apiManager.invokeApi('messages.getSearchCounters', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
filters |
|
}); |
|
} |
|
|
|
public getSearch({peerId, query, inputFilter, maxId, limit, nextRate, backLimit, threadId, folderId, minDate, maxDate}: { |
|
peerId?: number, |
|
maxId?: number, |
|
limit?: number, |
|
nextRate?: number, |
|
backLimit?: number, |
|
threadId?: number, |
|
folderId?: number, |
|
query?: string, |
|
inputFilter?: { |
|
_: MyInputMessagesFilter |
|
}, |
|
minDate?: number, |
|
maxDate?: number |
|
}): Promise<{ |
|
count: number, |
|
next_rate: number, |
|
offset_id_offset: number, |
|
history: MyMessage[] |
|
}> { |
|
if(!peerId) peerId = 0; |
|
if(!query) query = ''; |
|
if(!inputFilter) inputFilter = {_: 'inputMessagesFilterEmpty'}; |
|
if(limit === undefined) limit = 20; |
|
if(!nextRate) nextRate = 0; |
|
if(!backLimit) backLimit = 0; |
|
|
|
minDate = minDate ? minDate / 1000 | 0 : 0; |
|
maxDate = maxDate ? maxDate / 1000 | 0 : 0; |
|
|
|
const foundMsgs: Message.message[] = []; |
|
|
|
//this.log('search', maxId); |
|
|
|
if(backLimit) { |
|
limit += backLimit; |
|
} |
|
|
|
//const beta = inputFilter._ === 'inputMessagesFilterPinned' && !backLimit; |
|
const beta = false; |
|
|
|
let storage: { |
|
count?: number; |
|
history: SlicedArray; |
|
}; |
|
|
|
// * костыль для limit 1, если нужно и получить сообщение, и узнать количество сообщений |
|
if(peerId && !backLimit && !maxId && !query && limit !== 1 && !threadId/* && inputFilter._ !== 'inputMessagesFilterPinned' */) { |
|
storage = beta ? |
|
this.getSearchStorage(peerId, inputFilter._) as any : |
|
this.getHistoryStorage(peerId); |
|
let filtering = true; |
|
|
|
const history = /* maxId ? storage.history.slice(storage.history.indexOf(maxId) + 1) : */storage.history; |
|
|
|
if(storage !== undefined && history.length) { |
|
const neededContents: { |
|
[messageMediaType: string]: boolean |
|
} = {}, |
|
neededDocTypes: string[] = [], |
|
excludeDocTypes: string[] = []/* , |
|
neededFlags: string[] = [] */; |
|
|
|
switch(inputFilter._) { |
|
case 'inputMessagesFilterPhotos': |
|
neededContents['messageMediaPhoto'] = true; |
|
break; |
|
|
|
case 'inputMessagesFilterPhotoVideo': |
|
neededContents['messageMediaPhoto'] = true; |
|
neededContents['messageMediaDocument'] = true; |
|
neededDocTypes.push('video'); |
|
break; |
|
|
|
case 'inputMessagesFilterVideo': |
|
neededContents['messageMediaDocument'] = true; |
|
neededDocTypes.push('video'); |
|
break; |
|
|
|
case 'inputMessagesFilterDocument': |
|
neededContents['messageMediaDocument'] = true; |
|
excludeDocTypes.push('video'); |
|
break; |
|
|
|
case 'inputMessagesFilterVoice': |
|
neededContents['messageMediaDocument'] = true; |
|
neededDocTypes.push('voice'); |
|
break; |
|
|
|
case 'inputMessagesFilterRoundVoice': |
|
neededContents['messageMediaDocument'] = true; |
|
neededDocTypes.push('round', 'voice'); |
|
break; |
|
|
|
case 'inputMessagesFilterRoundVideo': |
|
neededContents['messageMediaDocument'] = true; |
|
neededDocTypes.push('round'); |
|
break; |
|
|
|
case 'inputMessagesFilterMusic': |
|
neededContents['messageMediaDocument'] = true; |
|
neededDocTypes.push('audio'); |
|
break; |
|
|
|
case 'inputMessagesFilterUrl': |
|
neededContents['url'] = true; |
|
break; |
|
|
|
case 'inputMessagesFilterChatPhotos': |
|
neededContents['avatar'] = true; |
|
break; |
|
|
|
/* case 'inputMessagesFilterPinned': |
|
neededFlags.push('pinned'); |
|
break; */ |
|
|
|
/* case 'inputMessagesFilterMyMentions': |
|
neededContents['mentioned'] = true; |
|
break; */ |
|
|
|
default: |
|
filtering = false; |
|
break; |
|
/* return Promise.resolve({ |
|
count: 0, |
|
next_rate: 0, |
|
history: [] as number[] |
|
}); */ |
|
} |
|
|
|
if(filtering) { |
|
const storage = this.getMessagesStorage(peerId); |
|
for(let i = 0, length = history.length; i < length; i++) { |
|
const message = storage[history.slice[i]]; |
|
|
|
if(!message) continue; |
|
|
|
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ === 'messageEntityMention')); |
|
|
|
let found = false; |
|
if(message.media && neededContents[message.media._] && !message.fwd_from) { |
|
if(message.media._ === 'messageMediaDocument') { |
|
if((neededDocTypes.length && !neededDocTypes.includes(message.media.document.type)) |
|
|| excludeDocTypes.includes(message.media.document.type)) { |
|
continue; |
|
} |
|
} |
|
|
|
found = true; |
|
} else if(neededContents['url'] && message.message) { |
|
const goodEntities = ['messageEntityTextUrl', 'messageEntityUrl']; |
|
if((message.totalEntities as MessageEntity[]).find(e => goodEntities.includes(e._)) || RichTextProcessor.matchUrl(message.message)) { |
|
found = true; |
|
} |
|
} else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto'].includes(message.action._)) { |
|
found = true; |
|
}/* else if(neededFlags.find(flag => message.pFlags[flag])) { |
|
found = true; |
|
} */ |
|
|
|
if(found) { |
|
foundMsgs.push(message); |
|
if(foundMsgs.length >= limit) { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if(foundMsgs.length) { |
|
if(foundMsgs.length < limit && (beta ? storage.count !== storage.history.length : true)) { |
|
maxId = foundMsgs[foundMsgs.length - 1].mid; |
|
limit = limit - foundMsgs.length; |
|
} else { |
|
return Promise.resolve({ |
|
count: beta ? storage.count : 0, |
|
next_rate: 0, |
|
offset_id_offset: 0, |
|
history: foundMsgs |
|
}); |
|
} |
|
} else if(beta && storage?.count) { |
|
return Promise.resolve({ |
|
count: storage.count, |
|
next_rate: 0, |
|
offset_id_offset: 0, |
|
history: [] |
|
}); |
|
} |
|
|
|
let apiPromise: Promise<any>; |
|
if(peerId && !nextRate && folderId === undefined/* || !query */) { |
|
apiPromise = apiManager.invokeApi('messages.search', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
q: query || '', |
|
filter: inputFilter as any as MessagesFilter, |
|
min_date: minDate, |
|
max_date: maxDate, |
|
limit, |
|
offset_id: this.getServerMessageId(maxId) || 0, |
|
add_offset: backLimit ? -backLimit : 0, |
|
max_id: 0, |
|
min_id: 0, |
|
hash: 0, |
|
top_msg_id: this.getServerMessageId(threadId) || 0 |
|
}, { |
|
//timeout: APITIMEOUT, |
|
noErrorBox: true |
|
}); |
|
} else { |
|
//var offsetDate = 0; |
|
let offsetPeerId = 0; |
|
let offsetId = 0; |
|
let offsetMessage = maxId && this.getMessageByPeer(peerId, maxId); |
|
|
|
if(offsetMessage && offsetMessage.date) { |
|
//offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset; |
|
offsetId = offsetMessage.id; |
|
offsetPeerId = this.getMessagePeer(offsetMessage); |
|
} |
|
|
|
apiPromise = apiManager.invokeApi('messages.searchGlobal', { |
|
q: query, |
|
filter: inputFilter as any as MessagesFilter, |
|
min_date: minDate, |
|
max_date: maxDate, |
|
offset_rate: nextRate, |
|
offset_peer: appPeersManager.getInputPeerById(offsetPeerId), |
|
offset_id: offsetId, |
|
limit, |
|
folder_id: folderId |
|
}, { |
|
//timeout: APITIMEOUT, |
|
noErrorBox: true |
|
}); |
|
} |
|
|
|
return apiPromise.then((searchResult: any) => { |
|
appUsersManager.saveApiUsers(searchResult.users); |
|
appChatsManager.saveApiChats(searchResult.chats); |
|
this.saveMessages(searchResult.messages); |
|
|
|
/* if(beta && storage && (!maxId || storage.history[storage.history.length - 1] === maxId)) { |
|
const storage = this.getSearchStorage(peerId, inputFilter._); |
|
const add = (searchResult.messages.map((m: any) => m.mid) as number[]).filter(mid => storage.history.indexOf(mid) === -1); |
|
storage.history.push(...add); |
|
storage.history.sort((a, b) => b - a); |
|
storage.count = searchResult.count; |
|
} */ |
|
|
|
if(DEBUG) { |
|
this.log('getSearch result:', inputFilter, searchResult); |
|
} |
|
|
|
const foundCount: number = searchResult.count || (foundMsgs.length + searchResult.messages.length); |
|
|
|
searchResult.messages.forEach((message: any) => { |
|
const peerId = this.getMessagePeer(message); |
|
if(peerId < 0) { |
|
const chat = appChatsManager.getChat(-peerId); |
|
if(chat.migrated_to) { |
|
this.migrateChecks(peerId, -chat.migrated_to.channel_id); |
|
} |
|
} |
|
|
|
foundMsgs.push(message); |
|
}); |
|
|
|
return { |
|
count: foundCount, |
|
offset_id_offset: searchResult.offset_id_offset || 0, |
|
next_rate: searchResult.next_rate, |
|
history: foundMsgs |
|
}; |
|
}); |
|
} |
|
|
|
public subscribeRepliesThread(peerId: number, mid: number) { |
|
const repliesKey = peerId + '_' + mid; |
|
for(const threadKey in this.threadsToReplies) { |
|
if(this.threadsToReplies[threadKey] === repliesKey) return; |
|
} |
|
|
|
this.getDiscussionMessage(peerId, mid); |
|
} |
|
|
|
public generateThreadServiceStartMessage(message: Message.message) { |
|
const threadKey = message.peerId + '_' + message.mid; |
|
if(this.threadsServiceMessagesIdsStorage[threadKey]) return; |
|
|
|
const maxMessageId = this.getServerMessageId(Math.max(...this.getMidsByMessage(message))); |
|
const serviceStartMessage: Message.messageService = { |
|
_: 'messageService', |
|
pFlags: { |
|
is_single: true |
|
} as any, |
|
id: this.generateMessageId(maxMessageId, true), |
|
date: message.date, |
|
from_id: {_: 'peerUser', user_id: 0}/* message.from_id */, |
|
peer_id: message.peer_id, |
|
action: { |
|
_: 'messageActionCustomAction', |
|
message: 'Discussion started' |
|
}, |
|
reply_to: this.generateReplyHeader(message.id) |
|
}; |
|
|
|
this.saveMessages([serviceStartMessage], {isOutgoing: true}); |
|
this.threadsServiceMessagesIdsStorage[threadKey] = serviceStartMessage.mid; |
|
} |
|
|
|
public getDiscussionMessage(peerId: number, mid: number) { |
|
return apiManager.invokeApi('messages.getDiscussionMessage', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
msg_id: this.getServerMessageId(mid) |
|
}).then(result => { |
|
appChatsManager.saveApiChats(result.chats); |
|
appUsersManager.saveApiUsers(result.users); |
|
this.saveMessages(result.messages); |
|
|
|
const message = this.filterMessages(result.messages[0], message => !!(message as Message.message).replies)[0] as Message.message; |
|
const threadKey = message.peerId + '_' + message.mid; |
|
|
|
this.generateThreadServiceStartMessage(message); |
|
|
|
const historyStorage = this.getHistoryStorage(message.peerId, message.mid); |
|
result.max_id = historyStorage.maxId = this.generateMessageId(result.max_id) || 0; |
|
result.read_inbox_max_id = historyStorage.readMaxId = this.generateMessageId(result.read_inbox_max_id) || 0; |
|
result.read_outbox_max_id = historyStorage.readOutboxMaxId = this.generateMessageId(result.read_outbox_max_id) || 0; |
|
|
|
this.threadsToReplies[threadKey] = peerId + '_' + mid; |
|
|
|
return message; |
|
}); |
|
} |
|
|
|
handleNewMessages = () => { |
|
clearTimeout(this.newMessagesHandlePromise); |
|
this.newMessagesHandlePromise = 0; |
|
|
|
rootScope.broadcast('history_multiappend', this.newMessagesToHandle); |
|
this.newMessagesToHandle = {}; |
|
}; |
|
|
|
handleNewDialogs = () => { |
|
clearTimeout(this.newDialogsHandlePromise); |
|
this.newDialogsHandlePromise = 0; |
|
|
|
let newMaxSeenId = 0; |
|
for(const peerId in this.newDialogsToHandle) { |
|
const dialog = this.newDialogsToHandle[peerId]; |
|
if('reload' in dialog) { |
|
this.reloadConversation(+peerId); |
|
delete this.newDialogsToHandle[peerId]; |
|
} else { |
|
this.dialogsStorage.pushDialog(dialog); |
|
if(!appPeersManager.isChannel(+peerId)) { |
|
newMaxSeenId = Math.max(newMaxSeenId, dialog.top_message || 0); |
|
} |
|
} |
|
} |
|
|
|
//this.log('after order:', this.dialogsStorage[0].map(d => d.peerId)); |
|
|
|
if(newMaxSeenId !== 0) { |
|
this.incrementMaxSeenId(newMaxSeenId); |
|
} |
|
|
|
rootScope.broadcast('dialogs_multiupdate', this.newDialogsToHandle as any); |
|
this.newDialogsToHandle = {}; |
|
}; |
|
|
|
public scheduleHandleNewDialogs() { |
|
if(!this.newDialogsHandlePromise) { |
|
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs, 0); |
|
} |
|
} |
|
|
|
public deleteMessages(peerId: number, mids: number[], revoke?: true) { |
|
let promise: Promise<any>; |
|
|
|
const localMessageIds = mids.map(mid => this.getServerMessageId(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) { |
|
mids.forEach((msgId, i) => { |
|
const message = this.getMessageByPeer(peerId, mids[i]); |
|
if(message.pFlags.out) { |
|
goodMsgIds.push(msgId); |
|
} |
|
}); |
|
} |
|
|
|
if(!goodMsgIds.length) { |
|
return; |
|
} |
|
|
|
mids = goodMsgIds; |
|
} |
|
|
|
promise = apiManager.invokeApi('channels.deleteMessages', { |
|
channel: appChatsManager.getChannelInput(channelId), |
|
id: localMessageIds |
|
}).then((affectedMessages) => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateDeleteChannelMessages', |
|
channel_id: channelId, |
|
messages: mids, |
|
pts: affectedMessages.pts, |
|
pts_count: affectedMessages.pts_count |
|
} |
|
}); |
|
}); |
|
} else { |
|
promise = apiManager.invokeApi('messages.deleteMessages', { |
|
revoke, |
|
id: localMessageIds |
|
}).then((affectedMessages) => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateDeleteMessages', |
|
messages: mids, |
|
pts: affectedMessages.pts, |
|
pts_count: affectedMessages.pts_count |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
return promise; |
|
} |
|
|
|
public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) { |
|
// return Promise.resolve(); |
|
// console.trace('start read') |
|
this.log('readHistory:', peerId, maxId, threadId); |
|
if(!this.getReadMaxIdIfUnread(peerId, threadId) && !force) { |
|
this.log('readHistory: isn\'t unread'); |
|
return Promise.resolve(); |
|
} |
|
|
|
const isChannel = appPeersManager.isChannel(peerId); |
|
const historyStorage = this.getHistoryStorage(peerId, threadId); |
|
|
|
let apiPromise: Promise<any>; |
|
if(threadId) { |
|
if(!historyStorage.readPromise) { |
|
apiPromise = apiManager.invokeApi('messages.readDiscussion', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
msg_id: this.getServerMessageId(threadId), |
|
read_max_id: this.getServerMessageId(maxId) |
|
}); |
|
} |
|
|
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateReadChannelDiscussionInbox', |
|
channel_id: -peerId, |
|
top_msg_id: threadId, |
|
read_max_id: maxId |
|
} as Update.updateReadChannelDiscussionInbox |
|
}); |
|
} else if(isChannel) { |
|
if(!historyStorage.readPromise) { |
|
apiPromise = apiManager.invokeApi('channels.readHistory', { |
|
channel: appChatsManager.getChannelInput(-peerId), |
|
max_id: this.getServerMessageId(maxId) |
|
}); |
|
} |
|
|
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateReadChannelInbox', |
|
max_id: maxId, |
|
channel_id: -peerId |
|
} |
|
}); |
|
} else { |
|
if(!historyStorage.readPromise) { |
|
apiPromise = apiManager.invokeApi('messages.readHistory', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
max_id: this.getServerMessageId(maxId) |
|
}).then((affectedMessages) => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updatePts', |
|
pts: affectedMessages.pts, |
|
pts_count: affectedMessages.pts_count |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateReadHistoryInbox', |
|
max_id: maxId, |
|
peer: appPeersManager.getOutputPeer(peerId) |
|
} |
|
}); |
|
} |
|
|
|
if(!threadId && historyStorage && historyStorage.history.length) { |
|
const slice = historyStorage.history.slice; |
|
for(const mid of slice) { |
|
const message = this.getMessageByPeer(peerId, mid); |
|
if(message && !message.pFlags.out) { |
|
message.pFlags.unread = false; |
|
appNotificationsManager.cancel('msg' + mid); |
|
} |
|
} |
|
} |
|
|
|
appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId)); |
|
|
|
if(historyStorage.readPromise) { |
|
return historyStorage.readPromise; |
|
} |
|
|
|
apiPromise.finally(() => { |
|
delete historyStorage.readPromise; |
|
|
|
this.log('readHistory: promise finally', maxId, historyStorage.readMaxId); |
|
|
|
if(historyStorage.readMaxId > maxId) { |
|
this.readHistory(peerId, historyStorage.readMaxId, threadId, true); |
|
} |
|
}); |
|
|
|
return historyStorage.readPromise = apiPromise; |
|
} |
|
|
|
public readAllHistory(peerId: number, threadId?: number, force = false) { |
|
const historyStorage = this.getHistoryStorage(peerId, threadId); |
|
if(historyStorage.maxId) { |
|
this.readHistory(peerId, historyStorage.maxId, threadId, force); // lol |
|
} |
|
} |
|
|
|
public readMessages(peerId: number, msgIds: number[]) { |
|
msgIds = msgIds.map(mid => this.getServerMessageId(mid)); |
|
if(peerId < 0 && appPeersManager.isChannel(peerId)) { |
|
const channelId = -peerId; |
|
apiManager.invokeApi('channels.readMessageContents', { |
|
channel: appChatsManager.getChannelInput(channelId), |
|
id: msgIds |
|
}).then(() => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateChannelReadMessagesContents', |
|
channel_id: channelId, |
|
messages: msgIds |
|
} |
|
}); |
|
}); |
|
} else { |
|
apiManager.invokeApi('messages.readMessageContents', { |
|
id: msgIds |
|
}).then((affectedMessages) => { |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updateShort', |
|
update: { |
|
_: 'updateReadMessagesContents', |
|
messages: msgIds, |
|
pts: affectedMessages.pts, |
|
pts_count: affectedMessages.pts_count |
|
} |
|
}); |
|
}); |
|
} |
|
} |
|
|
|
public getHistoryStorage(peerId: number, threadId?: number) { |
|
if(threadId) { |
|
//threadId = this.getLocalMessageId(threadId); |
|
if(!this.threadsStorage[peerId]) this.threadsStorage[peerId] = {}; |
|
return this.threadsStorage[peerId][threadId] ?? (this.threadsStorage[peerId][threadId] = {count: null, history: new SlicedArray()}); |
|
} |
|
|
|
return this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: new SlicedArray()}); |
|
} |
|
|
|
private handleNotifications = () => { |
|
window.clearTimeout(this.notificationsHandlePromise); |
|
this.notificationsHandlePromise = 0; |
|
|
|
//var timeout = $rootScope.idle.isIDLE && StatusManager.isOtherDeviceActive() ? 30000 : 1000; |
|
//const timeout = 1000; |
|
|
|
for(const _peerId in this.notificationsToHandle) { |
|
const peerId = +_peerId; |
|
const notifyPeerToHandle = this.notificationsToHandle[peerId]; |
|
|
|
Promise.all([ |
|
appNotificationsManager.getPeerMuted(peerId), |
|
appNotificationsManager.getNotifySettings(appPeersManager.getInputNotifyPeerById(peerId, true)) |
|
]).then(([muted, peerTypeNotifySettings]) => { |
|
const topMessage = notifyPeerToHandle.topMessage; |
|
if(muted || !topMessage.pFlags.unread) { |
|
return; |
|
} |
|
|
|
//setTimeout(() => { |
|
if(topMessage.pFlags.unread) { |
|
this.notifyAboutMessage(topMessage, { |
|
fwdCount: notifyPeerToHandle.fwdCount, |
|
peerTypeNotifySettings |
|
}); |
|
} |
|
//}, timeout); |
|
}); |
|
} |
|
|
|
this.notificationsToHandle = {}; |
|
}; |
|
|
|
public handleUpdate(update: Update) { |
|
/* if(DEBUG) { |
|
this.log.debug('handleUpdate', update._, update); |
|
} */ |
|
|
|
switch(update._) { |
|
case 'updateMessageID': { |
|
const randomId = update.random_id; |
|
const pendingData = this.pendingByRandomId[randomId]; |
|
//this.log('AMM updateMessageID:', update, pendingData); |
|
if(pendingData) { |
|
const {peerId, tempId, storage} = pendingData; |
|
//const mid = update.id; |
|
const mid = this.generateMessageId(update.id); |
|
const message = this.getMessageFromStorage(storage, mid); |
|
if(!message.deleted) { |
|
const historyStorage = this.getHistoryStorage(peerId); |
|
historyStorage.history.delete(tempId); |
|
|
|
this.finalizePendingMessageCallbacks(storage, tempId, mid); |
|
} else { |
|
this.pendingByMessageId[mid] = randomId; |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updateNewMessage': |
|
case 'updateNewChannelMessage': { |
|
const message = update.message as MyMessage; |
|
const peerId = this.getMessagePeer(message); |
|
const storage = this.getMessagesStorage(peerId); |
|
const foundDialog = this.getDialogByPeerId(peerId); |
|
|
|
if(!foundDialog.length) { |
|
let good = true; |
|
if(peerId < 0) { |
|
const chat = appChatsManager.getChat(-peerId); |
|
if(chat._ === 'channelForbidden' |
|
|| chat._ === 'chatForbidden' |
|
|| (chat as Chat.chat).pFlags.left |
|
|| (chat as Chat.chat).pFlags.kicked |
|
|| (chat as Chat.chat).pFlags.deactivated) { |
|
good = false; |
|
} |
|
} |
|
|
|
if(good) { |
|
const set = this.newUpdatesAfterReloadToHandle[peerId] ?? (this.newUpdatesAfterReloadToHandle[peerId] = new Set()); |
|
if(set.has(update)) { |
|
this.log.error('here we go again', peerId); |
|
break; |
|
} |
|
|
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
this.scheduleHandleNewDialogs(); |
|
this.newUpdatesAfterReloadToHandle[peerId].add(update); |
|
} |
|
|
|
break; |
|
} |
|
|
|
/* if(update._ === 'updateNewChannelMessage') { |
|
const chat = appChatsManager.getChat(-peerId); |
|
if(chat.pFlags && (chat.pFlags.left || chat.pFlags.kicked)) { |
|
break; |
|
} |
|
} */ |
|
|
|
this.saveMessages([message], {storage}); |
|
// this.log.warn(dT(), 'message unread', message.mid, message.pFlags.unread) |
|
|
|
/* if((message as Message.message).grouped_id) { |
|
this.log('updateNewMessage', message); |
|
} */ |
|
|
|
const pendingMessage = this.checkPendingMessage(message); |
|
const historyStorage = this.getHistoryStorage(peerId); |
|
this.updateMessageRepliesIfNeeded(message); |
|
|
|
if(historyStorage.history.findSlice(message.mid)) { |
|
return false; |
|
} |
|
|
|
const history = historyStorage.history.slice; |
|
const topMsgId = history[0]; |
|
history.unshift(message.mid); |
|
if(message.mid < topMsgId) { |
|
//this.log.error('this should\'nt have happenned!', message, history); |
|
history.sort((a, b) => { |
|
return b - a; |
|
}); |
|
} |
|
|
|
if(historyStorage.count !== null) { |
|
historyStorage.count++; |
|
} |
|
|
|
if(this.mergeReplyKeyboard(historyStorage, message)) { |
|
rootScope.broadcast('history_reply_markup', {peerId}); |
|
} |
|
|
|
if(message.fromId > 0 && !message.pFlags.out && message.from_id) { |
|
appUsersManager.forceUserOnline(message.fromId, message.date); |
|
} |
|
|
|
if(!pendingMessage) { |
|
if(this.newMessagesToHandle[peerId] === undefined) { |
|
this.newMessagesToHandle[peerId] = []; |
|
} |
|
|
|
this.newMessagesToHandle[peerId].push(message.mid); |
|
if(!this.newMessagesHandlePromise) { |
|
this.newMessagesHandlePromise = window.setTimeout(this.handleNewMessages, 0); |
|
} |
|
} |
|
|
|
const dialog = foundDialog[0]; |
|
const inboxUnread = !message.pFlags.out && message.pFlags.unread; |
|
if(dialog) { |
|
this.setDialogTopMessage(message, dialog); |
|
if(inboxUnread) { |
|
dialog.unread_count++; |
|
} |
|
} |
|
|
|
if(inboxUnread/* && ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE) */) { |
|
const notifyPeer = message.peerId; |
|
let notifyPeerToHandle = this.notificationsToHandle[notifyPeer]; |
|
if(notifyPeerToHandle === undefined) { |
|
notifyPeerToHandle = this.notificationsToHandle[notifyPeer] = { |
|
fwdCount: 0, |
|
fromId: 0 |
|
}; |
|
} |
|
|
|
if(notifyPeerToHandle.fromId !== message.fromId) { |
|
notifyPeerToHandle.fromId = message.fromId; |
|
notifyPeerToHandle.fwdCount = 0; |
|
} |
|
|
|
if((message as Message.message).fwd_from) { |
|
notifyPeerToHandle.fwdCount++; |
|
} |
|
|
|
notifyPeerToHandle.topMessage = message; |
|
|
|
if(!this.notificationsHandlePromise) { |
|
this.notificationsHandlePromise = window.setTimeout(this.handleNotifications, 0); |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updateDialogUnreadMark': { |
|
//this.log('updateDialogUnreadMark', update); |
|
const peerId = appPeersManager.getPeerId((update.peer as DialogPeer.dialogPeer).peer); |
|
const foundDialog = this.getDialogByPeerId(peerId); |
|
|
|
if(!foundDialog.length) { |
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
this.scheduleHandleNewDialogs(); |
|
} else { |
|
const dialog = foundDialog[0]; |
|
|
|
if(!update.pFlags.unread) { |
|
delete dialog.pFlags.unread_mark; |
|
} else { |
|
dialog.pFlags.unread_mark = true; |
|
} |
|
|
|
rootScope.broadcast('dialogs_multiupdate', {peerId: dialog}); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updateFolderPeers': { // only 0 and 1 folders |
|
//this.log('updateFolderPeers', update); |
|
const peers = update.folder_peers; |
|
|
|
this.scheduleHandleNewDialogs(); |
|
peers.forEach((folderPeer) => { |
|
const {folder_id, peer} = folderPeer; |
|
|
|
const peerId = appPeersManager.getPeerId(peer); |
|
const dropped = this.dialogsStorage.dropDialog(peerId); |
|
if(!dropped.length) { |
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
} else { |
|
const dialog = dropped[0]; |
|
this.newDialogsToHandle[peerId] = dialog; |
|
|
|
if(dialog.pFlags?.pinned) { |
|
delete dialog.pFlags.pinned; |
|
this.dialogsStorage.pinnedOrders[folder_id].findAndSplice(p => p === dialog.peerId); |
|
} |
|
|
|
dialog.folder_id = folder_id; |
|
|
|
this.dialogsStorage.generateIndexForDialog(dialog); |
|
this.dialogsStorage.pushDialog(dialog); // need for simultaneously updatePinnedDialogs |
|
} |
|
}); |
|
break; |
|
} |
|
|
|
case 'updateDialogPinned': { |
|
const folderId = update.folder_id ?? 0; |
|
//this.log('updateDialogPinned', update); |
|
const peerId = appPeersManager.getPeerId((update.peer as DialogPeer.dialogPeer).peer); |
|
const foundDialog = this.getDialogByPeerId(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); |
|
} |
|
} */ |
|
|
|
this.scheduleHandleNewDialogs(); |
|
if(!foundDialog.length) { |
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
} else { |
|
const dialog = foundDialog[0]; |
|
this.newDialogsToHandle[peerId] = dialog; |
|
|
|
if(!update.pFlags.pinned) { |
|
delete dialog.pFlags.pinned; |
|
this.dialogsStorage.pinnedOrders[folderId].findAndSplice(p => p === dialog.peerId); |
|
} else { // means set |
|
dialog.pFlags.pinned = true; |
|
} |
|
|
|
this.dialogsStorage.generateIndexForDialog(dialog); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updatePinnedDialogs': { |
|
const folderId = update.folder_id ?? 0; |
|
|
|
const handleOrder = (order: number[]) => { |
|
this.dialogsStorage.pinnedOrders[folderId].length = 0; |
|
let willHandle = false; |
|
order.reverse(); // index must be higher |
|
order.forEach((peerId) => { |
|
newPinned[peerId] = true; |
|
|
|
const foundDialog = this.getDialogByPeerId(peerId); |
|
if(!foundDialog.length) { |
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
willHandle = true; |
|
return; |
|
} |
|
|
|
const dialog = foundDialog[0]; |
|
dialog.pFlags.pinned = true; |
|
this.dialogsStorage.generateIndexForDialog(dialog); |
|
|
|
this.newDialogsToHandle[peerId] = dialog; |
|
willHandle = true; |
|
}); |
|
|
|
this.dialogsStorage.getFolder(folderId).forEach(dialog => { |
|
const peerId = dialog.peerId; |
|
if(dialog.pFlags.pinned && !newPinned[peerId]) { |
|
this.newDialogsToHandle[peerId] = {reload: true}; |
|
willHandle = true; |
|
} |
|
}); |
|
|
|
if(willHandle) { |
|
this.scheduleHandleNewDialogs(); |
|
} |
|
}; |
|
|
|
//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.applyConversations(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(); |
|
} |
|
}); */ |
|
}); |
|
|
|
break; |
|
} |
|
|
|
//this.log('before order:', this.dialogsStorage[0].map(d => d.peerId)); |
|
|
|
handleOrder(update.order.map(peer => appPeersManager.getPeerId((peer as DialogPeer.dialogPeer).peer))); |
|
|
|
break; |
|
} |
|
|
|
case 'updateEditMessage': |
|
case 'updateEditChannelMessage': { |
|
const message = update.message as MyMessage; |
|
const peerId = this.getMessagePeer(message); |
|
const mid = this.generateMessageId(message.id); |
|
const storage = this.getMessagesStorage(peerId); |
|
if(storage[mid] === undefined) { |
|
break; |
|
} |
|
|
|
// console.trace(dT(), 'edit message', message) |
|
|
|
const oldMessage = this.getMessageFromStorage(storage, mid); |
|
this.saveMessages([message], {storage}); |
|
const newMessage = this.getMessageFromStorage(storage, mid); |
|
|
|
this.handleEditedMessage(oldMessage, newMessage); |
|
|
|
const dialog = this.getDialogByPeerId(peerId)[0]; |
|
const isTopMessage = dialog && dialog.top_message === mid; |
|
// @ts-ignore |
|
if(message.clear_history) { // that's will never happen |
|
if(isTopMessage) { |
|
rootScope.broadcast('dialog_flush', {peerId}); |
|
} |
|
} else { |
|
rootScope.broadcast('message_edit', { |
|
storage, |
|
peerId, |
|
mid |
|
}); |
|
|
|
if(isTopMessage || (message as Message.message).grouped_id) { |
|
const updatedDialogs: {[peerId: number]: Dialog} = {}; |
|
updatedDialogs[peerId] = dialog; |
|
rootScope.broadcast('dialogs_multiupdate', updatedDialogs); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
case 'updateReadChannelDiscussionInbox': |
|
case 'updateReadChannelDiscussionOutbox': |
|
case 'updateReadHistoryInbox': |
|
case 'updateReadHistoryOutbox': |
|
case 'updateReadChannelInbox': |
|
case 'updateReadChannelOutbox': { |
|
const channelId = (update as Update.updateReadChannelInbox).channel_id; |
|
const maxId = this.generateMessageId((update as Update.updateReadChannelInbox).max_id || (update as Update.updateReadChannelDiscussionInbox).read_max_id); |
|
const threadId = this.generateMessageId((update as Update.updateReadChannelDiscussionInbox).top_msg_id); |
|
const peerId = channelId ? -channelId : appPeersManager.getPeerId((update as Update.updateReadHistoryInbox).peer); |
|
|
|
const isOut = update._ === 'updateReadHistoryOutbox' || update._ === 'updateReadChannelOutbox' || update._ === 'updateReadChannelDiscussionOutbox' ? true : undefined; |
|
|
|
const storage = this.getMessagesStorage(peerId); |
|
const history = getObjectKeysAndSort(storage, 'desc'); |
|
const foundDialog = this.getDialogByPeerId(peerId)[0]; |
|
const stillUnreadCount = (update as Update.updateReadChannelInbox).still_unread_count; |
|
let newUnreadCount = 0; |
|
let foundAffected = false; |
|
|
|
//this.log.warn(dT(), 'read', peerId, isOut ? 'out' : 'in', maxId) |
|
|
|
const historyStorage = this.getHistoryStorage(peerId, threadId); |
|
|
|
if(peerId > 0 && isOut) { |
|
appUsersManager.forceUserOnline(peerId); |
|
} |
|
|
|
if(threadId) { |
|
const repliesKey = this.threadsToReplies[peerId + '_' + threadId]; |
|
if(repliesKey) { |
|
const [peerId, mid] = repliesKey.split('_').map(n => +n); |
|
this.updateMessage(peerId, mid, 'replies_updated'); |
|
} |
|
} |
|
|
|
for(let i = 0, length = history.length; i < length; i++) { |
|
const messageId = history[i]; |
|
if(messageId > maxId) { |
|
continue; |
|
} |
|
|
|
const message = storage[messageId]; |
|
|
|
if(message.pFlags.out !== isOut) { |
|
continue; |
|
} |
|
|
|
if(!message.pFlags.unread) { |
|
break; |
|
} |
|
|
|
if(threadId) { |
|
const replyTo = message.reply_to as MessageReplyHeader; |
|
if(!replyTo || (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) !== threadId) { |
|
continue; |
|
} |
|
} |
|
|
|
// this.log.warn('read', messageId, message.pFlags.unread, message) |
|
if(message.pFlags.unread) { |
|
delete message.pFlags.unread; |
|
if(!foundAffected) { |
|
foundAffected = true; |
|
} |
|
|
|
if(!message.pFlags.out && !threadId && stillUnreadCount === undefined) { |
|
newUnreadCount = --foundDialog.unread_count; |
|
} |
|
|
|
appNotificationsManager.cancel('msg' + messageId); |
|
} |
|
} |
|
|
|
if(isOut) historyStorage.readOutboxMaxId = maxId; |
|
else historyStorage.readMaxId = maxId; |
|
|
|
if(!threadId && foundDialog) { |
|
if(isOut) foundDialog.read_outbox_max_id = maxId; |
|
else foundDialog.read_inbox_max_id = maxId; |
|
|
|
if(!isOut) { |
|
if(newUnreadCount < 0 || !this.getReadMaxIdIfUnread(peerId)) { |
|
foundDialog.unread_count = 0; |
|
} else if(newUnreadCount && foundDialog.top_message > maxId) { |
|
foundDialog.unread_count = newUnreadCount; |
|
} |
|
} |
|
|
|
rootScope.broadcast('dialog_unread', {peerId}); |
|
} |
|
|
|
if(foundAffected) { |
|
rootScope.broadcast('messages_read'); |
|
} |
|
|
|
if(!threadId && channelId) { |
|
const threadKeyPart = peerId + '_'; |
|
for(const threadKey in this.threadsToReplies) { |
|
if(threadKey.indexOf(threadKeyPart) === 0) { |
|
const [peerId, mid] = this.threadsToReplies[threadKey].split('_').map(n => +n); |
|
rootScope.broadcast('replies_updated', this.getMessageByPeer(peerId, mid)); |
|
} |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updateChannelReadMessagesContents': |
|
case 'updateReadMessagesContents': { |
|
const channelId = (update as Update.updateChannelReadMessagesContents).channel_id; |
|
const mids = (update as Update.updateReadMessagesContents).messages.map(id => this.generateMessageId(id)); |
|
const peerId = channelId ? -channelId : this.getMessageById(mids[0]).peerId; |
|
for(const mid of mids) { |
|
const message = this.getMessageByPeer(peerId, mid); |
|
if(!message.deleted) { |
|
delete message.pFlags.media_unread; |
|
} |
|
} |
|
|
|
rootScope.broadcast('messages_media_read', {peerId, mids}); |
|
break; |
|
} |
|
|
|
case 'updateChannelAvailableMessages': { |
|
const channelId: number = update.channel_id; |
|
const messages: number[] = []; |
|
const peerId: number = -channelId; |
|
const history = this.getHistoryStorage(peerId).history.slice; |
|
if(history.length) { |
|
history.forEach((msgId: number) => { |
|
if(!update.available_min_id || msgId <= update.available_min_id) { |
|
messages.push(msgId); |
|
} |
|
}); |
|
} |
|
|
|
(update as any as Update.updateDeleteChannelMessages).messages = messages; |
|
} |
|
|
|
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.map(id => this.generateMessageId(id)); |
|
const peerId = channelId ? -channelId : this.getMessageById(messages[0]).peerId; |
|
|
|
if(!peerId) { |
|
break; |
|
} |
|
|
|
const historyUpdated = this.handleDeletedMessages(peerId, this.getMessagesStorage(peerId), messages); |
|
|
|
const historyStorage = this.getHistoryStorage(peerId); |
|
//if(historyStorage !== undefined) { |
|
for(const mid in historyUpdated.msgs) { |
|
historyStorage.history.delete(+mid); |
|
} |
|
if(historyUpdated.count && |
|
historyStorage.count !== null && |
|
historyStorage.count > 0) { |
|
historyStorage.count -= historyUpdated.count; |
|
if(historyStorage.count < 0) { |
|
historyStorage.count = 0; |
|
} |
|
} |
|
|
|
rootScope.broadcast('history_delete', {peerId, msgs: historyUpdated.msgs}); |
|
//} |
|
|
|
const foundDialog = this.getDialogByPeerId(peerId)[0]; |
|
if(foundDialog) { |
|
if(historyUpdated.unread) { |
|
foundDialog.unread_count -= historyUpdated.unread; |
|
|
|
rootScope.broadcast('dialog_unread', {peerId}); |
|
} |
|
|
|
if(historyUpdated.msgs[foundDialog.top_message]) { |
|
this.reloadConversation(peerId); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
case 'updateChannel': { |
|
const channelId: number = update.channel_id; |
|
const peerId = -channelId; |
|
const channel = appChatsManager.getChat(channelId); |
|
|
|
const needDialog = channel._ === 'channel' && (!channel.pFlags.left && !channel.pFlags.kicked); |
|
const foundDialog = this.getDialogByPeerId(peerId); |
|
const hasDialog = foundDialog.length > 0; |
|
|
|
const canViewHistory = channel._ === 'channel' && (channel.username || !channel.pFlags.left && !channel.pFlags.kicked); |
|
const hasHistory = this.historiesStorage[peerId] !== undefined; |
|
|
|
if(canViewHistory !== hasHistory) { |
|
delete this.historiesStorage[peerId]; |
|
rootScope.broadcast('history_forbidden', peerId); |
|
} |
|
|
|
if(hasDialog !== needDialog) { |
|
if(needDialog) { |
|
this.reloadConversation(-channelId); |
|
} else { |
|
if(foundDialog[0]) { |
|
this.dialogsStorage.dropDialog(peerId); |
|
rootScope.broadcast('dialog_drop', {peerId: peerId, dialog: foundDialog[0]}); |
|
} |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
// @ts-ignore |
|
case 'updateChannelReload': { |
|
// @ts-ignore |
|
const channelId: number = update.channel_id; |
|
const peerId = -channelId; |
|
|
|
this.dialogsStorage.dropDialog(peerId); |
|
|
|
delete this.historiesStorage[peerId]; |
|
this.reloadConversation(-channelId).then(() => { |
|
rootScope.broadcast('history_reload', peerId); |
|
}); |
|
|
|
break; |
|
} |
|
|
|
case 'updateChannelMessageViews': { |
|
const views = update.views; |
|
//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; |
|
rootScope.broadcast('message_views', {mid, views}); |
|
} |
|
break; |
|
} |
|
|
|
case 'updateServiceNotification': { |
|
//this.log('updateServiceNotification', update); |
|
const fromId = 777000; |
|
const peerId = fromId; |
|
const messageId = this.generateTempMessageId(peerId); |
|
const message: any = { |
|
_: 'message', |
|
id: messageId, |
|
from_id: appPeersManager.getOutputPeer(fromId), |
|
peer_id: appPeersManager.getOutputPeer(peerId), |
|
pFlags: {unread: true}, |
|
date: (update.inbox_date || tsNow(true)) + serverTimeManager.serverTimeOffset, |
|
message: update.message, |
|
media: update.media, |
|
entities: update.entities |
|
}; |
|
if(!appUsersManager.hasUser(fromId)) { |
|
appUsersManager.saveApiUsers([{ |
|
_: 'user', |
|
id: fromId, |
|
pFlags: {verified: true}, |
|
access_hash: 0, |
|
first_name: 'Telegram', |
|
phone: '42777' |
|
}]); |
|
} |
|
this.saveMessages([message], {isOutgoing: true}); |
|
|
|
if(update.inbox_date) { |
|
this.pendingTopMsgs[peerId] = messageId; |
|
this.handleUpdate({ |
|
_: 'updateNewMessage', |
|
message |
|
} as any); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updatePinnedMessages': |
|
case 'updatePinnedChannelMessages': { |
|
const channelId = update._ === 'updatePinnedChannelMessages' ? update.channel_id : undefined; |
|
const peerId = channelId ? -channelId : appPeersManager.getPeerId((update as Update.updatePinnedMessages).peer); |
|
|
|
/* const storage = this.getSearchStorage(peerId, 'inputMessagesFilterPinned'); |
|
if(storage.count !== storage.history.length) { |
|
if(storage.count !== undefined) { |
|
delete this.searchesStorage[peerId]['inputMessagesFilterPinned']; |
|
} |
|
|
|
rootScope.broadcast('peer_pinned_messages', peerId); |
|
break; |
|
} */ |
|
|
|
const messages = update.messages.map(id => this.generateMessageId(id)); |
|
|
|
const storage = this.getMessagesStorage(peerId); |
|
const missingMessages = messages.filter(mid => !storage[mid]); |
|
const getMissingPromise = missingMessages.length ? Promise.all(missingMessages.map(mid => this.wrapSingleMessage(peerId, mid))) : Promise.resolve(); |
|
getMissingPromise.finally(() => { |
|
const werePinned = update.pFlags?.pinned; |
|
if(werePinned) { |
|
for(const mid of messages) { |
|
//storage.history.push(mid); |
|
const message = storage[mid]; |
|
message.pFlags.pinned = true; |
|
} |
|
|
|
/* if(this.pinnedMessages[peerId]?.maxId) { |
|
const maxMid = Math.max(...messages); |
|
this.pinnedMessages |
|
} */ |
|
|
|
//storage.history.sort((a, b) => b - a); |
|
} else { |
|
for(const mid of messages) { |
|
//storage.history.findAndSplice(_mid => _mid === mid); |
|
const message = storage[mid]; |
|
delete message.pFlags.pinned; |
|
} |
|
} |
|
|
|
/* const info = this.pinnedMessages[peerId]; |
|
if(info) { |
|
info.count += messages.length * (werePinned ? 1 : -1); |
|
} */ |
|
|
|
delete this.pinnedMessages[peerId]; |
|
appStateManager.getState().then(state => { |
|
delete state.hiddenPinnedMessages[peerId]; |
|
rootScope.broadcast('peer_pinned_messages', {peerId, mids: messages, pinned: werePinned}); |
|
}); |
|
}); |
|
|
|
break; |
|
} |
|
|
|
case 'updateNotifySettings': { |
|
const {peer, notify_settings} = update; |
|
if(peer._ === 'notifyPeer') { |
|
const peerId = appPeersManager.getPeerId((peer as NotifyPeer.notifyPeer).peer); |
|
|
|
const dialog = this.getDialogByPeerId(peerId)[0]; |
|
if(dialog) { |
|
dialog.notify_settings = notify_settings; |
|
rootScope.broadcast('dialog_notify_settings', dialog); |
|
} |
|
} |
|
|
|
/////this.log('updateNotifySettings', peerId, notify_settings); |
|
break; |
|
} |
|
|
|
case 'updateNewScheduledMessage': { |
|
const message = update.message as MyMessage; |
|
const peerId = this.getMessagePeer(message); |
|
|
|
const storage = this.scheduledMessagesStorage[peerId]; |
|
if(storage) { |
|
const mid = this.generateMessageId(message.id); |
|
|
|
const oldMessage = this.getMessageFromStorage(storage, mid); |
|
this.saveMessages([message], {storage, isScheduled: true}); |
|
const newMessage = this.getMessageFromStorage(storage, mid); |
|
|
|
if(!oldMessage.deleted) { |
|
this.handleEditedMessage(oldMessage, newMessage); |
|
rootScope.broadcast('message_edit', {storage, peerId, mid: message.mid}); |
|
} else { |
|
const pendingMessage = this.checkPendingMessage(message); |
|
if(!pendingMessage) { |
|
rootScope.broadcast('scheduled_new', {peerId, mid: message.mid}); |
|
} |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'updateDeleteScheduledMessages': { |
|
const peerId = appPeersManager.getPeerId(update.peer); |
|
|
|
const storage = this.scheduledMessagesStorage[peerId]; |
|
if(storage) { |
|
const mids = update.messages.map(id => this.generateMessageId(id)); |
|
this.handleDeletedMessages(peerId, storage, mids); |
|
|
|
rootScope.broadcast('scheduled_delete', {peerId, mids}); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
private updateMessageRepliesIfNeeded(threadMessage: MyMessage) { |
|
try { // * на всякий случай, скорее всего это не понадобится |
|
if(threadMessage.peerId < 0 && threadMessage.reply_to) { |
|
const threadId = threadMessage.reply_to.reply_to_top_id || threadMessage.reply_to.reply_to_msg_id; |
|
const threadKey = threadMessage.peerId + '_' + threadId; |
|
const repliesKey = this.threadsToReplies[threadKey]; |
|
if(repliesKey) { |
|
const [peerId, mid] = repliesKey.split('_').map(n => +n); |
|
|
|
this.updateMessage(peerId, mid, 'replies_updated'); |
|
} |
|
} |
|
} catch(err) { |
|
this.log.error('incrementMessageReplies err', err, threadMessage); |
|
} |
|
} |
|
|
|
public updateMessage(peerId: number, mid: number, broadcastEventName?: 'replies_updated'): Promise<Message.message> { |
|
const promise: Promise<Message.message> = this.wrapSingleMessage(peerId, mid, true).then(() => { |
|
const message = this.getMessageByPeer(peerId, mid); |
|
|
|
if(broadcastEventName) { |
|
rootScope.broadcast(broadcastEventName, message); |
|
} |
|
|
|
return message; |
|
}); |
|
|
|
return promise; |
|
} |
|
|
|
private checkPendingMessage(message: any) { |
|
const randomId = this.pendingByMessageId[message.mid]; |
|
let pendingMessage: any; |
|
if(randomId) { |
|
const pendingData = this.pendingByRandomId[randomId]; |
|
if(pendingMessage = this.finalizePendingMessage(randomId, message)) { |
|
rootScope.broadcast('history_update', {storage: pendingData.storage, peerId: message.peerId, mid: message.mid}); |
|
} |
|
|
|
delete this.pendingByMessageId[message.mid]; |
|
} |
|
|
|
return pendingMessage; |
|
} |
|
|
|
public mutePeer(peerId: number) { |
|
const settings: InputPeerNotifySettings = { |
|
_: 'inputPeerNotifySettings' |
|
}; |
|
|
|
const dialog = appMessagesManager.getDialogByPeerId(peerId)[0]; |
|
let muted = true; |
|
if(dialog && dialog.notify_settings) { |
|
muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0); |
|
} |
|
|
|
if(!muted) { |
|
settings.mute_until = 2147483647; |
|
} |
|
|
|
return appNotificationsManager.updateNotifySettings({ |
|
_: 'inputNotifyPeer', |
|
peer: appPeersManager.getInputPeerById(peerId) |
|
}, settings); |
|
} |
|
|
|
public canWriteToPeer(peerId: number) { |
|
if(peerId < 0) { |
|
const isChannel = appPeersManager.isChannel(peerId); |
|
const hasRights = isChannel && appChatsManager.hasRights(-peerId, 'send_messages'); |
|
return !isChannel || hasRights; |
|
} else { |
|
return appUsersManager.canSendToUser(peerId); |
|
} |
|
} |
|
|
|
public finalizePendingMessage(randomId: string, finalMessage: any) { |
|
const pendingData = this.pendingByRandomId[randomId]; |
|
// this.log('pdata', randomID, pendingData) |
|
|
|
if(pendingData) { |
|
const {peerId, tempId, storage} = pendingData; |
|
const historyStorage = this.getHistoryStorage(peerId); |
|
|
|
// this.log('pending', randomID, historyStorage.pending) |
|
historyStorage.history.delete(tempId); |
|
|
|
const message = this.getMessageFromStorage(storage, tempId); |
|
if(!message.deleted) { |
|
delete message.pFlags.is_outgoing; |
|
delete message.pending; |
|
delete message.error; |
|
delete message.random_id; |
|
delete message.send; |
|
|
|
rootScope.broadcast('messages_pending'); |
|
} |
|
|
|
delete this.pendingByRandomId[randomId]; |
|
|
|
this.finalizePendingMessageCallbacks(storage, tempId, finalMessage.mid); |
|
|
|
return message; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public finalizePendingMessageCallbacks(storage: MessagesStorage, tempId: number, mid: number) { |
|
const message = this.getMessageFromStorage(storage, mid); |
|
const callbacks = this.tempFinalizeCallbacks[tempId]; |
|
//this.log.warn(callbacks, tempId); |
|
if(callbacks !== undefined) { |
|
for(const name in callbacks) { |
|
const {deferred, callback} = callbacks[name]; |
|
//this.log(`finalizePendingMessageCallbacks: will invoke ${name} callback`); |
|
callback(message).then(deferred.resolve, deferred.reject); |
|
} |
|
|
|
delete this.tempFinalizeCallbacks[tempId]; |
|
} |
|
|
|
// set cached url to media |
|
if(message.media) { |
|
if(message.media.photo) { |
|
const photo = appPhotosManager.getPhoto('' + tempId); |
|
if(/* photo._ !== 'photoEmpty' */photo) { |
|
const newPhoto = message.media.photo as MyPhoto; |
|
// костыль |
|
defineNotNumerableProperties(newPhoto, ['downloaded', 'url']); |
|
newPhoto.downloaded = photo.downloaded; |
|
newPhoto.url = photo.url; |
|
|
|
const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize; |
|
defineNotNumerableProperties(photoSize, ['url']); |
|
photoSize.url = photo.url; |
|
|
|
const downloadOptions = appPhotosManager.getPhotoDownloadOptions(newPhoto, photoSize); |
|
const fileName = getFileNameByLocation(downloadOptions.location); |
|
appDownloadManager.fakeDownload(fileName, photo.url); |
|
} |
|
} else if(message.media.document) { |
|
const doc = appDocsManager.getDoc('' + tempId); |
|
if(doc) { |
|
if(/* doc._ !== 'documentEmpty' && */doc.type && doc.type !== 'sticker') { |
|
const newDoc = message.media.document; |
|
newDoc.downloaded = doc.downloaded; |
|
newDoc.url = doc.url; |
|
|
|
const fileName = appDocsManager.getInputFileName(newDoc); |
|
appDownloadManager.fakeDownload(fileName, doc.url); |
|
} |
|
} |
|
} else if(message.media.poll) { |
|
delete appPollsManager.polls[tempId]; |
|
delete appPollsManager.results[tempId]; |
|
} |
|
} |
|
|
|
const tempMessage = this.getMessageFromStorage(storage, tempId); |
|
delete storage[tempId]; |
|
|
|
rootScope.broadcast('message_sent', {storage, tempId, tempMessage, mid}); |
|
} |
|
|
|
public incrementMaxSeenId(maxId: number) { |
|
if(!maxId || !(!this.maxSeenId || maxId > this.maxSeenId)) { |
|
return false; |
|
} |
|
|
|
this.maxSeenId = maxId; |
|
|
|
apiManager.invokeApi('messages.receivedMessages', { |
|
max_id: this.getServerMessageId(maxId) |
|
}); |
|
} |
|
|
|
private notifyAboutMessage(message: MyMessage, options: Partial<{ |
|
fwdCount: number, |
|
peerTypeNotifySettings: PeerNotifySettings |
|
}> = {}) { |
|
const peerId = this.getMessagePeer(message); |
|
let peerString: string; |
|
const notification: NotifyOptions = {}; |
|
var notificationMessage: string, |
|
notificationPhoto: any; |
|
|
|
const _ = (str: string) => str; |
|
|
|
const localSettings = appNotificationsManager.getLocalSettings(); |
|
if(options.peerTypeNotifySettings.show_previews) { |
|
if(message._ === 'message' && message.fwd_from && options.fwdCount) { |
|
notificationMessage = 'Forwarded ' + options.fwdCount + ' messages';//fwdMessagesPluralize(options.fwd_count); |
|
} else { |
|
notificationMessage = this.wrapMessageForReply(message, undefined, undefined, true); |
|
} |
|
} else { |
|
notificationMessage = 'New notification'; |
|
} |
|
|
|
if(peerId > 0) { |
|
const fromUser = appUsersManager.getUser(message.fromId); |
|
const fromPhoto = appUsersManager.getUserPhoto(message.fromId); |
|
|
|
notification.title = (fromUser.first_name || '') + |
|
(fromUser.first_name && fromUser.last_name ? ' ' : '') + |
|
(fromUser.last_name || ''); |
|
if(!notification.title) { |
|
notification.title = fromUser.phone || _('conversation_unknown_user_raw'); |
|
} |
|
|
|
notificationPhoto = fromPhoto; |
|
|
|
peerString = appUsersManager.getUserString(peerId); |
|
} else { |
|
notification.title = appChatsManager.getChat(-peerId).title || _('conversation_unknown_chat_raw'); |
|
|
|
if(message.fromId) { |
|
var fromUser = appUsersManager.getUser(message.fromId); |
|
notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) + |
|
' @ ' + |
|
notification.title; |
|
} |
|
|
|
notificationPhoto = appChatsManager.getChatPhoto(-peerId); |
|
|
|
peerString = appChatsManager.getChatString(-peerId); |
|
} |
|
|
|
notification.title = RichTextProcessor.wrapPlainText(notification.title); |
|
|
|
notification.onclick = () => { |
|
/* rootScope.broadcast('history_focus', { |
|
peerString: peerString, |
|
messageID: message.flags & 16 ? message.mid : 0 |
|
}); */ |
|
}; |
|
|
|
notification.message = notificationMessage; |
|
notification.key = 'msg' + message.mid; |
|
notification.tag = peerString; |
|
notification.silent = true;//message.pFlags.silent || false; |
|
|
|
/* if(notificationPhoto.location && !notificationPhoto.location.empty) { |
|
apiManager.downloadSmallFile(notificationPhoto.location, notificationPhoto.size).then(function (blob) { |
|
if (message.pFlags.unread) { |
|
notification.image = blob |
|
NotificationsManager.notify(notification) |
|
} |
|
}) |
|
} else { */ |
|
appNotificationsManager.notify(notification); |
|
//} |
|
} |
|
|
|
public getScheduledMessagesStorage(peerId: number) { |
|
return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = this.createMessageStorage()); |
|
} |
|
|
|
public getScheduledMessages(peerId: number): Promise<number[]> { |
|
if(!this.canWriteToPeer(peerId)) return Promise.resolve([]); |
|
|
|
const storage = this.getScheduledMessagesStorage(peerId); |
|
if(Object.keys(storage).length) { |
|
return Promise.resolve(Object.keys(storage).map(id => +id)); |
|
} |
|
|
|
return apiManager.invokeApi('messages.getScheduledHistory', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
hash: 0 |
|
}).then(historyResult => { |
|
if(historyResult._ !== 'messages.messagesNotModified') { |
|
appUsersManager.saveApiUsers(historyResult.users); |
|
appChatsManager.saveApiChats(historyResult.chats); |
|
|
|
const storage = this.getScheduledMessagesStorage(peerId); |
|
this.saveMessages(historyResult.messages, {storage, isScheduled: true}); |
|
return Object.keys(storage).map(id => +id); |
|
} |
|
|
|
return []; |
|
}); |
|
} |
|
|
|
public sendScheduledMessages(peerId: number, mids: number[]) { |
|
return apiManager.invokeApi('messages.sendScheduledMessages', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
id: mids.map(mid => this.getServerMessageId(mid)) |
|
}).then(updates => { |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}); |
|
} |
|
|
|
public deleteScheduledMessages(peerId: number, mids: number[]) { |
|
return apiManager.invokeApi('messages.deleteScheduledMessages', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
id: mids.map(mid => this.getServerMessageId(mid)) |
|
}).then(updates => { |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
}); |
|
} |
|
|
|
/** |
|
* * https://core.telegram.org/api/offsets, offset_id is inclusive |
|
*/ |
|
public getHistory(peerId: number, maxId = 0, limit: number, backLimit?: number, threadId?: number): Promise<HistoryResult> | HistoryResult { |
|
const historyStorage = this.getHistoryStorage(peerId, threadId); |
|
|
|
let offset = 0; |
|
/* |
|
let offsetFound = true; |
|
|
|
if(maxId) { |
|
offsetFound = false; |
|
for(; offset < historyStorage.history.length; offset++) { |
|
if(maxId > historyStorage.history.slice[offset]) { |
|
offsetFound = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if(offsetFound && ( |
|
historyStorage.count !== null && historyStorage.history.length === historyStorage.count || |
|
historyStorage.history.length >= offset + limit |
|
)) { |
|
if(backLimit) { |
|
backLimit = Math.min(offset, backLimit); |
|
offset = Math.max(0, offset - backLimit); |
|
limit += backLimit; |
|
} else { |
|
limit = limit; |
|
} |
|
|
|
const history = historyStorage.history.slice.slice(offset, offset + limit); |
|
return { |
|
count: historyStorage.count, |
|
history: history, |
|
offsetIdOffset: offset |
|
}; |
|
} |
|
|
|
if(offsetFound) { |
|
offset = 0; |
|
} */ |
|
|
|
if(backLimit) { |
|
offset = -backLimit; |
|
limit += backLimit; |
|
|
|
/* return this.requestHistory(reqPeerId, maxId, limit, offset, undefined, threadId).then((historyResult) => { |
|
historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; |
|
|
|
const history = (historyResult.messages as MyMessage[]).map(message => message.mid); |
|
return { |
|
count: historyStorage.count, |
|
history, |
|
offsetIdOffset: (historyResult as MessagesMessages.messagesMessagesSlice).offset_id_offset || 0 |
|
}; |
|
}); */ |
|
} |
|
|
|
const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit); |
|
if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both))) { |
|
return { |
|
count: historyStorage.count, |
|
history: haveSlice.slice, |
|
offsetIdOffset: haveSlice.offsetIdOffset |
|
}; |
|
} |
|
|
|
return this.fillHistoryStorage(peerId, maxId, limit, offset, historyStorage, threadId).then(() => { |
|
const slice = historyStorage.history.sliceMe(maxId, offset, limit); |
|
return { |
|
count: historyStorage.count, |
|
history: slice?.slice || historyStorage.history.constructSlice(), |
|
offsetIdOffset: slice?.offsetIdOffset || historyStorage.count |
|
}; |
|
}); |
|
} |
|
|
|
public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> { |
|
return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => { |
|
historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; |
|
|
|
const offsetIdOffset = (historyResult as MessagesMessages.messagesMessagesSlice).offset_id_offset || 0; |
|
const isTopEnd = offsetIdOffset >= (historyStorage.count - limit) || historyStorage.count < (limit + add_offset); |
|
|
|
/* if(!maxId && historyResult.messages.length) { |
|
maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1); |
|
} |
|
|
|
const wasTotalCount = historyStorage.history.length; */ |
|
|
|
historyResult.messages.forEach((message) => { |
|
if(this.mergeReplyKeyboard(historyStorage, message)) { |
|
rootScope.broadcast('history_reply_markup', {peerId}); |
|
} |
|
}); |
|
|
|
const mids = historyResult.messages.map((message) => (message as MyMessage).mid); |
|
// * add bound manually. |
|
// * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id') |
|
if(offset_id && !mids.includes(offset_id) && offsetIdOffset < historyStorage.count) { |
|
let i = 0; |
|
for(const length = mids.length; i < length; ++i) { |
|
if(offset_id > mids[i]) { |
|
break; |
|
} |
|
} |
|
|
|
mids.splice(i, 0, offset_id); |
|
} |
|
|
|
historyStorage.history.insertSlice(mids); |
|
|
|
if(isTopEnd) { |
|
historyStorage.history.last.setEnd(SliceEnd.Top); |
|
} |
|
|
|
/* const isBackLimit = offset < 0 && -offset !== fullLimit; |
|
if(isBackLimit) { |
|
return; |
|
} |
|
|
|
const totalCount = historyStorage.history.length; |
|
fullLimit -= (totalCount - wasTotalCount); |
|
|
|
const migratedNextPeer = this.migratedFromTo[peerId]; |
|
const migratedPrevPeer = this.migratedToFrom[peerId] |
|
const isMigrated = migratedNextPeer !== undefined || migratedPrevPeer !== undefined; |
|
|
|
if(isMigrated) { |
|
historyStorage.count = Math.max(historyStorage.count, totalCount) + 1; |
|
} |
|
|
|
if(fullLimit > 0) { |
|
maxId = historyStorage.history.slice[totalCount - 1]; |
|
if(isMigrated) { |
|
if(!historyResult.messages.length) { |
|
if(migratedPrevPeer) { |
|
maxId = 0; |
|
peerId = migratedPrevPeer; |
|
} else { |
|
historyStorage.count = totalCount; |
|
return true; |
|
} |
|
} |
|
|
|
return this.fillHistoryStorage(peerId, maxId, fullLimit, historyStorage, threadId); |
|
} else if(totalCount < historyStorage.count) { |
|
return this.fillHistoryStorage(peerId, maxId, fullLimit, offset, historyStorage, threadId); |
|
} |
|
} */ |
|
}); |
|
} |
|
|
|
public requestHistory(peerId: number, maxId: number, limit = 0, offset = 0, offsetDate = 0, threadId = 0): Promise<Exclude<MessagesMessages, MessagesMessages.messagesMessagesNotModified>> { |
|
//console.trace('requestHistory', peerId, maxId, limit, offset); |
|
|
|
//rootScope.broadcast('history_request'); |
|
|
|
const options: any = { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
offset_id: this.getServerMessageId(maxId) || 0, |
|
offset_date: offsetDate, |
|
add_offset: offset, |
|
limit, |
|
max_id: 0, |
|
min_id: 0, |
|
hash: 0 |
|
}; |
|
|
|
if(threadId) { |
|
options.msg_id = this.getServerMessageId(threadId) || 0; |
|
} |
|
|
|
const promise: ReturnType<AppMessagesManager['requestHistory']> = apiManager.invokeApi(threadId ? 'messages.getReplies' : 'messages.getHistory', options, { |
|
//timeout: APITIMEOUT, |
|
noErrorBox: true |
|
}) as any; |
|
|
|
return promise.then((historyResult) => { |
|
if(DEBUG) { |
|
this.log('requestHistory result:', peerId, historyResult, maxId, limit, offset); |
|
} |
|
|
|
appUsersManager.saveApiUsers(historyResult.users); |
|
appChatsManager.saveApiChats(historyResult.chats); |
|
this.saveMessages(historyResult.messages); |
|
|
|
if(appPeersManager.isChannel(peerId)) { |
|
apiUpdatesManager.addChannelState(-peerId, (historyResult as MessagesMessages.messagesChannelMessages).pts); |
|
} |
|
|
|
let length = historyResult.messages.length; |
|
if(length && historyResult.messages[length - 1].deleted) { |
|
historyResult.messages.splice(length - 1, 1); |
|
length--; |
|
(historyResult as MessagesMessages.messagesMessagesSlice).count--; |
|
} |
|
|
|
// will load more history if last message is album grouped (because it can be not last item) |
|
const historyStorage = this.getHistoryStorage(peerId, threadId); |
|
// historyResult.messages: desc sorted |
|
if(length && (historyResult.messages[length - 1] as Message.message).grouped_id |
|
&& (historyStorage.history.length + historyResult.messages.length) < (historyResult as MessagesMessages.messagesMessagesSlice).count) { |
|
return this.requestHistory(peerId, (historyResult.messages[length - 1] as Message.message).mid, 10, 0, offsetDate, threadId).then((_historyResult) => { |
|
return historyResult; |
|
}); |
|
} |
|
|
|
return historyResult; |
|
}, (error) => { |
|
switch (error.type) { |
|
case 'CHANNEL_PRIVATE': |
|
let channel = appChatsManager.getChat(-peerId); |
|
channel = {_: 'channelForbidden', access_hash: channel.access_hash, title: channel.title}; |
|
apiUpdatesManager.processUpdateMessage({ |
|
_: 'updates', |
|
updates: [{ |
|
_: 'updateChannel', |
|
channel_id: -peerId |
|
}], |
|
chats: [channel], |
|
users: [] |
|
}); |
|
break; |
|
} |
|
|
|
throw error; |
|
}); |
|
} |
|
|
|
public fetchSingleMessages() { |
|
if(this.fetchSingleMessagesPromise) { |
|
return this.fetchSingleMessagesPromise; |
|
} |
|
|
|
return this.fetchSingleMessagesPromise = new Promise((resolve) => { |
|
setTimeout(() => { |
|
let promises: Promise<void>[] = []; |
|
|
|
for(const peerId in this.needSingleMessages) { |
|
const mids = this.needSingleMessages[peerId]; |
|
delete this.needSingleMessages[peerId]; |
|
|
|
const msgIds: InputMessage[] = mids.map((msgId: number) => { |
|
return { |
|
_: 'inputMessageID', |
|
id: this.getServerMessageId(msgId) |
|
}; |
|
}); |
|
|
|
let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>; |
|
if(+peerId < 0 && appPeersManager.isChannel(+peerId)) { |
|
promise = apiManager.invokeApi('channels.getMessages', { |
|
channel: appChatsManager.getChannelInput(-+peerId), |
|
id: msgIds |
|
}); |
|
} else { |
|
promise = apiManager.invokeApi('messages.getMessages', { |
|
id: msgIds |
|
}); |
|
} |
|
|
|
promises.push(promise.then(getMessagesResult => { |
|
if(getMessagesResult._ !== 'messages.messagesNotModified') { |
|
appUsersManager.saveApiUsers(getMessagesResult.users); |
|
appChatsManager.saveApiChats(getMessagesResult.chats); |
|
this.saveMessages(getMessagesResult.messages); |
|
} |
|
|
|
rootScope.broadcast('messages_downloaded', {peerId: +peerId, mids}); |
|
})); |
|
} |
|
|
|
Promise.all(promises).finally(() => { |
|
this.fetchSingleMessagesPromise = null; |
|
if(Object.keys(this.needSingleMessages).length) this.fetchSingleMessages(); |
|
resolve(); |
|
}); |
|
}, 0); |
|
}); |
|
} |
|
|
|
public wrapSingleMessage(peerId: number, msgId: number, overwrite = false): Promise<void> { |
|
if(!this.getMessageByPeer(peerId, msgId).deleted && !overwrite) { |
|
rootScope.broadcast('messages_downloaded', {peerId, mids: [msgId]}); |
|
return Promise.resolve(); |
|
} else if(!this.needSingleMessages[peerId] || this.needSingleMessages[peerId].indexOf(msgId) === -1) { |
|
(this.needSingleMessages[peerId] ?? (this.needSingleMessages[peerId] = [])).push(msgId); |
|
return this.fetchSingleMessages(); |
|
} else if(this.fetchSingleMessagesPromise) { |
|
return this.fetchSingleMessagesPromise; |
|
} |
|
} |
|
|
|
public setTyping(peerId: number, _action: any): Promise<boolean> { |
|
if(!rootScope.myId || !peerId || !this.canWriteToPeer(peerId) || peerId === rootScope.myId) return Promise.resolve(false); |
|
|
|
const action: SendMessageAction = typeof(_action) === 'string' ? {_: _action} : _action; |
|
return apiManager.invokeApi('messages.setTyping', { |
|
peer: appPeersManager.getInputPeerById(peerId), |
|
action |
|
}) as Promise<boolean>; |
|
} |
|
|
|
private handleDeletedMessages(peerId: number, storage: MessagesStorage, messages: number[]) { |
|
const history: { |
|
count: number, |
|
unread: number, |
|
msgs: {[mid: number]: true}, |
|
albums?: {[groupId: string]: Set<number>}, |
|
} = {count: 0, unread: 0, msgs: {}} as any; |
|
|
|
for(const mid of messages) { |
|
const message: MyMessage = this.getMessageFromStorage(storage, mid); |
|
if(message.deleted) continue; |
|
|
|
if((message as Message.message).media) { |
|
// @ts-ignore |
|
const c = message.media.webpage || message.media; |
|
const smth = c.photo || c.document; |
|
|
|
if(smth?.file_reference) { |
|
referenceDatabase.deleteContext(smth.file_reference, {type: 'message', peerId, messageId: mid}); |
|
} |
|
|
|
// @ts-ignore |
|
if(message.media.webpage) { |
|
// @ts-ignore |
|
appWebPagesManager.deleteWebPageFromPending(message.media.webpage, mid); |
|
} |
|
} |
|
|
|
this.updateMessageRepliesIfNeeded(message); |
|
|
|
if(!message.pFlags.out && !message.pFlags.is_outgoing && message.pFlags.unread) { |
|
history.unread++; |
|
appNotificationsManager.cancel('msg' + mid); |
|
} |
|
history.count++; |
|
history.msgs[mid] = true; |
|
|
|
message.deleted = true; |
|
|
|
if(message._ !== 'messageService' && message.grouped_id) { |
|
const groupedStorage = this.groupedMessagesStorage[message.grouped_id]; |
|
if(groupedStorage) { |
|
delete groupedStorage[mid]; |
|
|
|
if(!history.albums) history.albums = {}; |
|
(history.albums[message.grouped_id] || (history.albums[message.grouped_id] = new Set())).add(mid); |
|
|
|
if(!Object.keys(groupedStorage).length) { |
|
delete history.albums; |
|
delete this.groupedMessagesStorage[message.grouped_id]; |
|
} |
|
} |
|
} |
|
|
|
delete storage[mid]; |
|
|
|
const peerMessagesToHandle = this.newMessagesToHandle[peerId]; |
|
if(peerMessagesToHandle && peerMessagesToHandle.length) { |
|
const peerMessagesHandlePos = peerMessagesToHandle.indexOf(mid); |
|
if(peerMessagesHandlePos !== -1) { |
|
peerMessagesToHandle.splice(peerMessagesHandlePos); |
|
} |
|
} |
|
} |
|
|
|
if(history.albums) { |
|
for(const groupId in history.albums) { |
|
rootScope.broadcast('album_edit', {peerId, groupId, deletedMids: [...history.albums[groupId]]}); |
|
/* const mids = this.getMidsByAlbum(groupId); |
|
if(mids.length) { |
|
const mid = Math.max(...mids); |
|
rootScope.$broadcast('message_edit', {peerId, mid, justMedia: false}); |
|
} */ |
|
} |
|
} |
|
|
|
return history; |
|
} |
|
|
|
private handleEditedMessage(oldMessage: any, newMessage: any) { |
|
if(oldMessage.media?.webpage) { |
|
appWebPagesManager.deleteWebPageFromPending(oldMessage.media.webpage, oldMessage.mid); |
|
} |
|
} |
|
} |
|
|
|
const appMessagesManager = new AppMessagesManager(); |
|
MOUNT_CLASS_TO.appMessagesManager = appMessagesManager; |
|
export default appMessagesManager;
|
|
|