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.

5431 lines
179 KiB

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import { LazyLoadQueueBase } from "../../components/lazyLoadQueue";
import ProgressivePreloader from "../../components/preloader";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { formatTime, tsNow } from "../../helpers/date";
import { createPosterForVideo } from "../../helpers/files";
import { copy, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string";
3 years ago
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo, Updates, ReplyMarkup, InputPeer, InputPhoto, InputDocument, InputGeoPoint, WebPage, GeoPoint, ReportReason } from "../../layer";
import { InvokeApiOptions } from "../../types";
import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack";
import { logger, LogTypes } 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 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";
3 years ago
import appDraftsManager from "./appDraftsManager";
import { getFileNameByLocation } from "../../helpers/fileName";
import appProfileManager from "./appProfileManager";
3 years ago
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug";
3 years ago
import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray";
import appNotificationsManager, { NotifyOptions } from "./appNotificationsManager";
import PeerTitle from "../../components/peerTitle";
import { forEachReverse } from "../../helpers/array";
import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment";
import htmlToSpan from "../../helpers/dom/htmlToSpan";
3 years ago
import { REPLIES_PEER_ID } from "../mtproto/mtproto_config";
import formatCallDuration from "../../helpers/formatCallDuration";
3 years ago
import appAvatarsManager from "./appAvatarsManager";
import telegramMeWebManager from "../mtproto/telegramMeWebManager";
import { getMiddleware } from "../../helpers/middleware";
import assumeType from "../../helpers/assumeType";
3 years ago
import appMessagesIdsManager from "./appMessagesIdsManager";
import type { MediaSize } from "../../helpers/mediaSizes";
//console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
4 years ago
const APITIMEOUT = 0;
3 years ago
const DO_NOT_READ_HISTORY = false;
4 years ago
export type HistoryStorage = {
4 years ago
count: number | null,
3 years ago
history: SlicedArray,
maxId?: number,
readPromise?: Promise<void>,
readMaxId?: number,
readOutboxMaxId?: number,
3 years ago
triedToReadMaxId?: number,
maxOutId?: number,
3 years ago
replyMarkup?: Exclude<ReplyMarkup, ReplyMarkup.replyInlineMarkup>
4 years ago
};
4 years ago
export type HistoryResult = {
count: number,
3 years ago
history: Slice,
offsetIdOffset?: number,
4 years ago
};
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
};
3 years ago
export type MyMessageActionType = Message.messageService['action']['_'];
type PendingAfterMsg = Partial<InvokeApiOptions & {
afterMessageId: string,
messageId: string
}>;
4 years ago
export class AppMessagesManager {
private messagesStorageByPeerId: {[peerId: string]: MessagesStorage};
public groupedMessagesStorage: {[groupId: string]: MessagesStorage}; // will be used for albums
private scheduledMessagesStorage: {[peerId: string]: MessagesStorage};
private historiesStorage: {
[peerId: string]: HistoryStorage
};
private threadsStorage: {
[peerId: string]: {
[threadId: string]: HistoryStorage
}
};
private searchesStorage: {
[peerId: string]: Partial<{
[inputFilter in MyInputMessagesFilter]: {
count?: number,
history: number[]
}
}>
};
public pinnedMessages: {[peerId: string]: PinnedStorage};
public threadsServiceMessagesIdsStorage: {[peerId_threadId: string]: number};
private threadsToReplies: {
[peerId_threadId: string]: string;
};
private pendingByRandomId: {
[randomId: string]: {
peerId: number,
tempId: number,
threadId: number,
storage: MessagesStorage
}
} = {};
private pendingByMessageId: {[mid: string]: string} = {};
private pendingAfterMsgs: {[peerId: string]: PendingAfterMsg} = {};
public pendingTopMsgs: {[peerId: string]: number} = {};
private tempNum = 0;
private tempFinalizeCallbacks: {
[tempId: string]: {
[callbackName: string]: Partial<{
deferred: CancellablePromise<void>,
callback: (message: any) => Promise<any>
}>
}
} = {};
private sendSmthLazyLoadQueue = new LazyLoadQueueBase(1);
4 years ago
private needSingleMessages: {[peerId: string]: number[]} = {};
private fetchSingleMessagesPromise: Promise<void> = null;
4 years ago
private maxSeenId = 0;
4 years ago
public migratedFromTo: {[peerId: number]: number} = {};
public migratedToFrom: {[peerId: number]: number} = {};
4 years ago
private newMessagesHandleTimeout = 0;
private newMessagesToHandle: {[peerId: string]: Set<number>} = {};
private newDialogsHandlePromise: Promise<any>;
private newDialogsToHandle: {[peerId: string]: Dialog} = {};
public newUpdatesAfterReloadToHandle: {[peerId: string]: Set<Update>} = {};
private notificationsHandlePromise = 0;
private notificationsToHandle: {[peerId: string]: {
fwdCount: number,
fromId: number,
topMessage?: MyMessage
}} = {};
4 years ago
4 years ago
private reloadConversationsPromise: Promise<void>;
private reloadConversationsPeers: Set<number> = new Set();
4 years ago
public log = logger('MESSAGES', LogTypes.Error | LogTypes.Debug | LogTypes.Log | LogTypes.Warn);
public dialogsStorage: DialogsStorage;
public filtersStorage: FiltersStorage;
private groupedTempId = 0;
private typings: {[peerId: string]: {type: SendMessageAction['_'], timeout?: number}} = {};
private middleware: ReturnType<typeof getMiddleware>;
3 years ago
private unreadMentions: {[peerId: string]: SlicedArray} = {};
private goToNextMentionPromises: {[peerId: string]: Promise<any>} = {};
4 years ago
constructor() {
this.clear();
rootScope.addMultipleEventsListeners({
updateMessageID: this.onUpdateMessageId,
updateNewDiscussionMessage: this.onUpdateNewMessage,
updateNewMessage: this.onUpdateNewMessage,
updateNewChannelMessage: this.onUpdateNewMessage,
updateDialogUnreadMark: this.onUpdateDialogUnreadMark,
updateEditMessage: this.onUpdateEditMessage,
updateEditChannelMessage: this.onUpdateEditMessage,
updateReadChannelDiscussionInbox: this.onUpdateReadHistory,
updateReadChannelDiscussionOutbox: this.onUpdateReadHistory,
updateReadHistoryInbox: this.onUpdateReadHistory,
updateReadHistoryOutbox: this.onUpdateReadHistory,
updateReadChannelInbox: this.onUpdateReadHistory,
updateReadChannelOutbox: this.onUpdateReadHistory,
updateChannelReadMessagesContents: this.onUpdateReadMessagesContents,
updateReadMessagesContents: this.onUpdateReadMessagesContents,
updateChannelAvailableMessages: this.onUpdateChannelAvailableMessages,
updateDeleteMessages: this.onUpdateDeleteMessages,
updateDeleteChannelMessages: this.onUpdateDeleteMessages,
updateChannel: this.onUpdateChannel,
updateChannelReload: this.onUpdateChannelReload,
updateChannelMessageViews: this.onUpdateChannelMessageViews,
updateServiceNotification: this.onUpdateServiceNotification,
updatePinnedMessages: this.onUpdatePinnedMessages,
updatePinnedChannelMessages: this.onUpdatePinnedMessages,
updateNotifySettings: this.onUpdateNotifySettings,
updateNewScheduledMessage: this.onUpdateNewScheduledMessage,
updateDeleteScheduledMessages: this.onUpdateDeleteScheduledMessages
4 years ago
});
// ! Invalidate notify settings, can optimize though
rootScope.addEventListener('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.dispatchEvent('dialog_notify_settings', dialog);
});
});
});
3 years ago
rootScope.addEventListener('webpage_updated', ({id, msgs}) => {
msgs.forEach(({peerId, mid, isScheduled}) => {
const storage = isScheduled ? this.getScheduledMessagesStorage(peerId) : this.getMessagesStorage(peerId);
const message = this.getMessageFromStorage(storage, mid) as Message.message;
if(!message) return;
message.media = {
_: 'messageMediaWebPage',
3 years ago
webpage: appWebPagesManager.getWebPage(id)
};
rootScope.dispatchEvent('message_edit', {
storage,
peerId,
mid
});
4 years ago
});
});
3 years ago
rootScope.addEventListener('draft_updated', ({peerId, threadId, draft}) => {
3 years ago
if(threadId) return;
4 years ago
const dialog = this.getDialogOnly(peerId);
3 years ago
if(dialog && !threadId) {
dialog.draft = draft;
this.dialogsStorage.generateIndexForDialog(dialog);
this.dialogsStorage.pushDialog(dialog);
4 years ago
rootScope.dispatchEvent('dialog_draft', {
peerId,
draft,
4 years ago
index: dialog.index
});
3 years ago
} else {
this.reloadConversation(peerId);
4 years ago
}
3 years ago
});
appStateManager.getState().then(state => {
if(state.maxSeenMsgId) {
this.maxSeenId = state.maxSeenMsgId;
}
});
}
public clear() {
if(this.middleware) {
this.middleware.clean();
} else {
this.middleware = getMiddleware();
}
this.messagesStorageByPeerId = {};
this.groupedMessagesStorage = {};
this.scheduledMessagesStorage = {};
this.historiesStorage = {};
this.threadsStorage = {};
this.searchesStorage = {};
this.pinnedMessages = {};
this.threadsServiceMessagesIdsStorage = {};
this.threadsToReplies = {};
this.dialogsStorage && this.dialogsStorage.clear();
this.filtersStorage && this.filtersStorage.clear();
}
public construct() {
this.filtersStorage = new FiltersStorage(this, appPeersManager, appUsersManager, appNotificationsManager, appStateManager, apiUpdatesManager, /* apiManager, */ rootScope);
3 years ago
this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, appUsersManager, appDraftsManager, appNotificationsManager, appStateManager, apiUpdatesManager, serverTimeManager, appMessagesIdsManager);
}
public getInputEntities(entities: MessageEntity[]) {
const sendEntites = copy(entities);
sendEntites.forEach((entity) => {
if(entity._ === 'messageEntityMentionName') {
(entity as any as MessageEntity.inputMessageEntityMentionName)._ = 'inputMessageEntityMentionName';
(entity as any as MessageEntity.inputMessageEntityMentionName).user_id = appUsersManager.getUserInput(entity.user_id);
4 years ago
}
});
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<{
3 years ago
entities: MessageEntity[],
replyToMsgId: number,
threadId: number,
viaBotId: number,
queryId: string,
resultId: string,
noWebPage: true,
3 years ago
replyMarkup: ReplyMarkup,
clearDraft: true,
3 years ago
webPage: WebPage,
scheduleDate: number,
silent: true
}> = {}) {
if(typeof(text) !== 'string' || !text.length) {
4 years ago
return;
}
//this.checkSendOptions(options);
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
const MAX_LENGTH = rootScope.config.message_length_max;
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;
4 years ago
let entities = options.entities || [];
if(!options.viaBotId) {
4 years ago
text = RichTextProcessor.parseMarkdown(text, entities);
//entities = RichTextProcessor.mergeEntities(entities, RichTextProcessor.parseEntities(text));
4 years ago
}
let sendEntites = this.getInputEntities(entities);
if(!sendEntites.length) {
sendEntites = undefined;
}
const message = this.generateOutgoingMessage(peerId, options);
message.entities = entities;
message.message = text;
3 years ago
const replyToMsgId = options.replyToMsgId ? appMessagesIdsManager.getServerMessageId(options.replyToMsgId) : undefined;
const isChannel = appPeersManager.isChannel(peerId);
4 years ago
if(options.webPage) {
message.media = {
_: 'messageMediaWebPage',
webpage: options.webPage
};
}
const toggleError = (on: boolean) => {
4 years ago
if(on) {
message.error = true;
} else {
delete message.error;
}
rootScope.dispatchEvent('messages_pending');
};
4 years ago
message.send = () => {
4 years ago
toggleError(false);
const sentRequestOptions: PendingAfterMsg = {};
if(this.pendingAfterMsgs[peerId]) {
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId;
4 years ago
}
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
4 years ago
}, sentRequestOptions);
} else {
apiPromise = apiManager.invokeApiAfter('messages.sendMessage', {
no_webpage: options.noWebPage,
peer: appPeersManager.getInputPeerById(peerId),
4 years ago
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
4 years ago
}, sentRequestOptions);
}
/* function is<T>(value: any, condition: boolean): value is T {
return condition;
} */
//this.log('sendText', message.mid);
this.pendingAfterMsgs[peerId] = sentRequestOptions;
return apiPromise.then((updates: Updates) => {
//this.log('sendText sent', message.mid);
//if(is<Updates.updateShortSentMessage>(updates, updates._ === 'updateShortSentMessage')) {
if(updates._ === 'updateShortSentMessage') {
//assumeType<Updates.updateShortSentMessage>(updates);
4 years ago
message.date = updates.date;
message.id = updates.id;
message.media = updates.media;
message.entities = updates.entities;
this.wrapMessageEntities(message);
if(updates.pFlags.out) {
message.pFlags.out = true;
}
// * override with new updates
4 years ago
updates = {
_: 'updates',
users: [],
chats: [],
seq: 0,
3 years ago
date: undefined,
4 years ago
updates: [{
_: 'updateMessageID',
random_id: message.random_id,
4 years ago
id: updates.id
}, {
_: options.scheduleDate ? 'updateNewScheduledMessage' : (isChannel ? 'updateNewChannelMessage' : 'updateNewMessage'),
4 years ago
message: message,
pts: updates.pts,
pts_count: updates.pts_count
}]
3 years ago
};
} else if((updates as Updates.updates).updates) {
(updates as Updates.updates).updates.forEach((update) => {
if(update._ === 'updateDraftMessage') {
4 years ago
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];
4 years ago
}
});
};
4 years ago
3 years ago
this.beforeMessageSending(message, {
isScheduled: !!options.scheduleDate || undefined,
threadId: options.threadId,
clearDraft: options.clearDraft
});
4 years ago
}
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,
thumb: {
blob: Blob,
url: string,
size: MediaSize
},
duration: number,
background: true,
silent: true,
3 years ago
clearDraft: true,
scheduleDate: number,
noSound: boolean,
waveform: Uint8Array,
}> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
//this.checkSendOptions(options);
const message = this.generateOutgoingMessage(peerId, options);
3 years ago
const replyToMsgId = options.replyToMsgId ? appMessagesIdsManager.getServerMessageId(options.replyToMsgId) : undefined;
let attachType: string, apiFileName: string;
4 years ago
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);
4 years ago
let caption = options.caption || '';
this.log('sendFile', file, fileType);
const entities = options.entities || [];
4 years ago
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: SendMessageAction['_'];
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) {
4 years ago
attachType = 'document';
apiFileName = 'document.' + fileType.split('/')[1];
actionName = 'sendMessageUploadDocumentAction';
} else if(isPhoto) {
4 years ago
attachType = 'photo';
apiFileName = 'photo.' + fileType.split('/')[1];
actionName = 'sendMessageUploadPhotoAction';
const photoSize = {
_: 'photoSize',
w: options.width,
h: options.height,
type: 'full',
location: null,
size: file.size
} as PhotoSize.photoSize;
photo = {
_: 'photo',
id: '' + message.id,
sizes: [photoSize],
w: options.width,
h: options.height
} as any;
const cacheContext = appDownloadManager.getCacheContext(photo, photoSize.type);
cacheContext.downloaded = file.size;
cacheContext.url = options.objectURL || '';
photo = appPhotosManager.savePhoto(photo);
} else if(fileType.indexOf('video/') === 0) {
attachType = 'video';
4 years ago
apiFileName = 'video.mp4';
actionName = 'sendMessageUploadVideoAction';
const videoAttribute: DocumentAttribute.documentAttributeVideo = {
_: 'documentAttributeVideo',
pFlags: {
round_message: options.isRoundMessage,
supports_streaming: true
},
duration: options.duration,
w: options.width,
h: options.height
};
attributes.push(videoAttribute);
// * must follow after video attribute
if(options.noSound &&
file.size > (10 * 1024) &&
file.size < (10 * 1024 * 1024)) {
attributes.push({
_: 'documentAttributeAnimated'
});
}
} 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;
const cacheContext = appDownloadManager.getCacheContext(document);
cacheContext.downloaded = file.size;
cacheContext.url = options.objectURL || '';
let thumb: PhotoSize.photoSize;
if(isPhoto) {
attributes.push({
_: 'documentAttributeImageSize',
w: options.width,
h: options.height
});
thumb = {
_: 'photoSize',
w: options.width,
h: options.height,
type: 'full',
size: file.size
};
} else if(attachType === 'video') {
if(options.thumb) {
thumb = {
_: 'photoSize',
w: options.thumb.size.width,
h: options.thumb.size.height,
type: 'local-thumb',
size: options.thumb.blob.size
};
const thumbCacheContext = appDownloadManager.getCacheContext(document, thumb.type);
thumbCacheContext.downloaded = thumb.size;
thumbCacheContext.url = options.thumb.url;
}
}
if(thumb) {
thumbs.push(thumb);
}
/* if(thumbs.length) {
const thumb = thumbs[0] as PhotoSize.photoSize;
const docThumb = appPhotosManager.getDocumentCachedThumb(document.id);
docThumb.downloaded = thumb.size;
docThumb.url = thumb.url;
} */
document = appDocsManager.saveDoc(document);
4 years ago
}
this.log('sendFile', attachType, apiFileName, file.type, options);
4 years ago
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);
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
} as MessageMedia.messageMediaDocument : media as any;
4 years ago
const toggleError = (on: boolean) => {
4 years ago
if(on) {
message.error = true;
} else {
delete message.error;
}
rootScope.dispatchEvent('messages_pending');
4 years ago
};
let uploaded = false,
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
4 years ago
message.send = () => {
if(isDocument) {
const {id, access_hash, file_reference} = file as MyDocument;
4 years ago
const inputMedia: InputMedia = {
4 years ago
_: 'inputMediaDocument',
id: {
_: 'inputDocument',
id,
access_hash,
file_reference
4 years ago
}
};
sentDeferred.resolve(inputMedia);
4 years ago
} else if(file instanceof File || file instanceof Blob) {
const load = () => {
4 years ago
if(!uploaded || message.error) {
uploaded = false;
uploadPromise = appDownloadManager.upload(file);
sentDeferred.notifyAll({done: 0, total: file.size});
4 years ago
}
let thumbUploadPromise: typeof uploadPromise;
if(attachType === 'video' && options.objectURL) {
thumbUploadPromise = new Promise((resolve, reject) => {
const thumbPromise = options.thumb && options.thumb.blob ? Promise.resolve(options.thumb) : createPosterForVideo(options.objectURL);
thumbPromise.then(thumb => {
if(!thumb) {
resolve(null);
} else {
appDownloadManager.upload(thumb.blob).then(resolve, reject);
}
}, reject);
});
}
4 years ago
uploadPromise && uploadPromise.then(async(inputFile) => {
/* if(DEBUG) {
this.log('appMessagesManager: sendFile uploaded:', inputFile);
} */
// @ts-ignore
delete message.media.preloader;
4 years ago
inputFile.name = apiFileName;
uploaded = true;
let inputMedia: InputMedia;
4 years ago
switch(attachType) {
case 'photo':
inputMedia = {
_: 'inputMediaUploadedPhoto',
file: inputFile
};
break;
4 years ago
default:
inputMedia = {
_: 'inputMediaUploadedDocument',
file: inputFile,
mime_type: fileType,
attributes
4 years ago
};
}
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);
4 years ago
}, (/* 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));
if(actionName) {
this.setTyping(peerId, {_: actionName, progress: percents | 0});
}
sentDeferred.notifyAll(progress);
});
return sentDeferred;
};
4 years ago
if(options.isGroupedItem) {
load();
} else {
this.sendSmthLazyLoadQueue.push({
load
});
}
4 years ago
}
return sentDeferred;
4 years ago
};
3 years ago
this.beforeMessageSending(message, {
isGroupedItem: options.isGroupedItem,
isScheduled: !!options.scheduleDate || undefined,
threadId: options.threadId,
clearDraft: options.clearDraft
});
4 years ago
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,
3 years ago
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;
}
4 years ago
toggleError(true);
});
});
}
return {message, promise: sentDeferred};
4 years ago
}
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,
3 years ago
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;
3 years ago
const replyToMsgId = options.replyToMsgId ? appMessagesIdsManager.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;
});
3 years ago
if(options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
3 years ago
}
// * test pending
//return;
const toggleError = (message: any, on: boolean) => {
if(on) {
message.error = true;
} else {
delete message.error;
}
rootScope.dispatchEvent('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,
3 years ago
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);
3 years ago
inputMedia = appPhotosManager.getMediaInput(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));
});
}
3 years ago
public sendOther(peerId: number, inputMedia: InputMedia, options: Partial<{
replyToMsgId: number,
threadId: number,
viaBotId: number,
3 years ago
replyMarkup: ReplyMarkup,
clearDraft: true,
queryId: string
resultId: string,
scheduleDate: number,
3 years ago
silent: true,
geoPoint: GeoPoint
4 years ago
}> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
//this.checkSendOptions(options);
const message = this.generateOutgoingMessage(peerId, options);
3 years ago
const replyToMsgId = options.replyToMsgId ? appMessagesIdsManager.getServerMessageId(options.replyToMsgId) : undefined;
4 years ago
let media: MessageMedia;
4 years ago
switch(inputMedia._) {
case 'inputMediaPoll': {
3 years ago
const pollId = '' + message.id;
inputMedia.poll.id = pollId;
4 years ago
appPollsManager.savePoll(inputMedia.poll, {
_: 'pollResults',
flags: 4,
total_voters: 0,
pFlags: {},
});
3 years ago
const {poll, results} = appPollsManager.getPoll(pollId);
4 years ago
media = {
_: 'messageMediaPoll',
poll,
results
};
break;
}
3 years ago
case 'inputMediaPhoto': {
4 years ago
media = {
_: 'messageMediaPhoto',
3 years ago
photo: appPhotosManager.getPhoto((inputMedia.id as InputPhoto.inputPhoto).id)
4 years ago
};
break;
3 years ago
}
4 years ago
3 years ago
case 'inputMediaDocument': {
const doc = appDocsManager.getDoc((inputMedia.id as InputDocument.inputDocument).id);
/* if(doc.sticker && doc.stickerSetInput) {
4 years ago
appStickersManager.pushPopularSticker(doc.id);
3 years ago
} */
4 years ago
media = {
_: 'messageMediaDocument',
3 years ago
document: doc
4 years ago
};
break;
3 years ago
}
4 years ago
3 years ago
case 'inputMediaContact': {
4 years ago
media = {
_: 'messageMediaContact',
phone_number: inputMedia.phone_number,
first_name: inputMedia.first_name,
last_name: inputMedia.last_name,
3 years ago
user_id: 0,
vcard: inputMedia.vcard
4 years ago
};
break;
3 years ago
}
4 years ago
3 years ago
case 'inputMediaGeoPoint': {
4 years ago
media = {
_: 'messageMediaGeo',
3 years ago
geo: options.geoPoint
4 years ago
};
break;
3 years ago
}
4 years ago
3 years ago
case 'inputMediaVenue': {
4 years ago
media = {
_: 'messageMediaVenue',
3 years ago
geo: options.geoPoint,
4 years ago
title: inputMedia.title,
address: inputMedia.address,
provider: inputMedia.provider,
3 years ago
venue_id: inputMedia.venue_id,
venue_type: inputMedia.venue_type
4 years ago
};
break;
3 years ago
}
// @ts-ignore
case 'messageMediaPending': {
4 years ago
media = inputMedia;
3 years ago
break;
}
4 years ago
}
message.media = media;
4 years ago
let toggleError = (on: boolean) => {
/* const historyMessage = this.messagesForHistory[messageId];
4 years ago
if (on) {
message.error = true
if (historyMessage) {
historyMessage.error = true
}
} else {
delete message.error
if (historyMessage) {
delete historyMessage.error
}
} */
rootScope.dispatchEvent('messages_pending');
4 years ago
};
message.send = () => {
const sentRequestOptions: PendingAfterMsg = {};
if(this.pendingAfterMsgs[peerId]) {
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId;
4 years ago
}
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,
3 years ago
clear_draft: options.clearDraft,
schedule_date: options.scheduleDate,
silent: options.silent
4 years ago
}, sentRequestOptions);
} else {
apiPromise = apiManager.invokeApiAfter('messages.sendMedia', {
peer: appPeersManager.getInputPeerById(peerId),
4 years ago
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
4 years ago
}, sentRequestOptions);
}
this.pendingAfterMsgs[peerId] = sentRequestOptions;
return apiPromise.then((updates) => {
4 years ago
if(updates.updates) {
3 years ago
updates.updates.forEach((update: Update) => {
if(update._ === 'updateDraftMessage') {
3 years ago
update.local = true;
4 years ago
}
});
}
apiUpdatesManager.processUpdateMessage(updates);
}, (error) => {
toggleError(true);
}).finally(() => {
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) {
delete this.pendingAfterMsgs[peerId];
4 years ago
}
});
};
4 years ago
3 years ago
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;
}
}
} */
4 years ago
3 years ago
private beforeMessageSending(message: Message.message, options: Partial<{
3 years ago
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);
4 years ago
if(options.isScheduled) {
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isScheduled: true, isOutgoing: true});
setTimeout(() => {
rootScope.dispatchEvent('scheduled_new', {peerId, mid: messageId});
}, 0);
} else {
/* if(options.threadId && this.threadsStorage[peerId]) {
3 years ago
delete this.threadsStorage[peerId][options.threadId];
} */
const storages: HistoryStorage[] = [
this.getHistoryStorage(peerId),
options.threadId ? this.getHistoryStorage(peerId, options.threadId) : undefined
];
for(const storage of storages) {
if(storage) {
storage.history.unshift(messageId);
}
}
3 years ago
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true});
setTimeout(() => {
this.setDialogTopMessage(message);
rootScope.dispatchEvent('history_append', {storage, peerId, mid: messageId});
}, 0);
}
3 years ago
if(!options.isGroupedItem && options.clearDraft) {
3 years ago
appDraftsManager.clearDraft(peerId, options.threadId);
3 years ago
}
this.pendingByRandomId[message.random_id] = {
peerId,
tempId: messageId,
threadId: options.threadId,
storage
};
4 years ago
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,
3 years ago
replyMarkup: ReplyMarkup,
}>) {
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
const message: Message.message = {
_: '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,
3 years ago
reply_markup: options.replyMarkup,
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 {
3 years ago
return appPeersManager.getOutputPeer(appUsersManager.getSelf().id);
}
}
private generateFlags(peerId: number) {
3 years ago
const pFlags: Message.message['pFlags'] = {};
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 generateFakeAvatarMessage(peerId: number, photo: Photo) {
const maxId = Number.MAX_SAFE_INTEGER;
const message = {
_: 'messageService',
action: {
_: 'messageActionChannelEditPhoto',
photo
},
mid: maxId,
peerId,
date: (photo as Photo.photo).date,
fromId: peerId
} as Message.messageService;
this.getMessagesStorage(peerId)[maxId] = message;
return message;
}
public setDialogTopMessage(message: MyMessage, dialog: MTDialog.dialog = this.getDialogOnly(message.peerId)) {
if(dialog) {
dialog.top_message = message.mid;
const historyStorage = this.getHistoryStorage(message.peerId);
historyStorage.maxId = message.mid;
3 years ago
this.dialogsStorage.generateIndexForDialog(dialog, false, message);
this.scheduleHandleNewDialogs(message.peerId, dialog);
}
4 years ago
}
public cancelPendingMessage(randomId: string) {
const pendingData = this.pendingByRandomId[randomId];
4 years ago
/* if(DEBUG) {
this.log('cancelPendingMessage', randomId, pendingData);
} */
4 years ago
if(pendingData) {
const {peerId, tempId, storage} = pendingData;
const historyStorage = this.getHistoryStorage(peerId);
4 years ago
apiUpdatesManager.processLocalUpdate({
_: 'updateDeleteMessages',
messages: [tempId],
pts: undefined,
pts_count: undefined
4 years ago
});
historyStorage.history.delete(tempId);
4 years ago
delete this.pendingByRandomId[randomId];
delete storage[tempId];
4 years ago
return true;
}
return false;
}
public async refreshConversations() {
const limit = 200, outDialogs: Dialog[] = [];
for(let folderId = 0; folderId < 2; ++folderId) {
let offsetDate = 0;
for(;;) {
const {dialogs, isEnd} = await this.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);
3 years ago
const mid = appMessagesIdsManager.generateMessageId(dialog.top_message);
offsetDate = this.getMessageByPeer(peerId, mid).date;
if(!offsetDate) {
console.error('refreshConversations: got no offsetDate', dialog);
break;
}
}
if(isEnd) {
break;
}
}
}
let obj: {[peerId: string]: Dialog} = {};
outDialogs.forEach(dialog => {
obj[dialog.peerId] = dialog;
});
rootScope.dispatchEvent('dialogs_multiupdate', obj);
return outDialogs;
}
public async getConversationsAll(query = '', folderId = 0) {
const limit = 200, outDialogs: Dialog[] = [];
for(; folderId < 2; ++folderId) {
let offsetIndex = 0;
for(;;) {
const {dialogs} = await appMessagesManager.getConversations(query, offsetIndex, limit, folderId).promise;
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, skipMigrated?: boolean) {
return this.dialogsStorage.getDialogs(query, offsetIndex, limit, folderId, skipMigrated);
4 years ago
}
public getReadMaxIdIfUnread(peerId: number, threadId?: number) {
const historyStorage = this.getHistoryStorage(peerId, threadId);
3 years ago
if(threadId) {
const chatHistoryStorage = this.getHistoryStorage(peerId);
3 years ago
const readMaxId = Math.max(chatHistoryStorage.readMaxId ?? 0, historyStorage.readMaxId);
const message = this.getMessageByPeer(peerId, historyStorage.maxId); // usually message is missing, so pFlags.out won't be there anyway
return !message.pFlags.out && readMaxId < historyStorage.maxId ? readMaxId : 0;
3 years ago
} else {
const message = this.getMessageByPeer(peerId, historyStorage.maxId);
const readMaxId = peerId > 0 ? Math.max(historyStorage.readMaxId, historyStorage.readOutboxMaxId) : historyStorage.readMaxId;
return !message.pFlags.out && readMaxId < historyStorage.maxId ? readMaxId : 0;
3 years ago
}
}
3 years ago
// public lolSet = new Set();
public getTopMessages(limit: number, folderId: number, offsetDate?: number) {
//const dialogs = this.dialogsStorage.getFolder(folderId);
let offsetId = 0;
let offsetPeerId = 0;
let offsetIndex = 0;
4 years ago
if(offsetDate === undefined) {
offsetDate = this.dialogsStorage.getOffsetDate(folderId);
}
if(offsetDate) {
offsetIndex = offsetDate * 0x10000;
offsetDate += serverTimeManager.serverTimeOffset;
4 years ago
}
const middleware = this.middleware.get();
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
return apiManager.invokeApiSingle('messages.getDialogs', {
folder_id: folderId,
4 years ago
offset_date: offsetDate,
offset_id: offsetId,
offset_peer: appPeersManager.getInputPeerById(offsetPeerId),
limit,
hash: 0
4 years ago
}, {
//timeout: APITIMEOUT,
noErrorBox: true
}).then((dialogsResult) => {
if(!middleware() || dialogsResult._ === 'messages.dialogsNotModified') return null;
3 years ago
if(DEBUG) {
this.log('messages.getDialogs result:', dialogsResult.dialogs, {...dialogsResult.dialogs[0]});
}
/* if(!offsetDate) {
4 years ago
telegramMeWebService.setAuthorized(true);
} */
4 years ago
// can reset pinned order here
if(!offsetId && !offsetDate && !offsetPeerId) {
this.dialogsStorage.resetPinnedOrder(folderId);
}
if(!offsetDate) {
telegramMeWebManager.setAuthorized(true);
}
4 years ago
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.dialogsStorage.saveDialog(dialog, dialog.folder_id ?? folderId, true);
if(!maxSeenIdIncremented &&
!appPeersManager.isChannel(dialog.peerId || appPeersManager.getPeerId(dialog.peer))) {
this.incrementMaxSeenId(dialog.top_message);
maxSeenIdIncremented = true;
}
if(dialog.peerId === undefined) {
return;
}
3 years ago
// if(!folderId && !dialog.folder_id) {
// this.lolSet.add(dialog.peerId);
// }
/* if(dialog.peerId === -1213511294) {
this.log.error('lun bot', folderId, d);
} */
4 years ago
if(offsetIndex && dialog.index > offsetIndex) {
this.scheduleHandleNewDialogs(dialog.peerId, dialog);
4 years ago
hasPrepend = true;
}
// ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет
// ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога
3 years ago
if(!appMessagesIdsManager.getServerMessageId(dialog.read_inbox_max_id) && !appMessagesIdsManager.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);
} */
4 years ago
}
});
4 years ago
if(Object.keys(noIdsDialogs).length) {
4 years ago
//setTimeout(() => { // test bad situation
this.reloadConversation(Object.keys(noIdsDialogs).map(id => +id)).then(() => {
rootScope.dispatchEvent('dialogs_multiupdate', noIdsDialogs);
4 years ago
for(let peerId in noIdsDialogs) {
rootScope.dispatchEvent('dialog_unread', {peerId: +peerId});
4 years ago
}
});
//}, 10e3);
}
const count = (dialogsResult as MessagesDialogs.messagesDialogsSlice).count;
// exclude empty draft dialogs
const dialogs = this.dialogsStorage.getFolder(folderId, false);
let dialogsLength = 0;
for(let i = 0, length = dialogs.length; i < length; ++i) {
3 years ago
if(appMessagesIdsManager.getServerMessageId(dialogs[i].top_message)) {
++dialogsLength;
}
}
const isEnd = /* limit > dialogsResult.dialogs.length || */
!count ||
dialogsLength >= count ||
!dialogsResult.dialogs.length;
if(isEnd) {
3 years ago
this.dialogsStorage.setDialogsLoaded(folderId, true);
4 years ago
}
if(hasPrepend) {
this.scheduleHandleNewDialogs();
4 years ago
} else {
rootScope.dispatchEvent('dialogs_multiupdate', {});
4 years ago
}
return {
isEnd,
count,
dialogs: (dialogsResult as MessagesDialogs.messagesDialogsSlice).dialogs
};
4 years ago
});
}
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: Message.message = this.getMessageByPeer(fromPeerId, mid);
const message: Message.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 => {
// @ts-ignore
message[key] = originalMessage[key];
});
const document = (message.media as MessageMedia.messageMediaDocument)?.document as MyDocument;
if(document) {
const types: MyDocument['type'][] = ['round', 'voice'];
if(types.includes(document.type)) {
(message as MyMessage).pFlags.media_unread = true;
}
}
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: PendingAfterMsg = {};
if(this.pendingAfterMsgs[peerId]) {
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId;
}
const promise = /* true ? Promise.resolve() : */apiManager.invokeApiAfter('messages.forwardMessages', {
from_peer: appPeersManager.getInputPeerById(fromPeerId),
3 years ago
id: mids.map(mid => appMessagesIdsManager.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] || {
4 years ago
_: 'messageEmpty',
id: messageId,
deleted: true,
pFlags: {}
4 years ago
};
}
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);
}
4 years ago
public getMessagePeer(message: any): number {
const toId = message.peer_id && appPeersManager.getPeerId(message.peer_id) || 0;
4 years ago
return toId;
4 years ago
}
public getDialogByPeerId(peerId: number): [Dialog, number] | [] {
return this.dialogsStorage.getDialog(peerId);
4 years ago
}
public getDialogOnly(peerId: number) {
return this.dialogsStorage.getDialogOnly(peerId);
}
public reloadConversation(peerId?: number | number[]) {
if(peerId !== undefined) {
[].concat(peerId).forEach(peerId => {
if(!this.reloadConversationsPeers.has(peerId)) {
this.reloadConversationsPeers.add(peerId);
//this.log('will reloadConversation', peerId);
}
});
}
4 years ago
4 years ago
if(this.reloadConversationsPromise) return this.reloadConversationsPromise;
return this.reloadConversationsPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const peers = Array.from(this.reloadConversationsPeers).map(peerId => appPeersManager.getInputDialogPeerById(peerId));
this.reloadConversationsPeers.clear();
4 years ago
apiManager.invokeApi('messages.getPeerDialogs', {peers}).then((result) => {
this.dialogsStorage.applyDialogs(result);
4 years ago
resolve();
}, reject).finally(() => {
this.reloadConversationsPromise = null;
if(this.reloadConversationsPeers.size) {
this.reloadConversation();
}
4 years ago
});
}, 0);
});
4 years ago
}
private doFlushHistory(peer: InputPeer, just_clear?: boolean, revoke?: boolean): Promise<true> {
return apiManager.invokeApiSingle('messages.deleteHistory', {
just_clear,
revoke,
peer,
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(peer, just_clear, revoke);
});
}
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.invokeApiSingle('channels.deleteHistory', {
channel: appChatsManager.getChannelInput(channelId),
3 years ago
max_id: appMessagesIdsManager.getServerMessageId(maxId)
}).then(() => {
apiUpdatesManager.processLocalUpdate({
_: '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];
delete this.scheduledMessagesStorage[peerId];
delete this.threadsStorage[peerId];
delete this.searchesStorage[peerId];
delete this.pinnedMessages[peerId];
delete this.pendingAfterMsgs[peerId];
delete this.pendingTopMsgs[peerId];
delete this.needSingleMessages[peerId];
if(justClear) {
rootScope.dispatchEvent('dialog_flush', {peerId});
} else {
delete this.notificationsToHandle[peerId];
delete this.typings[peerId];
this.reloadConversationsPeers.delete(peerId);
this.dialogsStorage.dropDialog(peerId);
rootScope.dispatchEvent('dialog_drop', {peerId});
}
});
}
public hidePinnedMessages(peerId: number) {
return Promise.all([
appStateManager.getState(),
this.getPinnedMessage(peerId)
])
.then(([state, pinned]) => {
state.hiddenPinnedMessages[peerId] = pinned.maxId;
rootScope.dispatchEvent('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;
});
}
3 years ago
public updatePinnedMessage(peerId: number, mid: number, unpin?: boolean, silent?: boolean, pm_oneside?: boolean) {
return apiManager.invokeApi('messages.updatePinnedMessage', {
peer: appPeersManager.getInputPeerById(peerId),
unpin,
silent,
3 years ago
pm_oneside,
3 years ago
id: appMessagesIdsManager.getServerMessageId(mid)
}).then(updates => {
//this.log('pinned updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
});
}
public unpinAllMessages(peerId: number): Promise<boolean> {
return apiManager.invokeApiSingle('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.dispatchEvent('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.getDialogOnly(peerId);
3 years ago
return appMessagesIdsManager.generateMessageId(dialog?.top_message || 0, true);
}
public saveMessages(messages: any[], options: Partial<{
storage: MessagesStorage,
isScheduled: true,
isOutgoing: true,
//isNew: boolean, // * new - from update
}> = {}) {
//let groups: Set<string>;
messages.forEach((message) => {
if(message.pFlags === undefined) {
message.pFlags = {};
4 years ago
}
if(message._ === 'messageEmpty') {
4 years ago
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);
4 years ago
if(options.isScheduled) {
message.pFlags.is_scheduled = true;
}
if(options.isOutgoing) {
message.pFlags.is_outgoing = true;
}
3 years ago
const mid = appMessagesIdsManager.generateMessageId(message.id);
message.mid = mid;
4 years ago
if(message.grouped_id) {
const storage = this.groupedMessagesStorage[message.grouped_id] ?? (this.groupedMessagesStorage[message.grouped_id] = {});
storage[mid] = message;
}
const dialog = this.getDialogOnly(peerId);
if(dialog && mid) {
if(mid > dialog[message.pFlags.out
4 years ago
? 'read_outbox_max_id'
: 'read_inbox_max_id']) {
message.pFlags.unread = true;
}
4 years ago
}
// this.log(dT(), 'msg unread', mid, apiMessage.pFlags.out, dialog && dialog[apiMessage.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id'])
4 years ago
if(message.reply_to) {
if(message.reply_to.reply_to_msg_id) {
3 years ago
message.reply_to.reply_to_msg_id = message.reply_to_mid = appMessagesIdsManager.generateMessageId(message.reply_to.reply_to_msg_id);
}
3 years ago
if(message.reply_to.reply_to_top_id) message.reply_to.reply_to_top_id = appMessagesIdsManager.generateMessageId(message.reply_to.reply_to_top_id);
}
if(message.replies) {
3 years ago
if(message.replies.max_id) message.replies.max_id = appMessagesIdsManager.generateMessageId(message.replies.max_id);
if(message.replies.read_max_id) message.replies.read_max_id = appMessagesIdsManager.generateMessageId(message.replies.read_max_id);
4 years ago
}
const overwriting = !!peerId;
if(!overwriting) {
message.date -= serverTimeManager.serverTimeOffset;
}
//storage.generateIndex(message);
const myId = appUsersManager.getSelf().id;
message.peerId = peerId;
if(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);
}
4 years ago
const fwdHeader = message.fwd_from as MessageFwdHeader;
4 years ago
if(fwdHeader) {
//if(peerId === myID) {
3 years ago
if(fwdHeader.saved_from_msg_id) fwdHeader.saved_from_msg_id = appMessagesIdsManager.generateMessageId(fwdHeader.saved_from_msg_id);
if(fwdHeader.channel_post) fwdHeader.channel_post = appMessagesIdsManager.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);
3 years ago
const savedFromMid = appMessagesIdsManager.generateMessageId(msgId);
message.savedFrom = savedFromPeerId + '_' + savedFromMid;
4 years ago
}
/* 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 {
4 years ago
apiMessage.fwdPostID = fwdHeader.channel_post;
} */
4 years ago
message.fwdFromId = appPeersManager.getPeerId(fwdHeader.from_id);
if(!overwriting) {
fwdHeader.date -= serverTimeManager.serverTimeOffset;
}
4 years ago
}
if(message.via_bot_id > 0) {
message.viaBotId = message.via_bot_id;
4 years ago
}
const mediaContext: ReferenceContext = {
type: 'message',
peerId,
messageId: mid
4 years ago
};
if(message.media) {
switch(message.media._) {
4 years ago
case 'messageMediaEmpty':
delete message.media;
4 years ago
break;
case 'messageMediaPhoto':
if(message.media.ttl_seconds) {
message.media = {_: 'messageMediaUnsupportedWeb'};
4 years ago
} else {
message.media.photo = appPhotosManager.savePhoto(message.media.photo, mediaContext);
4 years ago
}
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;
4 years ago
case 'messageMediaDocument':
if(message.media.ttl_seconds) {
message.media = {_: 'messageMediaUnsupportedWeb'};
4 years ago
} else {
message.media.document = appDocsManager.saveDoc(message.media.document, mediaContext); // 11.04.2020 warning
4 years ago
}
4 years ago
break;
case 'messageMediaWebPage':
3 years ago
const messageKey = appWebPagesManager.getMessageKeyForPendingWebPage(peerId, mid, options.isScheduled);
message.media.webpage = appWebPagesManager.saveWebPage(message.media.webpage, messageKey, mediaContext);
4 years ago
break;
/*case 'messageMediaGame':
4 years ago
AppGamesManager.saveGame(apiMessage.media.game, apiMessage.mid, mediaContext);
apiMessage.media.handleMessage = true;
break; */
case 'messageMediaInvoice':
message.media = {_: 'messageMediaUnsupportedWeb'};
4 years ago
break;
}
}
if(message.action) {
const action = message.action;
let migrateFrom: number;
let migrateTo: number;
const suffix = message.fromId === appUsersManager.getSelf().id ? 'You' : '';
switch(action._) {
//case 'messageActionChannelEditPhoto':
4 years ago
case 'messageActionChatEditPhoto':
action.photo = appPhotosManager.savePhoto(action.photo, mediaContext);
if(action.photo.video_sizes) {
action._ = isBroadcast ? 'messageActionChannelEditVideo' : 'messageActionChatEditVideo';
} else {
if(isBroadcast) { // ! messageActionChannelEditPhoto не существует в принципе, это используется для перевода.
action._ = 'messageActionChannelEditPhoto';
}
4 years ago
}
break;
case 'messageActionGroupCall': {
//assumeType<MessageAction.messageActionGroupCall>(action);
let type: string;
if(action.duration === undefined) {
type = 'started';
if(peerId !== message.fromId) {
type += '_by' + suffix;
}
} else {
type = 'ended_by' + suffix;
}
action.type = type;
break;
}
4 years ago
case 'messageActionChatEditTitle':
/* if(options.isNew) {
const chat = appChatsManager.getChat(-peerId);
chat.title = action.title;
appChatsManager.saveApiChat(chat, true);
} */
4 years ago
if(isBroadcast) {
action._ = 'messageActionChannelEditTitle';
4 years ago
}
break;
case 'messageActionChatDeletePhoto':
if(isBroadcast) {
action._ = 'messageActionChannelDeletePhoto';
4 years ago
}
break;
case 'messageActionChatAddUser':
if(action.users.length === 1) {
action.user_id = action.users[0];
if(message.fromId === action.user_id) {
4 years ago
if(isChannel) {
action._ = 'messageActionChatJoined' + suffix;
4 years ago
} else {
action._ = 'messageActionChatReturn' + suffix;
4 years ago
}
}
} else if(action.users.length > 1) {
action._ = 'messageActionChatAddUsers';
4 years ago
}
break;
case 'messageActionChatDeleteUser':
if(message.fromId === action.user_id) {
action._ = 'messageActionChatLeave' + suffix;
4 years ago
}
break;
case 'messageActionChannelMigrateFrom':
migrateFrom = -action.chat_id;
migrateTo = -channelId;
4 years ago
break
case 'messageActionChatMigrateTo':
migrateFrom = -channelId;
migrateTo = -action.channel_id;
4 years ago
break;
case 'messageActionHistoryClear':
//apiMessage.deleted = true;
message.clear_history = true;
delete message.pFlags.out;
delete message.pFlags.unread;
4 years ago
break;
case 'messageActionPhoneCall':
action.type =
(message.pFlags.out ? 'out_' : 'in_') +
4 years ago
(
action.reason._ === 'phoneCallDiscardReasonMissed' ||
action.reason._ === 'phoneCallDiscardReasonBusy'
4 years ago
? 'missed'
: 'ok'
);
break;
}
if(migrateFrom &&
migrateTo &&
!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo]) {
this.migrateChecks(migrateFrom, migrateTo);
}
}
/* if(message.grouped_id) {
if(!groups) {
3 years ago
groups = new Set();
}
3 years ago
groups.add(message.grouped_id);
} else {
message.rReply = this.getRichReplyText(message);
} */
if(message.message && message.message.length && !message.totalEntities) {
this.wrapMessageEntities(message);
4 years ago
}
storage[mid] = message;
});
/* if(groups) {
3 years ago
for(const groupId of groups) {
const mids = this.groupedMessagesStorage[groupId];
for(const mid in mids) {
3 years ago
const message = this.groupedMessagesStorage[groupId][mid];
message.rReply = this.getRichReplyText(message);
}
}
} */
}
private wrapMessageEntities(message: any) {
const apiEntities = message.entities ? message.entities.slice() : [];
message.message = RichTextProcessor.fixEmoji(message.message, apiEntities);
const myEntities = RichTextProcessor.parseEntities(message.message);
message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
}
public wrapMessageForReply(message: any, text: string, usingMids: number[], plain: true, highlightWord?: string, withoutMediaType?: boolean): string;
public wrapMessageForReply(message: any, text?: string, usingMids?: number[], plain?: false, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment;
public wrapMessageForReply(message: any, text: string = message.message, usingMids?: number[], plain?: boolean, highlightWord?: string, withoutMediaType?: boolean): 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 && !withoutMediaType) {
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;
}
3 years ago
case 'messageMediaDocument': {
const 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') {
3 years ago
addPart(undefined, ((plain ? document.stickerEmojiRaw : document.stickerEmoji) || ''));
addPart('AttachSticker');
text = '';
} else {
addPart(undefined, plain ? document.file_name : RichTextProcessor.wrapEmojiText(document.file_name), message.message);
}
break;
3 years ago
}
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) {
highlightWord = highlightWord.trim();
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;
}
4 years ago
}
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;
}
3 years ago
public wrapMessageActionTextNew(message: any, plain: true): string;
public wrapMessageActionTextNew(message: any, plain?: false): HTMLElement;
public wrapMessageActionTextNew(message: any, plain: boolean): HTMLElement | string;
3 years ago
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);
3 years ago
if((action as MessageAction.messageActionCustomAction).message) {
if(plain) {
return RichTextProcessor.wrapPlainText(message.message);
3 years ago
} else {
element.innerHTML = RichTextProcessor.wrapRichText((action as MessageAction.messageActionCustomAction).message, {noLinebreaks: true});
3 years ago
return element;
}
} else {
let _ = action._;
//let suffix = '';
let langPackKey: LangPackKey;
3 years ago
let args: any[];
const getNameDivHTML = (peerId: number, plain: boolean) => {
return plain ? appPeersManager.getPeerTitle(peerId, plain) + ' ' : (new PeerTitle({peerId})).element;
3 years ago
};
switch(action._) {
case 'messageActionPhoneCall': {
3 years ago
_ += '.' + (action as any).type;
args = [formatCallDuration(action.duration)];
break;
}
case 'messageActionGroupCall': {
_ += '.' + (action as any).type;
args = [];
if(!_.endsWith('You')) {
args.push(getNameDivHTML(message.fromId, plain));
}
args.push(formatCallDuration(action.duration));
break;
}
case 'messageActionInviteToGroupCall': {
const peerIds = [message.fromId, action.users[0]];
let a = 'ActionGroupCall';
const myId = appUsersManager.getSelf().id;
if(peerIds[0] === myId) a += 'You';
a += 'Invited';
if(peerIds[1] === myId) a += 'You';
peerIds.findAndSplice(peerId => peerId === myId);
langPackKey = a as LangPackKey;
args = peerIds.map(peerId => getNameDivHTML(peerId, plain));
break;
}
case 'messageActionGroupCallScheduled': {
const today = new Date();
const date = new Date(action.schedule_date * 1000);
const daysToStart = (date.getTime() - today.getTime()) / 86400e3;
const tomorrowDate = new Date(today);
tomorrowDate.setDate(tomorrowDate.getDate() + 1);
3 years ago
const isBroadcast = appPeersManager.isBroadcast(message.peerId);
langPackKey = isBroadcast ? 'ChatList.Service.VoiceChatScheduled.Channel' : 'ChatList.Service.VoiceChatScheduled';
args = [];
const myId = appUsersManager.getSelf().id;
if(message.fromId === myId) {
langPackKey += 'You';
3 years ago
} else if(!isBroadcast) {
args.push(getNameDivHTML(message.fromId, plain));
}
let k: LangPackKey, _args: any[] = [];
if(daysToStart < 1 && date.getDate() === today.getDate()) {
k = 'TodayAtFormattedWithToday';
} else if(daysToStart < 2 && date.getDate() === tomorrowDate.getDate()) {
k = 'Time.TomorrowAt';
} else {
k = 'formatDateAtTime';
_args.push(new I18n.IntlDateElement({
date,
options: {
day: '2-digit',
month: '2-digit',
year: '2-digit'
}
}).element);
}
_args.push(formatTime(date));
const t = i18n(k, _args);
3 years ago
args.push(t);
3 years ago
break;
}
case 'messageActionChatCreate': {
const myId = appUsersManager.getSelf().id;
if(message.fromId === myId) {
_ += 'You';
} else {
args = [getNameDivHTML(message.fromId, plain)];
}
break;
}
case 'messageActionPinMessage':
case 'messageActionContactSignUp':
case 'messageActionChatReturn':
case 'messageActionChatLeave':
case 'messageActionChatJoined':
case 'messageActionChatEditPhoto':
case 'messageActionChatDeletePhoto':
case 'messageActionChatEditVideo':
case 'messageActionChatJoinedByLink':
case 'messageActionChannelEditVideo':
case 'messageActionChannelDeletePhoto': {
args = [getNameDivHTML(message.fromId, plain)];
3 years ago
break;
}
case 'messageActionChannelEditTitle':
case 'messageActionChatEditTitle': {
args = [];
if(action._ === 'messageActionChatEditTitle') {
args.push(getNameDivHTML(message.fromId, plain));
}
3 years ago
args.push(plain ? action.title : htmlToSpan(RichTextProcessor.wrapEmojiText(action.title)));
break;
}
3 years ago
case 'messageActionChatDeleteUser':
case 'messageActionChatAddUsers':
case 'messageActionChatAddUser': {
const users: number[] = (action as MessageAction.messageActionChatAddUser).users
|| [(action as MessageAction.messageActionChatDeleteUser).user_id];
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));
}
3 years ago
break;
}
case 'messageActionBotAllowed': {
const anchorHTML = RichTextProcessor.wrapRichText(action.domain, {
entities: [{
_: 'messageEntityUrl',
length: action.domain.length,
offset: 0
}]
});
3 years ago
const node = htmlToSpan(anchorHTML);
args = [node];
3 years ago
break;
}
default:
langPackKey = (langPack[_] || `[${action._}]`) as any;
3 years ago
break;
}
if(!langPackKey) {
langPackKey = langPack[_];
if(langPackKey === undefined) {
langPackKey = '[' + _ + ']' as any;
3 years ago
}
}
if(plain) {
return I18n.format(langPackKey, true, args);
3 years ago
} else {
return _i18n(element, langPackKey, args);
}
//str = !langPackKey || langPackKey[0].toUpperCase() === langPackKey[0] ? langPackKey : getNameDivHTML(message.fromId) + langPackKey + (suffix ? ' ' : '');
}
}
3 years ago
public reportMessages(peerId: number, mids: number[], reason: ReportReason['_'], message?: string) {
return apiManager.invokeApiSingle('messages.report', {
peer: appPeersManager.getInputPeerById(peerId),
id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid)),
reason: {
_: reason
},
message
});
}
3 years ago
public startBot(botId: number, chatId: number, startParam: string) {
const peerId = chatId ? -chatId : botId;
if(startParam) {
const randomId = randomLong();
return apiManager.invokeApi('messages.startBot', {
bot: appUsersManager.getUserInput(botId),
peer: appPeersManager.getInputPeerById(peerId),
random_id: randomId,
start_param: startParam
}).then((updates) => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
const str = '/start';
if(chatId) {
let promise: Promise<void>;
if(appChatsManager.isChannel(chatId)) {
promise = appChatsManager.inviteToChannel(chatId, [botId]);
} else {
promise = appChatsManager.addChatUser(chatId, botId, 0);
}
return promise.catch((error) => {
if(error && error.type == 'USER_ALREADY_PARTICIPANT') {
error.handled = true;
return;
}
throw error;
}).then(() => {
const bot = appUsersManager.getUser(botId);
return this.sendText(peerId, str + '@' + bot.username);
});
}
return this.sendText(peerId, str);
}
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) {
return this.filtersStorage.toggleDialogPin(peerId, filterId);
}
const dialog = this.getDialogOnly(peerId);
if(!dialog) return Promise.reject();
const pinned = dialog.pFlags?.pinned ? undefined : true;
if(pinned) {
const max = filterId === 1 ? rootScope.config.pinned_infolder_count_max : rootScope.config.pinned_dialogs_count_max;
if(this.dialogsStorage.getPinnedOrders(filterId).length >= max) {
return Promise.reject({type: 'PINNED_DIALOGS_TOO_MUCH'});
}
}
return apiManager.invokeApi('messages.toggleDialogPin', {
peer: appPeersManager.getInputDialogPeerById(peerId),
pinned
}).then(bool => {
if(bool) {
const pFlags: Update.updateDialogPinned['pFlags'] = pinned ? {pinned} : {};
apiUpdatesManager.saveUpdate({
_: 'updateDialogPinned',
peer: appPeersManager.getDialogPeer(peerId),
folder_id: filterId,
pFlags
});
}
});
}
public markDialogUnread(peerId: number, read?: true) {
const dialog = this.getDialogOnly(peerId);
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.onUpdateDialogUnreadMark({
_: 'updateDialogUnreadMark',
peer: appPeersManager.getDialogPeer(peerId),
pFlags
});
}
});
}
public migrateChecks(migrateFrom: number, migrateTo: number) {
4 years ago
if(!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo] &&
appChatsManager.hasChat(-migrateTo)) {
const fromChat = appChatsManager.getChat(-migrateFrom);
4 years ago
if(fromChat &&
fromChat.migrated_to &&
fromChat.migrated_to.channel_id === -migrateTo) {
4 years ago
this.migratedFromTo[migrateFrom] = migrateTo;
this.migratedToFrom[migrateTo] = migrateFrom;
//setTimeout(() => {
rootScope.dispatchEvent('dialog_migrate', {migrateFrom, migrateTo});
const dropped = this.dialogsStorage.dropDialog(migrateFrom);
if(dropped.length) {
rootScope.dispatchEvent('dialog_drop', {peerId: migrateFrom, dialog: dropped[0]});
4 years ago
}
//}, 100);
4 years ago
}
}
}
private canMessageBeEdited(message: any, kind: 'text' | 'poll') {
if(message.pFlags.is_outgoing) {
return false;
}
const goodMedias = [
4 years ago
'messageMediaPhoto',
'messageMediaDocument',
'messageMediaWebPage'
];
if(kind === 'poll') {
goodMedias.push('messageMediaPoll');
}
if(message._ !== 'message' ||
4 years ago
message.deleted ||
message.fwd_from ||
message.via_bot_id ||
message.media && goodMedias.indexOf(message.media._) === -1 ||
message.fromId && appUsersManager.isBot(message.fromId)) {
4 years ago
return false;
}
4 years ago
if(message.media &&
message.media._ === 'messageMediaDocument' &&
(message.media.document.sticker || message.media.document.type === 'round')) {
4 years ago
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) - rootScope.config.edit_time_limit) &&
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')
) && !message.pFlags.is_outgoing;
}
public getReplyKeyboard(peerId: number) {
3 years ago
return this.getHistoryStorage(peerId).replyMarkup;
}
public mergeReplyKeyboard(historyStorage: HistoryStorage, message: Message.messageService | Message.message) {
// this.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup)
let messageReplyMarkup = (message as Message.message).reply_markup;
if(!messageReplyMarkup &&
!message.pFlags?.out &&
!(message as Message.messageService).action) {
4 years ago
return false;
}
if(messageReplyMarkup?._ === 'replyInlineMarkup') {
4 years ago
return false;
}
3 years ago
const lastReplyMarkup = historyStorage.replyMarkup;
4 years ago
if(messageReplyMarkup) {
if(lastReplyMarkup && lastReplyMarkup.mid >= message.mid) {
return false;
}
if(messageReplyMarkup.pFlags.selective) {
4 years ago
return false;
}
if(historyStorage.maxOutId &&
message.mid < historyStorage.maxOutId &&
(messageReplyMarkup as ReplyMarkup.replyKeyboardMarkup | ReplyMarkup.replyKeyboardForceReply).pFlags.single_use) {
(messageReplyMarkup as ReplyMarkup.replyKeyboardMarkup | ReplyMarkup.replyKeyboardForceReply).pFlags.hidden = true;
4 years ago
}
messageReplyMarkup.mid = message.mid;
/* messageReplyMarkup = Object.assign({
4 years ago
mid: message.mid
}, messageReplyMarkup); */
if(messageReplyMarkup._ !== 'replyKeyboardHide') {
messageReplyMarkup.fromId = appPeersManager.getPeerId(message.from_id);
4 years ago
}
3 years ago
historyStorage.replyMarkup = messageReplyMarkup;
// this.log('set', historyStorage.reply_markup)
4 years ago
return true;
}
if(message.pFlags.out) {
if(lastReplyMarkup) {
assumeType<ReplyMarkup.replyKeyboardMarkup>(lastReplyMarkup);
4 years ago
if(lastReplyMarkup.pFlags.single_use &&
!lastReplyMarkup.pFlags.hidden &&
(message.mid > lastReplyMarkup.mid || message.pFlags.is_outgoing) &&
(message as Message.message).message) {
4 years ago
lastReplyMarkup.pFlags.hidden = true;
// this.log('set', historyStorage.reply_markup)
4 years ago
return true;
}
} else if(!historyStorage.maxOutId ||
message.mid > historyStorage.maxOutId) {
historyStorage.maxOutId = message.mid;
4 years ago
}
}
assumeType<Message.messageService>(message);
if(message.action?._ === 'messageActionChatDeleteUser' &&
4 years ago
(lastReplyMarkup
? message.action.user_id === (lastReplyMarkup as ReplyMarkup.replyKeyboardMarkup).fromId
4 years ago
: appUsersManager.isBot(message.action.user_id)
)
) {
3 years ago
historyStorage.replyMarkup = {
4 years ago
_: 'replyKeyboardHide',
mid: message.mid,
pFlags: {}
};
// this.log('set', historyStorage.reply_markup)
4 years ago
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[], canCache = true) {
const func = (canCache ? apiManager.invokeApiCacheable : apiManager.invokeApi).bind(apiManager);
return func('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[] = [];
4 years ago
//this.log('search', maxId);
if(backLimit) {
limit += backLimit;
4 years ago
}
//const beta = inputFilter._ === 'inputMessagesFilterPinned' && !backLimit;
const beta = false;
4 years ago
let storage: {
count?: number;
3 years ago
history: SlicedArray;
};
// * костыль для limit 1, если нужно и получить сообщение, и узнать количество сообщений
if(peerId && !backLimit && !maxId && !query && limit !== 1 && !threadId/* && inputFilter._ !== 'inputMessagesFilterPinned' */) {
storage = beta ?
3 years ago
this.getSearchStorage(peerId, inputFilter._) as any :
this.getHistoryStorage(peerId);
let filtering = true;
4 years ago
3 years ago
const history = /* maxId ? storage.history.slice(storage.history.indexOf(maxId) + 1) : */storage.history;
if(storage !== undefined && history.length) {
const neededContents: {
[messageMediaType: string]: boolean
4 years ago
} = {},
neededDocTypes: string[] = [],
excludeDocTypes: string[] = []/* ,
neededFlags: string[] = [] */;
4 years ago
switch(inputFilter._) {
case 'inputMessagesFilterPhotos':
neededContents['messageMediaPhoto'] = true;
break;
case 'inputMessagesFilterPhotoVideo':
neededContents['messageMediaPhoto'] = true;
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('video');
4 years ago
break;
case 'inputMessagesFilterVideo':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('video');
4 years ago
break;
case 'inputMessagesFilterDocument':
neededContents['messageMediaDocument'] = true;
excludeDocTypes.push('video');
4 years ago
break;
case 'inputMessagesFilterVoice':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('voice');
break;
case 'inputMessagesFilterRoundVoice':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('round', 'voice');
4 years ago
break;
case 'inputMessagesFilterRoundVideo':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('round');
4 years ago
break;
case 'inputMessagesFilterMusic':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('audio');
4 years ago
break;
case 'inputMessagesFilterUrl':
neededContents['url'] = true;
break;
case 'inputMessagesFilterChatPhotos':
neededContents['avatar'] = true;
break;
/* case 'inputMessagesFilterPinned':
neededFlags.push('pinned');
break; */
/* case 'inputMessagesFilterMyMentions':
4 years ago
neededContents['mentioned'] = true;
break; */
4 years ago
default:
filtering = false;
break;
/* return Promise.resolve({
4 years ago
count: 0,
next_rate: 0,
4 years ago
history: [] as number[]
}); */
4 years ago
}
if(filtering) {
const storage = this.getMessagesStorage(peerId);
for(let i = 0, length = history.length; i < length; i++) {
3 years ago
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;
}
3 years ago
} else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto', 'messageActionChannelEditVideo', 'messageActionChatEditVideo'].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;
}
4 years ago
}
}
}
}
}
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
});
4 years ago
}
} else if(beta && storage?.count) {
return Promise.resolve({
count: storage.count,
next_rate: 0,
offset_id_offset: 0,
history: []
});
4 years ago
}
3 years ago
const canCache = false && (['inputMessagesFilterChatPhotos', 'inputMessagesFilterPinned'] as MyInputMessagesFilter[]).includes(inputFilter._);
3 years ago
const method = (canCache ? apiManager.invokeApiCacheable : apiManager.invokeApi).bind(apiManager);
let apiPromise: Promise<any>;
if(peerId && !nextRate && folderId === undefined/* || !query */) {
3 years ago
apiPromise = method('messages.search', {
peer: appPeersManager.getInputPeerById(peerId),
4 years ago
q: query || '',
filter: inputFilter as any as MessagesFilter,
min_date: minDate,
max_date: maxDate,
limit,
3 years ago
offset_id: appMessagesIdsManager.getServerMessageId(maxId) || 0,
add_offset: backLimit ? -backLimit : 0,
4 years ago
max_id: 0,
min_id: 0,
hash: 0,
3 years ago
top_msg_id: appMessagesIdsManager.getServerMessageId(threadId) || 0
4 years ago
}, {
//timeout: APITIMEOUT,
4 years ago
noErrorBox: true
});
} else {
//var offsetDate = 0;
let offsetPeerId = 0;
let offsetId = 0;
let offsetMessage = maxId && this.getMessageByPeer(peerId, maxId);
4 years ago
if(offsetMessage && offsetMessage.date) {
//offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset;
offsetId = offsetMessage.id;
offsetPeerId = this.getMessagePeer(offsetMessage);
4 years ago
}
3 years ago
apiPromise = method('messages.searchGlobal', {
4 years ago
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
4 years ago
}, {
//timeout: APITIMEOUT,
4 years ago
noErrorBox: true
});
}
return apiPromise.then((searchResult: any) => {
appUsersManager.saveApiUsers(searchResult.users);
appChatsManager.saveApiChats(searchResult.chats);
this.saveMessages(searchResult.messages);
3 years ago
/* 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;
3 years ago
} */
if(DEBUG) {
this.log('getSearch result:', inputFilter, searchResult);
}
const foundCount: number = searchResult.count || (foundMsgs.length + searchResult.messages.length);
4 years ago
searchResult.messages.forEach((message: any) => {
const peerId = this.getMessagePeer(message);
if(peerId < 0) {
const chat = appChatsManager.getChat(-peerId);
4 years ago
if(chat.migrated_to) {
this.migrateChecks(peerId, -chat.migrated_to.channel_id);
4 years ago
}
}
foundMsgs.push(message);
4 years ago
});
return {
count: foundCount,
offset_id_offset: searchResult.offset_id_offset || 0,
next_rate: searchResult.next_rate,
4 years ago
history: foundMsgs
};
4 years ago
});
}
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;
3 years ago
const maxMessageId = appMessagesIdsManager.getServerMessageId(Math.max(...this.getMidsByMessage(message)));
const serviceStartMessage: Message.messageService = {
_: 'messageService',
pFlags: {
is_single: true
},
3 years ago
id: appMessagesIdsManager.generateMessageId(maxMessageId, true),
date: message.date,
3 years ago
from_id: {_: 'peerUser', user_id: 0}/* message.from_id */,
peer_id: message.peer_id,
action: {
_: 'messageActionDiscussionStarted'
},
reply_to: this.generateReplyHeader(message.id)
};
this.saveMessages([serviceStartMessage], {isOutgoing: true});
this.threadsServiceMessagesIdsStorage[threadKey] = serviceStartMessage.mid;
}
public getDiscussionMessage(peerId: number, mid: number) {
return apiManager.invokeApiSingle('messages.getDiscussionMessage', {
peer: appPeersManager.getInputPeerById(peerId),
3 years ago
msg_id: appMessagesIdsManager.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);
3 years ago
result.max_id = historyStorage.maxId = appMessagesIdsManager.generateMessageId(result.max_id) || 0;
result.read_inbox_max_id = historyStorage.readMaxId = appMessagesIdsManager.generateMessageId(result.read_inbox_max_id ?? message.mid);
result.read_outbox_max_id = historyStorage.readOutboxMaxId = appMessagesIdsManager.generateMessageId(result.read_outbox_max_id) || 0;
this.threadsToReplies[threadKey] = peerId + '_' + mid;
return message;
});
}
private handleNewMessage(peerId: number, mid: number) {
if(this.newMessagesToHandle[peerId] === undefined) {
this.newMessagesToHandle[peerId] = new Set();
}
this.newMessagesToHandle[peerId].add(mid);
if(!this.newMessagesHandleTimeout) {
this.newMessagesHandleTimeout = window.setTimeout(this.handleNewMessages, 0);
}
}
handleNewMessages = () => {
clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandleTimeout = 0;
4 years ago
rootScope.dispatchEvent('history_multiappend', this.newMessagesToHandle);
4 years ago
this.newMessagesToHandle = {};
};
4 years ago
handleNewDialogs = () => {
let newMaxSeenId = 0;
const obj = this.newDialogsToHandle;
for(const peerId in obj) {
const dialog = obj[peerId];
if(!dialog) {
this.reloadConversation(+peerId);
delete obj[peerId];
4 years ago
} else {
this.dialogsStorage.pushDialog(dialog);
if(!appPeersManager.isChannel(+peerId)) {
newMaxSeenId = Math.max(newMaxSeenId, dialog.top_message || 0);
4 years ago
}
}
}
//this.log('after order:', this.dialogsStorage[0].map(d => d.peerId));
4 years ago
if(newMaxSeenId !== 0) {
this.incrementMaxSeenId(newMaxSeenId);
4 years ago
}
rootScope.dispatchEvent('dialogs_multiupdate', obj);
4 years ago
this.newDialogsToHandle = {};
};
4 years ago
public scheduleHandleNewDialogs(peerId?: number, dialog?: Dialog) {
if(peerId !== undefined) {
this.newDialogsToHandle[peerId] = dialog;
}
if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise;
return this.newDialogsHandlePromise = new Promise((resolve) => {
setTimeout(() => {
this.newDialogsHandlePromise = undefined;
this.handleNewDialogs();
}, 0);
});
}
3 years ago
public deleteMessages(peerId: number, mids: number[], revoke?: boolean) {
let promise: Promise<any>;
3 years ago
const localMessageIds = mids.map(mid => appMessagesIdsManager.getServerMessageId(mid));
if(peerId < 0 && appPeersManager.isChannel(peerId)) {
const channelId = -peerId;
const channel: Chat.channel = appChatsManager.getChat(channelId);
if(!channel.pFlags.creator && !channel.admin_rights?.pFlags?.delete_messages) {
mids = mids.filter((mid) => {
const message = this.getMessageByPeer(peerId, mid);
return !!message.pFlags.out;
});
if(!mids.length) {
return;
}
}
promise = apiManager.invokeApi('channels.deleteMessages', {
channel: appChatsManager.getChannelInput(channelId),
3 years ago
id: localMessageIds
}).then((affectedMessages) => {
apiUpdatesManager.processLocalUpdate({
_: 'updateDeleteChannelMessages',
channel_id: channelId,
messages: mids,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
});
});
} else {
promise = apiManager.invokeApi('messages.deleteMessages', {
revoke,
3 years ago
id: localMessageIds
}).then((affectedMessages) => {
apiUpdatesManager.processLocalUpdate({
_: 'updateDeleteMessages',
messages: mids,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
});
});
}
return promise;
}
// TODO: cancel notification by peer when this function is being called
public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) {
3 years ago
if(DO_NOT_READ_HISTORY) {
return Promise.resolve();
}
4 years ago
// 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 historyStorage = this.getHistoryStorage(peerId, threadId);
4 years ago
3 years ago
if(historyStorage.triedToReadMaxId >= maxId) {
return Promise.resolve();
}
let apiPromise: Promise<any>;
if(threadId) {
if(!historyStorage.readPromise) {
apiPromise = apiManager.invokeApi('messages.readDiscussion', {
peer: appPeersManager.getInputPeerById(peerId),
3 years ago
msg_id: appMessagesIdsManager.getServerMessageId(threadId),
read_max_id: appMessagesIdsManager.getServerMessageId(maxId)
});
}
apiUpdatesManager.processLocalUpdate({
_: 'updateReadChannelDiscussionInbox',
channel_id: -peerId,
top_msg_id: threadId,
read_max_id: maxId
});
3 years ago
} else if(appPeersManager.isChannel(peerId)) {
if(!historyStorage.readPromise) {
apiPromise = apiManager.invokeApi('channels.readHistory', {
channel: appChatsManager.getChannelInput(-peerId),
3 years ago
max_id: appMessagesIdsManager.getServerMessageId(maxId)
});
}
apiUpdatesManager.processLocalUpdate({
_: 'updateReadChannelInbox',
max_id: maxId,
channel_id: -peerId,
still_unread_count: undefined,
pts: undefined
4 years ago
});
} else {
if(!historyStorage.readPromise) {
apiPromise = apiManager.invokeApi('messages.readHistory', {
peer: appPeersManager.getInputPeerById(peerId),
3 years ago
max_id: appMessagesIdsManager.getServerMessageId(maxId)
}).then((affectedMessages) => {
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updatePts',
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
});
4 years ago
});
}
apiUpdatesManager.processLocalUpdate({
_: 'updateReadHistoryInbox',
max_id: maxId,
peer: appPeersManager.getOutputPeer(peerId),
still_unread_count: undefined,
pts: undefined,
pts_count: undefined
});
}
appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId));
if(historyStorage.readPromise) {
return historyStorage.readPromise;
}
3 years ago
historyStorage.triedToReadMaxId = maxId;
apiPromise.finally(() => {
4 years ago
delete historyStorage.readPromise;
this.log('readHistory: promise finally', maxId, historyStorage.readMaxId);
if(historyStorage.readMaxId > maxId) {
this.readHistory(peerId, historyStorage.readMaxId, threadId, true);
}
});
4 years ago
return historyStorage.readPromise = apiPromise;
4 years ago
}
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
}
}
3 years ago
public modifyCachedMentions(peerId: number, mid: number, add: boolean) {
const slicedArray = this.unreadMentions[peerId];
if(!slicedArray) return;
if(add) {
if(slicedArray.first.isEnd(SliceEnd.Top)) {
slicedArray.insertSlice([mid]);
}
} else {
slicedArray.delete(mid);
}
}
public goToNextMention(peerId: number) {
/* this.getUnreadMentions(peerId, 1, 2, 0).then(messages => {
console.log(messages);
}); */
const promise = this.goToNextMentionPromises[peerId];
if(promise) {
return promise;
}
const slicedArray = this.unreadMentions[peerId] ?? (this.unreadMentions[peerId] = new SlicedArray());
const length = slicedArray.length;
const isTopEnd = slicedArray.first.isEnd(SliceEnd.Top);
if(!length && isTopEnd) {
return Promise.resolve();
}
let loadNextPromise = Promise.resolve();
if(!isTopEnd && length < 25) {
loadNextPromise = this.loadNextMentions(peerId);
}
return this.goToNextMentionPromises[peerId] = loadNextPromise.then(() => {
const last = slicedArray.last;
const mid = last && last[last.length - 1];
if(mid) {
slicedArray.delete(mid);
rootScope.dispatchEvent('history_focus', {peerId, mid});
}
}).finally(() => {
delete this.goToNextMentionPromises[peerId];
});
}
public loadNextMentions(peerId: number) {
const slicedArray = this.unreadMentions[peerId];
const maxId = slicedArray.first[0] || 1;
const backLimit = 50;
const add_offset = -backLimit;
const limit = backLimit;
return this.getUnreadMentions(peerId, maxId, add_offset, limit).then(messages => {
this.mergeHistoryResult(slicedArray, messages, maxId === 1 ? 0 : maxId, limit, add_offset);
});
}
public getUnreadMentions(peerId: number, offsetId: number, add_offset: number, limit: number, maxId = 0, minId = 0) {
return apiManager.invokeApiSingle('messages.getUnreadMentions', {
peer: appPeersManager.getInputPeerById(peerId),
offset_id: appMessagesIdsManager.getServerMessageId(offsetId),
add_offset,
limit,
max_id: appMessagesIdsManager.getServerMessageId(maxId),
min_id: appMessagesIdsManager.getServerMessageId(minId)
}).then(messagesMessages => {
assumeType<Exclude<MessagesMessages, MessagesMessages.messagesMessagesNotModified>>(messagesMessages);
appUsersManager.saveApiUsers(messagesMessages.users);
appChatsManager.saveApiChats(messagesMessages.chats);
this.saveMessages(messagesMessages.messages);
return messagesMessages;
});
}
public readMessages(peerId: number, msgIds: number[]) {
3 years ago
if(DO_NOT_READ_HISTORY) {
return Promise.resolve();
}
if(!msgIds.length) {
return Promise.resolve();
}
3 years ago
msgIds = msgIds.map(mid => appMessagesIdsManager.getServerMessageId(mid));
3 years ago
let promise: Promise<any>, update: Update.updateChannelReadMessagesContents | Update.updateReadMessagesContents;
if(peerId < 0 && appPeersManager.isChannel(peerId)) {
const channelId = -peerId;
3 years ago
update = {
_: 'updateChannelReadMessagesContents',
channel_id: channelId,
messages: msgIds
};
promise = apiManager.invokeApi('channels.readMessageContents', {
channel: appChatsManager.getChannelInput(channelId),
id: msgIds
});
} else {
3 years ago
update = {
_: 'updateReadMessagesContents',
messages: msgIds,
pts: undefined,
pts_count: undefined
};
promise = apiManager.invokeApi('messages.readMessageContents', {
id: msgIds
}).then((affectedMessages) => {
3 years ago
(update as Update.updateReadMessagesContents).pts = affectedMessages.pts;
(update as Update.updateReadMessagesContents).pts_count = affectedMessages.pts_count;
apiUpdatesManager.processLocalUpdate(update);
});
}
3 years ago
apiUpdatesManager.processLocalUpdate(update);
return promise;
4 years ago
}
public getHistoryStorage(peerId: number, threadId?: number) {
if(threadId) {
3 years ago
//threadId = this.getLocalMessageId(threadId);
if(!this.threadsStorage[peerId]) this.threadsStorage[peerId] = {};
3 years ago
return this.threadsStorage[peerId][threadId] ?? (this.threadsStorage[peerId][threadId] = {count: null, history: new SlicedArray()});
}
3 years ago
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;
if(rootScope.peerId === peerId && !rootScope.idle.isIDLE) {
continue;
}
const notifyPeerToHandle = this.notificationsToHandle[peerId];
Promise.all([
appNotificationsManager.getNotifyPeerTypeSettings(),
appNotificationsManager.getNotifySettings(appPeersManager.getInputNotifyPeerById(peerId, true))
]).then(([_, peerTypeNotifySettings]) => {
const topMessage = notifyPeerToHandle.topMessage;
if(appNotificationsManager.isPeerLocalMuted(peerId, true) || !topMessage.pFlags.unread) {
return;
}
//setTimeout(() => {
if(topMessage.pFlags.unread) {
this.notifyAboutMessage(topMessage, {
fwdCount: notifyPeerToHandle.fwdCount,
peerTypeNotifySettings
});
}
//}, timeout);
});
}
this.notificationsToHandle = {};
};
private onUpdateMessageId = (update: Update.updateMessageID) => {
const randomId = update.random_id;
const pendingData = this.pendingByRandomId[randomId];
//this.log('AMM updateMessageID:', update, pendingData);
if(pendingData) {
const {peerId, tempId, threadId, storage} = pendingData;
//const mid = update.id;
3 years ago
const mid = appMessagesIdsManager.generateMessageId(update.id);
const message = this.getMessageFromStorage(storage, mid);
if(!message.deleted) {
[this.getHistoryStorage(peerId), threadId ? this.getHistoryStorage(peerId, threadId) : undefined]
.filter(Boolean)
.forEach(storage => {
storage.history.delete(tempId);
});
this.finalizePendingMessageCallbacks(storage, tempId, mid);
} else {
this.pendingByMessageId[mid] = randomId;
}
}
};
4 years ago
private onUpdateNewMessage = (update: Update.updateNewDiscussionMessage | Update.updateNewMessage | Update.updateNewChannelMessage) => {
const message = update.message as MyMessage;
const peerId = this.getMessagePeer(message);
const storage = this.getMessagesStorage(peerId);
const dialog = this.getDialogOnly(peerId);
// * local update
const isLocalThreadUpdate = update._ === 'updateNewDiscussionMessage';
4 years ago
// * temporary save the message for info (peerId, reply mids...)
this.saveMessages([message], {storage: {}});
4 years ago
const threadKey = this.getThreadKey(message);
const threadId = threadKey ? +threadKey.split('_')[1] : undefined;
if(threadId && !isLocalThreadUpdate && this.threadsStorage[peerId] && this.threadsStorage[peerId][threadId]) {
const update = {
_: 'updateNewDiscussionMessage',
message
} as Update.updateNewDiscussionMessage;
this.onUpdateNewMessage(update);
}
if(!dialog && !isLocalThreadUpdate) {
let good = true;
if(peerId < 0) {
3 years ago
good = appChatsManager.isInChat(-peerId);
}
if(good) {
const set = this.newUpdatesAfterReloadToHandle[peerId] ?? (this.newUpdatesAfterReloadToHandle[peerId] = new Set());
if(set.has(update)) {
this.log.error('here we go again', peerId);
return;
}
this.scheduleHandleNewDialogs(peerId);
set.add(update);
}
return;
}
4 years ago
/* if(update._ === 'updateNewChannelMessage') {
const chat = appChatsManager.getChat(-peerId);
if(chat.pFlags && (chat.pFlags.left || chat.pFlags.kicked)) {
return;
}
} */
4 years ago
this.saveMessages([message], {storage});
// this.log.warn(dT(), 'message unread', message.mid, message.pFlags.unread)
4 years ago
/* if((message as Message.message).grouped_id) {
this.log('updateNewMessage', message);
} */
3 years ago
const pendingMessage = this.checkPendingMessage(message);
const historyStorage = this.getHistoryStorage(peerId, isLocalThreadUpdate ? threadId : undefined);
if(!isLocalThreadUpdate) {
this.updateMessageRepliesIfNeeded(message);
}
4 years ago
if(historyStorage.history.findSlice(message.mid)) {
return false;
}
3 years ago
// * catch situation with disconnect. if message's id is lower than we already have (in bottom end slice), will sort it
const firstSlice = historyStorage.history.first;
if(firstSlice.isEnd(SliceEnd.Bottom)) {
let i = 0;
for(const length = firstSlice.length; i < length; ++i) {
if(message.mid > firstSlice[i]) {
break;
}
}
firstSlice.splice(i, 0, message.mid);
} else {
historyStorage.history.unshift(message.mid);
}
if(historyStorage.count !== null) {
historyStorage.count++;
}
4 years ago
if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.dispatchEvent('history_reply_markup', {peerId});
}
const fromId = message.fromId;
if(fromId > 0 && !message.pFlags.out && message.from_id) {
appUsersManager.forceUserOnline(fromId, message.date);
const action: SendMessageAction = {
_: 'sendMessageCancelAction'
};
let update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping;
if(peerId > 0) {
update = {
_: 'updateUserTyping',
action,
user_id: fromId
};
} else if(appPeersManager.isChannel(peerId)) {
update = {
_: 'updateChannelUserTyping',
action,
channel_id: -peerId,
from_id: appPeersManager.getOutputPeer(fromId),
3 years ago
top_msg_id: threadId ? appMessagesIdsManager.getServerMessageId(threadId) : undefined
};
} else {
update = {
_: 'updateChatUserTyping',
action,
chat_id: -peerId,
from_id: appPeersManager.getOutputPeer(fromId)
};
}
apiUpdatesManager.processLocalUpdate(update);
}
if(!pendingMessage) {
this.handleNewMessage(peerId, message.mid);
}
if(isLocalThreadUpdate) {
return;
}
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
if(dialog) {
if(inboxUnread) {
3 years ago
++dialog.unread_count;
if(message.pFlags.mentioned) {
++dialog.unread_mentions_count;
3 years ago
this.modifyCachedMentions(peerId, message.mid, true);
3 years ago
}
}
3 years ago
this.setDialogTopMessage(message, dialog);
}
if(inboxUnread/* && ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE) */) {
const notifyPeer = peerId;
let notifyPeerToHandle = this.notificationsToHandle[notifyPeer];
if(notifyPeerToHandle === undefined) {
notifyPeerToHandle = this.notificationsToHandle[notifyPeer] = {
fwdCount: 0,
fromId: 0
};
}
if(notifyPeerToHandle.fromId !== fromId) {
notifyPeerToHandle.fromId = 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);
}
}
};
private onUpdateDialogUnreadMark = (update: Update.updateDialogUnreadMark) => {
//this.log('updateDialogUnreadMark', update);
const peerId = appPeersManager.getPeerId((update.peer as DialogPeer.dialogPeer).peer);
const dialog = this.getDialogOnly(peerId);
if(!dialog) {
this.scheduleHandleNewDialogs(peerId);
} else {
if(!update.pFlags.unread) {
delete dialog.pFlags.unread_mark;
} else {
dialog.pFlags.unread_mark = true;
4 years ago
}
rootScope.dispatchEvent('dialogs_multiupdate', {[peerId]: dialog});
this.dialogsStorage.setDialogToState(dialog);
}
};
private onUpdateEditMessage = (update: Update.updateEditMessage | Update.updateEditChannelMessage) => {
const message = update.message as MyMessage;
const peerId = this.getMessagePeer(message);
3 years ago
const mid = appMessagesIdsManager.generateMessageId(message.id);
const storage = this.getMessagesStorage(peerId);
if(storage[mid] === undefined) {
return;
}
// console.trace(dT(), 'edit message', message)
const oldMessage = this.getMessageFromStorage(storage, mid);
this.saveMessages([message], {storage});
const newMessage = this.getMessageFromStorage(storage, mid);
4 years ago
this.handleEditedMessage(oldMessage, newMessage);
4 years ago
const dialog = this.getDialogOnly(peerId);
const isTopMessage = dialog && dialog.top_message === mid;
if((message as Message.message).clear_history) {
if(isTopMessage) {
rootScope.dispatchEvent('dialog_flush', {peerId});
4 years ago
}
} else {
rootScope.dispatchEvent('message_edit', {
storage,
peerId,
mid
});
4 years ago
if(isTopMessage || (message as Message.message).grouped_id) {
const updatedDialogs: {[peerId: number]: Dialog} = {};
updatedDialogs[peerId] = dialog;
rootScope.dispatchEvent('dialogs_multiupdate', updatedDialogs);
this.dialogsStorage.setDialogToState(dialog);
}
}
};
private onUpdateReadHistory = (update: Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox
| Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox
| Update.updateReadChannelInbox | Update.updateReadChannelOutbox) => {
const channelId = (update as Update.updateReadChannelInbox).channel_id;
3 years ago
const maxId = appMessagesIdsManager.generateMessageId((update as Update.updateReadChannelInbox).max_id || (update as Update.updateReadChannelDiscussionInbox).read_max_id);
const threadId = appMessagesIdsManager.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;
4 years ago
const storage = this.getMessagesStorage(peerId);
const history = getObjectKeysAndSort(storage, 'desc');
const foundDialog = this.getDialogOnly(peerId);
const stillUnreadCount = (update as Update.updateReadChannelInbox).still_unread_count;
let newUnreadCount = 0;
3 years ago
let newUnreadMentionsCount = 0;
let foundAffected = false;
4 years ago
//this.log.warn(dT(), 'read', peerId, isOut ? 'out' : 'in', maxId)
const historyStorage = this.getHistoryStorage(peerId, threadId);
4 years ago
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');
}
}
4 years ago
for(let i = 0, length = history.length; i < length; i++) {
const messageId = history[i];
if(messageId > maxId) {
continue;
}
3 years ago
const message: MyMessage = storage[messageId];
if(message.pFlags.out !== isOut) {
continue;
}
if(!message.pFlags.unread) {
break;
}
if(threadId) {
3 years ago
const replyTo = message.reply_to;
if(!replyTo || (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) !== threadId) {
continue;
4 years ago
}
}
// this.log.warn('read', messageId, message.pFlags.unread, message)
if(message.pFlags.unread) {
delete message.pFlags.unread;
if(!foundAffected) {
foundAffected = true;
}
4 years ago
3 years ago
if(!message.pFlags.out && !threadId && foundDialog) {
if(stillUnreadCount === undefined) {
newUnreadCount = --foundDialog.unread_count;
}
if(message.pFlags.mentioned) {
newUnreadMentionsCount = --foundDialog.unread_mentions_count;
3 years ago
this.modifyCachedMentions(peerId, message.mid, false);
3 years ago
}
4 years ago
}
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;
4 years ago
if(!isOut) {
3 years ago
if(stillUnreadCount !== undefined) {
foundDialog.unread_count = stillUnreadCount;
} else if(newUnreadCount < 0 || !this.getReadMaxIdIfUnread(peerId)) {
foundDialog.unread_count = 0;
} else if(newUnreadCount && foundDialog.top_message > maxId) {
foundDialog.unread_count = newUnreadCount;
4 years ago
}
3 years ago
if(newUnreadMentionsCount < 0) {
foundDialog.unread_mentions_count = 0;
}
4 years ago
}
rootScope.dispatchEvent('dialog_unread', {peerId});
this.dialogsStorage.setDialogToState(foundDialog);
}
if(foundAffected) {
rootScope.dispatchEvent('messages_read');
}
4 years ago
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.dispatchEvent('replies_updated', this.getMessageByPeer(peerId, mid));
4 years ago
}
}
}
};
private onUpdateReadMessagesContents = (update: Update.updateChannelReadMessagesContents | Update.updateReadMessagesContents) => {
const channelId = (update as Update.updateChannelReadMessagesContents).channel_id;
3 years ago
const mids = (update as Update.updateReadMessagesContents).messages.map(id => appMessagesIdsManager.generateMessageId(id));
const peerId = channelId ? -channelId : this.getMessageById(mids[0]).peerId;
3 years ago
for(let i = 0, length = mids.length; i < length; ++i) {
const mid = mids[i];
3 years ago
const message: MyMessage = this.getMessageByPeer(peerId, mid);
3 years ago
if(!message.deleted && message.pFlags.media_unread) {
delete message.pFlags.media_unread;
this.setDialogToStateIfMessageIsTop(message);
3 years ago
if(!message.pFlags.out && message.pFlags.mentioned) {
this.modifyCachedMentions(peerId, mid, false);
}
}
}
rootScope.dispatchEvent('messages_media_read', {peerId, mids});
};
private onUpdateChannelAvailableMessages = (update: Update.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;
this.onUpdateDeleteMessages(update as any as Update.updateDeleteChannelMessages);
};
private onUpdateDeleteMessages = (update: Update.updateDeleteMessages | Update.updateDeleteChannelMessages) => {
const channelId: number = (update as Update.updateDeleteChannelMessages).channel_id;
//const messages = (update as any as Update.updateDeleteChannelMessages).messages;
3 years ago
const messages = (update as any as Update.updateDeleteChannelMessages).messages.map(id => appMessagesIdsManager.generateMessageId(id));
const peerId: number = channelId ? -channelId : this.getMessageById(messages[0]).peerId;
if(!peerId) {
return;
}
apiManager.clearCache('messages.getSearchCounters', (params) => {
return appPeersManager.getPeerId(params.peer) === peerId;
});
4 years ago
const threadKeys: Set<string> = new Set();
for(const mid of messages) {
const message = this.getMessageByPeer(peerId, mid);
const threadKey = this.getThreadKey(message);
if(threadKey && this.threadsStorage[peerId] && this.threadsStorage[peerId][+threadKey.split('_')[1]]) {
threadKeys.add(threadKey);
}
}
const historyUpdated = this.handleDeletedMessages(peerId, this.getMessagesStorage(peerId), messages);
4 years ago
const threadsStorages = Array.from(threadKeys).map(threadKey => {
const splitted = threadKey.split('_');
return this.getHistoryStorage(+splitted[0], +splitted[1]);
});
4 years ago
[this.getHistoryStorage(peerId)].concat(threadsStorages).forEach(historyStorage => {
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;
}
4 years ago
}
});
4 years ago
rootScope.dispatchEvent('history_delete', {peerId, msgs: historyUpdated.msgs});
4 years ago
const foundDialog = this.getDialogOnly(peerId);
if(foundDialog) {
3 years ago
if(historyUpdated.unreadMentions) {
3 years ago
foundDialog.unread_mentions_count = Math.max(0, foundDialog.unread_mentions_count - historyUpdated.unreadMentions);
3 years ago
}
if(historyUpdated.unread) {
foundDialog.unread_count -= historyUpdated.unread;
rootScope.dispatchEvent('dialog_unread', {peerId});
}
3 years ago
if(historyUpdated.msgs.has(foundDialog.top_message)) {
this.reloadConversation(peerId);
4 years ago
}
}
};
4 years ago
private onUpdateChannel = (update: Update.updateChannel) => {
const channelId: number = update.channel_id;
const peerId = -channelId;
const channel: Chat.channel = appChatsManager.getChat(channelId);
const needDialog = appChatsManager.isInChat(channelId);
const canViewHistory = !!channel.username || !channel.pFlags.left;
const hasHistory = this.historiesStorage[peerId] !== undefined;
if(canViewHistory !== hasHistory) {
delete this.historiesStorage[peerId];
rootScope.dispatchEvent('history_forbidden', peerId);
}
const dialog = this.getDialogOnly(peerId);
if(!!dialog !== needDialog) {
if(needDialog) {
this.reloadConversation(-channelId);
} else {
if(dialog) {
this.dialogsStorage.dropDialog(peerId);
rootScope.dispatchEvent('dialog_drop', {peerId, dialog});
4 years ago
}
}
}
};
private onUpdateChannelReload = (update: Update.updateChannelReload) => {
const channelId = update.channel_id;
const peerId = -channelId;
this.dialogsStorage.dropDialog(peerId);
delete this.historiesStorage[peerId];
this.reloadConversation(-channelId).then(() => {
rootScope.dispatchEvent('history_reload', peerId);
});
};
private onUpdateChannelMessageViews = (update: Update.updateChannelMessageViews) => {
const views = update.views;
const peerId = -update.channel_id;
3 years ago
const mid = appMessagesIdsManager.generateMessageId(update.id);
const message: Message.message = this.getMessageByPeer(peerId, mid);
if(!message.deleted && message.views && message.views < views) {
message.views = views;
rootScope.dispatchEvent('message_views', {peerId, mid, views});
}
};
private onUpdateServiceNotification = (update: Update.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.onUpdateNewMessage({
_: 'updateNewMessage',
message
} as any);
}
};
private onUpdatePinnedMessages = (update: Update.updatePinnedMessages | Update.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;
} */
3 years ago
const messages = update.messages.map(id => appMessagesIdsManager.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.dispatchEvent('peer_pinned_messages', {peerId, mids: messages, pinned: werePinned});
});
});
};
private onUpdateNotifySettings = (update: Update.updateNotifySettings) => {
const {peer, notify_settings} = update;
if(peer._ === 'notifyPeer') {
const peerId = appPeersManager.getPeerId((peer as NotifyPeer.notifyPeer).peer);
const dialog = this.getDialogOnly(peerId);
if(dialog) {
dialog.notify_settings = notify_settings;
rootScope.dispatchEvent('dialog_notify_settings', dialog);
this.dialogsStorage.setDialogToState(dialog);
}
}
};
private onUpdateNewScheduledMessage = (update: Update.updateNewScheduledMessage) => {
const message = update.message as MyMessage;
const peerId = this.getMessagePeer(message);
const storage = this.scheduledMessagesStorage[peerId];
if(storage) {
3 years ago
const mid = appMessagesIdsManager.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.dispatchEvent('message_edit', {storage, peerId, mid: message.mid});
} else {
const pendingMessage = this.checkPendingMessage(message);
if(!pendingMessage) {
rootScope.dispatchEvent('scheduled_new', {peerId, mid: message.mid});
}
}
}
};
private onUpdateDeleteScheduledMessages = (update: Update.updateDeleteScheduledMessages) => {
const peerId = appPeersManager.getPeerId(update.peer);
const storage = this.scheduledMessagesStorage[peerId];
if(storage) {
3 years ago
const mids = update.messages.map(id => appMessagesIdsManager.generateMessageId(id));
this.handleDeletedMessages(peerId, storage, mids);
rootScope.dispatchEvent('scheduled_delete', {peerId, mids});
}
};
public setDialogToStateIfMessageIsTop(message: any) {
const dialog = this.getDialogOnly(message.peerId);
if(dialog && dialog.top_message === message.mid) {
this.dialogsStorage.setDialogToState(dialog);
}
}
private updateMessageRepliesIfNeeded(threadMessage: MyMessage) {
try { // * на всякий случай, скорее всего это не понадобится
const threadKey = this.getThreadKey(threadMessage);
if(threadKey) {
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);
}
}
private getThreadKey(threadMessage: MyMessage) {
let threadKey = '';
if(threadMessage.peerId < 0 && threadMessage.reply_to) {
const threadId = threadMessage.reply_to.reply_to_top_id || threadMessage.reply_to.reply_to_msg_id;
threadKey = threadMessage.peerId + '_' + threadId;
}
return threadKey;
}
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.dispatchEvent(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.dispatchEvent('history_update', {storage: pendingData.storage, peerId: message.peerId, mid: message.mid});
}
delete this.pendingByMessageId[message.mid];
}
return pendingMessage;
}
public mutePeer(peerId: number, mute?: boolean) {
const settings: InputPeerNotifySettings = {
_: 'inputPeerNotifySettings'
};
if(mute === undefined) {
mute = !appNotificationsManager.isPeerLocalMuted(peerId, false);
}
settings.mute_until = mute ? 0x7FFFFFFF : 0;
return appNotificationsManager.updateNotifySettings({
_: 'inputNotifyPeer',
peer: appPeersManager.getInputPeerById(peerId)
}, settings);
}
public canWriteToPeer(peerId: number, threadId?: number) {
if(peerId < 0) {
//const isChannel = appPeersManager.isChannel(peerId);
const hasRights = /* isChannel && */appChatsManager.hasRights(-peerId, 'send_messages', undefined, !!threadId);
return /* !isChannel || */hasRights;
} else {
return appUsersManager.canSendToUser(peerId);
}
4 years ago
}
public finalizePendingMessage(randomId: string, finalMessage: any) {
const pendingData = this.pendingByRandomId[randomId];
// this.log('pdata', randomID, pendingData)
4 years ago
if(pendingData) {
const {peerId, tempId, threadId, storage} = pendingData;
[this.getHistoryStorage(peerId), threadId ? this.getHistoryStorage(peerId, threadId) : undefined]
.filter(Boolean)
.forEach(storage => {
storage.history.delete(tempId);
});
4 years ago
// this.log('pending', randomID, historyStorage.pending)
4 years ago
const message = this.getMessageFromStorage(storage, tempId);
if(!message.deleted) {
delete message.pFlags.is_outgoing;
4 years ago
delete message.pending;
delete message.error;
delete message.random_id;
delete message.send;
rootScope.dispatchEvent('messages_pending');
4 years ago
}
delete this.pendingByRandomId[randomId];
4 years ago
this.finalizePendingMessageCallbacks(storage, tempId, finalMessage.mid);
4 years ago
return message;
}
return false;
4 years ago
}
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;
const newPhotoSize = newPhoto.sizes[newPhoto.sizes.length - 1];
const cacheContext = appDownloadManager.getCacheContext(newPhoto, newPhotoSize.type);
const oldCacheContext = appDownloadManager.getCacheContext(photo, 'full');
Object.assign(cacheContext, oldCacheContext);
const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize;
const downloadOptions = appPhotosManager.getPhotoDownloadOptions(newPhoto, photoSize);
const fileName = getFileNameByLocation(downloadOptions.location);
appDownloadManager.fakeDownload(fileName, oldCacheContext.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;
const cacheContext = appDownloadManager.getCacheContext(newDoc);
const oldCacheContext = appDownloadManager.getCacheContext(doc);
Object.assign(cacheContext, oldCacheContext);
const fileName = appDocsManager.getInputFileName(newDoc);
appDownloadManager.fakeDownload(fileName, oldCacheContext.url);
}
}
} else if(message.media.poll) {
delete appPollsManager.polls[tempId];
delete appPollsManager.results[tempId];
}
}
const tempMessage = this.getMessageFromStorage(storage, tempId);
delete storage[tempId];
3 years ago
this.handleReleasingMessage(tempMessage, storage);
rootScope.dispatchEvent('message_sent', {storage, tempId, tempMessage, mid});
4 years ago
}
public incrementMaxSeenId(maxId: number) {
if(!maxId || !(!this.maxSeenId || maxId > this.maxSeenId)) {
4 years ago
return false;
}
this.maxSeenId = maxId;
3 years ago
appStateManager.pushToState('maxSeenMsgId', maxId);
apiManager.invokeApi('messages.receivedMessages', {
3 years ago
max_id: appMessagesIdsManager.getServerMessageId(maxId)
4 years ago
});
}
public incrementMessageViews(peerId: number, mids: number[]) {
if(!mids.length) {
return;
}
return apiManager.invokeApiSingle('messages.getMessagesViews', {
peer: appPeersManager.getInputPeerById(peerId),
id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid)),
increment: true
}).then(views => {
const updates: Update[] = new Array(mids.length);
const channelId = -peerId;
for(let i = 0, length = mids.length; i < length; ++i) {
updates[i] = {
_: 'updateChannelMessageViews',
channel_id: channelId,
id: mids[i],
views: views.views[i].views
};
}
apiUpdatesManager.processUpdateMessage({
_: 'updates',
updates,
chats: views.chats,
users: views.users
});
});
}
private notifyAboutMessage(message: MyMessage, options: Partial<{
fwdCount: number,
peerTypeNotifySettings: PeerNotifySettings
}> = {}) {
const peerId = this.getMessagePeer(message);
const notification: NotifyOptions = {};
const peerString = appPeersManager.getPeerString(peerId);
let notificationMessage: string;
if(options.peerTypeNotifySettings.show_previews) {
if(message._ === 'message' && message.fwd_from && options.fwdCount) {
notificationMessage = I18n.format('Notifications.Forwarded', true, [options.fwdCount]);
} else {
notificationMessage = this.wrapMessageForReply(message, undefined, undefined, true);
}
} else {
notificationMessage = I18n.format('Notifications.New', true);
}
notification.title = appPeersManager.getPeerTitle(peerId, true);
if(peerId < 0 && message.fromId !== message.peerId) {
notification.title = appPeersManager.getPeerTitle(message.fromId, true) +
' @ ' +
notification.title;
}
notification.title = RichTextProcessor.wrapPlainText(notification.title);
notification.onclick = () => {
rootScope.dispatchEvent('history_focus', {peerId, mid: message.mid});
};
notification.message = notificationMessage;
notification.key = 'msg' + message.mid;
notification.tag = peerString;
notification.silent = true;//message.pFlags.silent || false;
const peerPhoto = appPeersManager.getPeerPhoto(peerId);
if(peerPhoto) {
3 years ago
appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small').loadPromise.then(url => {
if(message.pFlags.unread) {
notification.image = url;
appNotificationsManager.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.invokeApiSingle('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),
3 years ago
id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid))
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
public deleteScheduledMessages(peerId: number, mids: number[]) {
return apiManager.invokeApi('messages.deleteScheduledMessages', {
peer: appPeersManager.getInputPeerById(peerId),
3 years ago
id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid))
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
3 years ago
public getMessageWithReplies(message: Message.message) {
if(message.peerId !== REPLIES_PEER_ID) {
message = this.filterMessages(message, message => !!(message as Message.message).replies)[0] as any;
if(!(message && message.replies && message.replies.pFlags.comments && message.replies.channel_id !== 777)) {
return;
}
}
return message;
}
public isFetchIntervalNeeded(peerId: number) {
return peerId < 0 && !appChatsManager.isInChat(peerId);
}
public async getNewHistory(peerId: number, threadId?: number) {
if(!this.isFetchIntervalNeeded(peerId)) {
return;
}
const historyStorage = this.getHistoryStorage(peerId, threadId);
const slice = historyStorage.history.slice;
if(!slice.isEnd(SliceEnd.Bottom)) {
return;
}
delete historyStorage.maxId;
slice.unsetEnd(SliceEnd.Bottom);
// if there is no id - then request by first id because cannot request by id 0 with backLimit
let historyResult = this.getHistory(peerId, slice[0] ?? 1, 0, 50, threadId);
if(historyResult instanceof Promise) {
historyResult = await historyResult;
}
for(let i = 0, length = historyResult.history.length; i < length; ++i) {
this.handleNewMessage(peerId, historyResult.history[i]);
}
return historyStorage;
}
3 years ago
/**
* * 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;
3 years ago
/*
let offsetFound = true;
4 years ago
if(maxId) {
3 years ago
offsetFound = false;
for(; offset < historyStorage.history.length; offset++) {
3 years ago
if(maxId > historyStorage.history.slice[offset]) {
offsetFound = true;
4 years ago
break;
}
}
}
3 years ago
if(offsetFound && (
historyStorage.count !== null && historyStorage.history.length === historyStorage.count ||
historyStorage.history.length >= offset + limit
4 years ago
)) {
if(backLimit) {
backLimit = Math.min(offset, backLimit);
offset = Math.max(0, offset - backLimit);
limit += backLimit;
} else {
limit = limit;
4 years ago
}
4 years ago
3 years ago
const history = historyStorage.history.slice.slice(offset, offset + limit);
return {
4 years ago
count: historyStorage.count,
history: history,
offsetIdOffset: offset
};
4 years ago
}
3 years ago
if(offsetFound) {
4 years ago
offset = 0;
3 years ago
} */
if(backLimit) {
offset = -backLimit;
limit += backLimit;
4 years ago
3 years ago
/* return this.requestHistory(reqPeerId, maxId, limit, offset, undefined, threadId).then((historyResult) => {
historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length;
4 years ago
const history = (historyResult.messages as MyMessage[]).map(message => message.mid);
return {
4 years ago
count: historyStorage.count,
history,
offsetIdOffset: (historyResult as MessagesMessages.messagesMessagesSlice).offset_id_offset || 0
};
3 years ago
}); */
4 years ago
}
3 years ago
const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit);
if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both) === SliceEnd.Both)) {
3 years ago
return {
count: historyStorage.count,
history: haveSlice.slice,
offsetIdOffset: haveSlice.offsetIdOffset
};
}
4 years ago
3 years ago
return this.fillHistoryStorage(peerId, maxId, limit, offset, historyStorage, threadId).then(() => {
const slice = historyStorage.history.sliceMe(maxId, offset, limit);
return {
4 years ago
count: historyStorage.count,
3 years ago
history: slice?.slice || historyStorage.history.constructSlice(),
3 years ago
offsetIdOffset: slice?.offsetIdOffset || historyStorage.count
};
4 years ago
});
}
3 years ago
public isHistoryResultEnd(historyResult: Exclude<MessagesMessages, MessagesMessages.messagesMessagesNotModified>, limit: number, add_offset: number) {
const {offset_id_offset, messages} = historyResult as MessagesMessages.messagesMessagesSlice;
const count = (historyResult as MessagesMessages.messagesMessagesSlice).count || messages.length;
const offsetIdOffset = offset_id_offset || 0;
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const isTopEnd = offsetIdOffset >= (count - topWasMeantToLoad) || count < topWasMeantToLoad;
const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0);
return {count, offsetIdOffset, isTopEnd, isBottomEnd};
}
public mergeHistoryResult(slicedArray: SlicedArray,
historyResult: Parameters<AppMessagesManager['isHistoryResultEnd']>[0],
offset_id: number,
limit: number,
add_offset: number) {
const {messages} = historyResult as MessagesMessages.messagesMessagesSlice;
const isEnd = this.isHistoryResultEnd(historyResult, limit, add_offset);
const {count, offsetIdOffset, isTopEnd, isBottomEnd} = isEnd;
const mids = messages.map((message) => {
return (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 < count) {
let i = 0;
for(const length = mids.length; i < length; ++i) {
if(offset_id > mids[i]) {
break;
}
}
mids.splice(i, 0, offset_id);
}
4 years ago
3 years ago
const slice = slicedArray.insertSlice(mids) || slicedArray.slice;
if(isTopEnd) {
slice.setEnd(SliceEnd.Top);
}
if(isBottomEnd) {
slice.setEnd(SliceEnd.Bottom);
}
3 years ago
return {slice, mids, messages, ...isEnd};
}
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) => {
const {count, isBottomEnd, slice, messages} = this.mergeHistoryResult(historyStorage.history, historyResult, offset_id, limit, add_offset);
3 years ago
historyStorage.count = count;
3 years ago
3 years ago
/* if(!maxId && historyResult.messages.length) {
maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1);
4 years ago
}
3 years ago
const wasTotalCount = historyStorage.history.length; */
4 years ago
3 years ago
for(let i = 0, length = messages.length; i < length; ++i) {
const message = messages[i] as MyMessage;
if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.dispatchEvent('history_reply_markup', {peerId});
4 years ago
}
}
3 years ago
if(isBottomEnd) {
historyStorage.maxId = slice[0]; // ! WARNING
3 years ago
}
3 years ago
/* const isBackLimit = offset < 0 && -offset !== fullLimit;
if(isBackLimit) {
return;
}
const totalCount = historyStorage.history.length;
4 years ago
fullLimit -= (totalCount - wasTotalCount);
3 years ago
const migratedNextPeer = this.migratedFromTo[peerId];
const migratedPrevPeer = this.migratedToFrom[peerId]
const isMigrated = migratedNextPeer !== undefined || migratedPrevPeer !== undefined;
4 years ago
if(isMigrated) {
historyStorage.count = Math.max(historyStorage.count, totalCount) + 1;
3 years ago
}
4 years ago
if(fullLimit > 0) {
3 years ago
maxId = historyStorage.history.slice[totalCount - 1];
if(isMigrated) {
4 years ago
if(!historyResult.messages.length) {
if(migratedPrevPeer) {
maxId = 0;
peerId = migratedPrevPeer;
4 years ago
} else {
historyStorage.count = totalCount;
return true;
}
}
return this.fillHistoryStorage(peerId, maxId, fullLimit, historyStorage, threadId);
3 years ago
} else if(totalCount < historyStorage.count) {
return this.fillHistoryStorage(peerId, maxId, fullLimit, offset, historyStorage, threadId);
4 years ago
}
3 years ago
} */
4 years ago
});
}
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);
4 years ago
//rootScope.broadcast('history_request');
3 years ago
const options: any = {
peer: appPeersManager.getInputPeerById(peerId),
3 years ago
offset_id: appMessagesIdsManager.getServerMessageId(maxId) || 0,
offset_date: offsetDate,
add_offset: offset,
limit,
4 years ago
max_id: 0,
min_id: 0,
hash: 0
};
3 years ago
if(threadId) {
3 years ago
options.msg_id = appMessagesIdsManager.getServerMessageId(threadId) || 0;
3 years ago
}
const promise: ReturnType<AppMessagesManager['requestHistory']> = apiManager.invokeApiSingle(threadId ? 'messages.getReplies' : 'messages.getHistory', options, {
//timeout: APITIMEOUT,
4 years ago
noErrorBox: true
}) as any;
return promise.then((historyResult) => {
if(DEBUG) {
this.log('requestHistory result:', peerId, historyResult, maxId, limit, offset);
}
4 years ago
4 years ago
appUsersManager.saveApiUsers(historyResult.users);
appChatsManager.saveApiChats(historyResult.chats);
this.saveMessages(historyResult.messages);
if(appPeersManager.isChannel(peerId)) {
apiUpdatesManager.addChannelState(-peerId, (historyResult as MessagesMessages.messagesChannelMessages).pts);
4 years ago
}
let length = historyResult.messages.length, count = (historyResult as MessagesMessages.messagesMessagesSlice).count;
if(length && historyResult.messages[length - 1].deleted) {
4 years ago
historyResult.messages.splice(length - 1, 1);
length--;
count--;
4 years ago
}
// will load more history if last message is album grouped (because it can be not last item)
// historyResult.messages: desc sorted
const historyStorage = this.getHistoryStorage(peerId, threadId);
const oldestMessage: Message.message = historyResult.messages[length - 1] as any;
if(length && oldestMessage.grouped_id) {
const foundSlice = historyStorage.history.findSlice(oldestMessage.mid);
if(foundSlice && (foundSlice.slice.length + historyResult.messages.length) < count) {
return this.requestHistory(peerId, oldestMessage.mid, 10, 0, offsetDate, threadId).then((_historyResult) => {
return historyResult;
});
}
}
return historyResult;
4 years ago
}, (error) => {
switch (error.type) {
case 'CHANNEL_PRIVATE':
let channel = appChatsManager.getChat(-peerId);
4 years ago
channel = {_: 'channelForbidden', access_hash: channel.access_hash, title: channel.title};
apiUpdatesManager.processUpdateMessage({
_: 'updates',
updates: [{
_: 'updateChannel',
channel_id: -peerId
4 years ago
}],
chats: [channel],
users: []
});
break;
}
throw error;
4 years ago
});
}
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',
3 years ago
id: appMessagesIdsManager.getServerMessageId(msgId)
};
});
let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>;
if(+peerId < 0 && appPeersManager.isChannel(+peerId)) {
promise = apiManager.invokeApiSingle('channels.getMessages', {
channel: appChatsManager.getChannelInput(-+peerId),
id: msgIds
});
} else {
promise = apiManager.invokeApiSingle('messages.getMessages', {
id: msgIds
});
}
const after = promise.then(getMessagesResult => {
if(getMessagesResult._ !== 'messages.messagesNotModified') {
appUsersManager.saveApiUsers(getMessagesResult.users);
appChatsManager.saveApiChats(getMessagesResult.chats);
this.saveMessages(getMessagesResult.messages);
}
}).finally(() => {
rootScope.dispatchEvent('messages_downloaded', {peerId: +peerId, mids});
});
promises.push(after);
}
4 years ago
Promise.all(promises).finally(() => {
this.fetchSingleMessagesPromise = null;
if(Object.keys(this.needSingleMessages).length) this.fetchSingleMessages();
resolve();
});
}, 0);
4 years ago
});
}
public wrapSingleMessage(peerId: number, msgId: number, overwrite = false): Promise<void> {
if(!this.getMessageByPeer(peerId, msgId).deleted && !overwrite) {
rootScope.dispatchEvent('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;
}
4 years ago
}
public setTyping(peerId: number, action: SendMessageAction): Promise<boolean> {
let typing = this.typings[peerId];
if(!rootScope.myId ||
!peerId ||
!this.canWriteToPeer(peerId) ||
peerId === rootScope.myId ||
typing?.type === action._
) {
return Promise.resolve(false);
}
if(typing?.timeout) {
clearTimeout(typing.timeout);
}
typing = this.typings[peerId] = {
type: action._
};
return apiManager.invokeApi('messages.setTyping', {
peer: appPeersManager.getInputPeerById(peerId),
action
}).finally(() => {
if(typing === this.typings[peerId]) {
typing.timeout = window.setTimeout(() => {
delete this.typings[peerId];
}, 6000);
}
});
}
3 years ago
private handleReleasingMessage(message: MyMessage, storage: MessagesStorage) {
const media = (message as Message.message).media;
if(media) {
3 years ago
const c = (media as MessageMedia.messageMediaWebPage).webpage as WebPage.webPage || media as MessageMedia.messageMediaPhoto | MessageMedia.messageMediaDocument;
const smth: Photo.photo | MyDocument = (c as MessageMedia.messageMediaPhoto).photo as any || (c as MessageMedia.messageMediaDocument).document as any;
if(smth?.file_reference) {
referenceDatabase.deleteContext(smth.file_reference, {type: 'message', peerId: message.peerId, messageId: message.mid});
}
if('webpage' in media) {
3 years ago
const isScheduled = this.getScheduledMessagesStorage(message.peerId) === storage;
const messageKey = appWebPagesManager.getMessageKeyForPendingWebPage(message.peerId, message.mid, isScheduled);
appWebPagesManager.deleteWebPageFromPending(media.webpage, messageKey);
}
}
}
private handleDeletedMessages(peerId: number, storage: MessagesStorage, messages: number[]) {
const history: {
count: number,
unread: number,
3 years ago
unreadMentions: number,
msgs: Set<number>,
albums?: {[groupId: string]: Set<number>},
3 years ago
} = {
count: 0,
unread: 0,
unreadMentions: 0,
msgs: new Set()
};
for(const mid of messages) {
const message: MyMessage = this.getMessageFromStorage(storage, mid);
if(message.deleted) continue;
3 years ago
this.handleReleasingMessage(message, storage);
this.updateMessageRepliesIfNeeded(message);
if(!message.pFlags.out && !message.pFlags.is_outgoing && message.pFlags.unread) {
3 years ago
++history.unread;
appNotificationsManager.cancel('msg' + mid);
3 years ago
if(message.pFlags.mentioned) {
++history.unreadMentions;
3 years ago
this.modifyCachedMentions(peerId, mid, false);
3 years ago
}
}
3 years ago
++history.count;
history.msgs.add(mid);
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.has(mid)) {
peerMessagesToHandle.delete(mid);
}
}
if(history.albums) {
for(const groupId in history.albums) {
rootScope.dispatchEvent('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);
}
}
public getMediaFromMessage(message: any) {
return message.action ?
message.action.photo :
message.media && (
message.media.photo ||
message.media.document || (
message.media.webpage && (
message.media.webpage.document ||
message.media.webpage.photo
)
)
);
}
4 years ago
}
const appMessagesManager = new AppMessagesManager();
MOUNT_CLASS_TO.appMessagesManager = appMessagesManager;
export default appMessagesManager;