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.

6385 lines
211 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 { formatDateAccordingToTodayNew, formatTime, tsNow } from "../../helpers/date";
import { createPosterForVideo } from "../../helpers/files";
import { randomLong } from "../../helpers/random";
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, MessagesGetDialogs, InputChannel, InputDialogPeer, ReactionCount, MessagePeerReaction, MessagesSearchCounter, Peer, MessageReactions } from "../../layer";
import { ArgumentTypes, InvokeApiOptions } from "../../types";
import I18n, { FormatterArguments, i18n, join, langPack, LangPackKey, UNSUPPORTED_LANG_PACK_KEY, _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";
3 years ago
import DialogsStorage, { GLOBAL_FOLDER_ID } from "../storages/dialogs";
import FiltersStorage from "../storages/filters";
//import { telegramMeWebService } from "../mtproto/mtproto";
import apiUpdatesManager from "./apiUpdatesManager";
import appChatsManager, { ChatRights } from "./appChatsManager";
import appDocsManager, { MyDocument } from "./appDocsManager";
import appDownloadManager from "./appDownloadManager";
import appPeersManager from "./appPeersManager";
import appPhotosManager, { MyPhoto } from "./appPhotosManager";
import appPollsManager from "./appPollsManager";
import appStateManager from "./appStateManager";
import appUsersManager from "./appUsersManager";
import appWebPagesManager from "./appWebPagesManager";
import appDraftsManager, { MyDraftMessage } 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 htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment";
import htmlToSpan from "../../helpers/dom/htmlToSpan";
3 years ago
import { MUTE_UNTIL, NULL_PEER_ID, REPLIES_PEER_ID, SERVICE_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";
import IMAGE_MIME_TYPES_SUPPORTED from "../../environment/imageMimeTypesSupport";
import VIDEO_MIME_TYPES_SUPPORTED from "../../environment/videoMimeTypesSupport";
2 years ago
import './appGroupCallsManager';
import appGroupCallsManager from "./appGroupCallsManager";
import appReactionsManager from "./appReactionsManager";
import { getRestrictionReason, isRestricted } from "../../helpers/restrictions";
import copy from "../../helpers/object/copy";
import getObjectKeysAndSort from "../../helpers/object/getObjectKeysAndSort";
import forEachReverse from "../../helpers/array/forEachReverse";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import deepEqual from "../../helpers/object/deepEqual";
2 years ago
import escapeRegExp from "../../helpers/string/escapeRegExp";
import limitSymbols from "../../helpers/string/limitSymbols";
import splitStringByLength from "../../helpers/string/splitStringByLength";
import debounce from "../../helpers/schedulers/debounce";
//console.trace('include');
// 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 = Map<number, any>;
3 years ago
export type MyMessageActionType = Message.messageService['action']['_'];
type PendingAfterMsg = Partial<InvokeApiOptions & {
afterMessageId: string,
messageId: string
}>;
type MapValueType<A> = A extends Map<any, infer V> ? V : never;
export type BatchUpdates = {
'messages_reactions': AppMessagesManager['batchUpdateReactions'],
'messages_views': AppMessagesManager['batchUpdateViews']
};
4 years ago
export class AppMessagesManager {
private messagesStorageByPeerId: {[peerId: string]: MessagesStorage};
public groupedMessagesStorage: {[groupId: string]: MessagesStorage}; // will be used for albums
3 years ago
private scheduledMessagesStorage: {[peerId: PeerId]: MessagesStorage};
private historiesStorage: {
3 years ago
[peerId: PeerId]: HistoryStorage
};
private threadsStorage: {
3 years ago
[peerId: PeerId]: {
[threadId: string]: HistoryStorage
}
};
private searchesStorage: {
3 years ago
[peerId: PeerId]: Partial<{
[inputFilter in MyInputMessagesFilter]: {
count?: number,
history: number[]
}
}>
};
3 years ago
public pinnedMessages: {[peerId: PeerId]: PinnedStorage};
public threadsServiceMessagesIdsStorage: {[peerId_threadId: string]: number};
private threadsToReplies: {
[peerId_threadId: string]: string;
};
private pendingByRandomId: {
[randomId: string]: {
3 years ago
peerId: PeerId,
tempId: number,
threadId: number,
storage: MessagesStorage
}
} = {};
3 years ago
private pendingByMessageId: {[mid: string]: Long} = {};
private pendingAfterMsgs: {[peerId: PeerId]: PendingAfterMsg} = {};
public pendingTopMsgs: {[peerId: PeerId]: number} = {};
private tempFinalizeCallbacks: {
[tempId: string]: {
[callbackName: string]: Partial<{
deferred: CancellablePromise<void>,
callback: (message: any) => Promise<any>
}>
}
} = {};
private sendSmthLazyLoadQueue = new LazyLoadQueueBase(10);
4 years ago
3 years ago
private needSingleMessages: Map<PeerId, Map<number, CancellablePromise<Message>>> = new Map();
private fetchSingleMessagesPromise: Promise<void> = null;
4 years ago
private maxSeenId = 0;
4 years ago
3 years ago
public migratedFromTo: {[peerId: PeerId]: PeerId} = {};
public migratedToFrom: {[peerId: PeerId]: PeerId} = {};
4 years ago
private newMessagesHandleTimeout = 0;
3 years ago
private newMessagesToHandle: {[peerId: PeerId]: Set<number>} = {};
private newDialogsHandlePromise: Promise<any>;
3 years ago
private newDialogsToHandle: {[peerId: PeerId]: Dialog} = {};
public newUpdatesAfterReloadToHandle: {[peerId: PeerId]: Set<Update>} = {};
private notificationsHandlePromise = 0;
3 years ago
private notificationsToHandle: {[peerId: PeerId]: {
fwdCount: number,
3 years ago
fromId: PeerId,
topMessage?: MyMessage
}} = {};
4 years ago
4 years ago
private reloadConversationsPromise: Promise<void>;
3 years ago
private reloadConversationsPeers: Map<PeerId, {inputDialogPeer: InputDialogPeer, promise: CancellablePromise<Dialog>}> = new Map();
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: PeerId]: {action: SendMessageAction, timeout?: number}} = {};
private middleware: ReturnType<typeof getMiddleware>;
3 years ago
private unreadMentions: {[peerId: PeerId]: SlicedArray} = {};
private goToNextMentionPromises: {[peerId: PeerId]: Promise<any>} = {};
private batchUpdates: {
[k in keyof BatchUpdates]?: {
callback: BatchUpdates[k],
batch: ArgumentTypes<BatchUpdates[k]>[0]
}
} = {};
private batchUpdatesDebounced: () => Promise<void>;
3 years ago
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,
updateMessageReactions: this.onUpdateMessageReactions,
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}) => {
3 years ago
const dialogs = this.dialogsStorage.getFolderDialogs(0).concat(this.dialogsStorage.getFolderDialogs(1));
let filterFunc: (dialog: Dialog) => boolean;
if(key === 'notifyUsers') filterFunc = (dialog) => dialog.peerId.isUser();
else if(key === 'notifyBroadcasts') filterFunc = (dialog) => dialog.peerId.isBroadcast();
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);
if(dialog) {
if(!threadId) {
dialog.draft = draft;
let drop = false;
if(!draft && !appMessagesIdsManager.getServerMessageId(dialog.top_message)) {
this.dialogsStorage.dropDialog(peerId);
drop = true;
} else {
this.dialogsStorage.generateIndexForDialog(dialog);
this.dialogsStorage.pushDialog(dialog);
}
rootScope.dispatchEvent('dialog_draft', {
peerId,
dialog,
drop,
draft,
index: dialog.index
});
}
3 years ago
} else {
this.reloadConversation(peerId);
4 years ago
}
3 years ago
});
rootScope.addEventListener('poll_update', ({poll}) => {
const set = appPollsManager.pollToMessages[poll.id];
if(set) {
for(const key of set) {
const [peerId, mid] = key.split('_');
3 years ago
const message = this.getMessageByPeer(peerId.toPeerId(), +mid);
this.setDialogToStateIfMessageIsTop(message);
}
}
});
appStateManager.getState().then(state => {
if(state.maxSeenMsgId) {
this.maxSeenId = state.maxSeenMsgId;
}
});
this.batchUpdatesDebounced = debounce(() => {
for(const event in this.batchUpdates) {
const details = this.batchUpdates[event as keyof BatchUpdates];
delete this.batchUpdates[event as keyof BatchUpdates];
// @ts-ignore
const result = details.callback(details.batch);
if(result && (!(result instanceof Array) || result.length)) {
// @ts-ignore
rootScope.dispatchEvent(event as keyof BatchUpdates, result);
}
}
}, 33, false, true);
}
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);
});
}
3 years ago
public sendText(peerId: PeerId, text: string, options: Partial<{
3 years ago
entities: MessageEntity[],
replyToMsgId: number,
threadId: number,
3 years ago
viaBotId: BotId,
queryId: string,
resultId: string,
noWebPage: true,
3 years ago
replyMarkup: ReplyMarkup,
clearDraft: true,
3 years ago
webPage: WebPage,
scheduleDate: number,
silent: true,
sendAsPeerId: PeerId,
}> = {}) {
if(!text.trim()) {
return Promise.resolve();
4 years ago
}
//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
}
const sendAs = options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined
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,
send_as: sendAs
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,
send_as: sendAs
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);
// * fix copying object with promise
const promise = message.promise;
delete message.promise;
3 years ago
const newMessage = copy(message);
message.promise = promise;
3 years ago
newMessage.date = updates.date;
newMessage.id = updates.id;
newMessage.media = updates.media;
newMessage.entities = updates.entities;
this.wrapMessageEntities(newMessage);
if(updates.pFlags.out) {
3 years ago
newMessage.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,
3 years ago
id: newMessage.id
4 years ago
}, {
_: options.scheduleDate ? 'updateNewScheduledMessage' : (isChannel ? 'updateNewChannelMessage' : 'updateNewMessage'),
3 years ago
message: newMessage,
4 years ago
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)
message.promise.resolve();
}, (error: any) => {
4 years ago
toggleError(true);
message.promise.reject(error);
4 years ago
}).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
});
return message.promise;
4 years ago
}
3 years ago
public sendFile(peerId: PeerId, file: File | Blob | MyDocument, options: Partial<{
isRoundMessage: true,
isVoiceMessage: true,
isGroupedItem: true,
isMedia: true,
replyToMsgId: number,
sendAsPeerId: PeerId,
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: 'document' | 'audio' | 'video' | 'voice' | 'photo', 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_MIME_TYPES_SUPPORTED.has(fileType);
let photo: MyPhoto, document: MyDocument;
3 years ago
let actionName: Extract<SendMessageAction['_'], 'sendMessageUploadAudioAction' | 'sendMessageUploadDocumentAction' | 'sendMessageUploadPhotoAction' | 'sendMessageUploadVideoAction'>;
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(VIDEO_MIME_TYPES_SUPPORTED.has(fileType)) {
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'] as (typeof attachType)[]).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',
3 years ago
file: inputFile,
4 years ago
};
break;
4 years ago
default:
inputMedia = {
_: 'inputMediaUploadedDocument',
file: inputFile,
mime_type: fileType,
3 years ago
pFlags: {
force_file: actionName === 'sendMessageUploadDocumentAction' ? true : undefined,
// nosound_video: options.noSound ? true : undefined
},
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,
send_as: options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined
}).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);
throw error;
});
});
sentDeferred.then(message.promise.resolve, message.promise.reject);
}
return {message, promise: sentDeferred};
4 years ago
}
3 years ago
public async sendAlbum(peerId: PeerId, files: File[], options: Partial<{
isMedia: true,
entities: MessageEntity[],
replyToMsgId: number,
sendAsPeerId: PeerId,
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: Parameters<AppMessagesManager['sendFile']>[2] = {
isGroupedItem: true,
isMedia: options.isMedia,
scheduleDate: options.scheduleDate,
silent: options.silent,
replyToMsgId,
threadId: options.threadId,
sendAsPeerId: options.sendAsPeerId,
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) {
setTimeout(() => {
appDraftsManager.clearDraft(peerId, options.threadId);
}, 0);
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: InputSingleMedia[]) => {
this.setTyping(peerId, {_: 'sendMessageCancelAction'});
const deferred = deferredPromise<void>();
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,
send_as: options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined
}).then((updates) => {
apiUpdatesManager.processUpdateMessage(updates);
deferred.resolve();
}, (error) => {
messages.forEach(message => toggleError(message, true));
deferred.reject(error);
});
}
});
return deferred;
};
const promises: Promise<InputSingleMedia>[] = messages.map((message) => {
return (message.send() as Promise<InputMedia>).then((inputMedia) => {
return apiManager.invokeApi('messages.uploadMedia', {
peer: inputPeer,
media: inputMedia
});
})
.then(messageMedia => {
let inputMedia: InputMedia;
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;
});
});
return Promise.all(promises).then(inputs => {
return invoke(inputs.filter(Boolean));
});
}
public sendContact(peerId: PeerId, contactPeerId: PeerId) {
return this.sendOther(peerId, appUsersManager.getContactMediaInput(contactPeerId));
}
3 years ago
public sendOther(peerId: PeerId, inputMedia: InputMedia, options: Partial<{
replyToMsgId: number,
threadId: number,
3 years ago
viaBotId: BotId,
3 years ago
replyMarkup: ReplyMarkup,
clearDraft: true,
queryId: string
resultId: string,
scheduleDate: number,
3 years ago
silent: true,
geoPoint: GeoPoint,
sendAsPeerId: PeerId,
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: {},
recent_voters: []
4 years ago
});
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: inputMedia.user_id ?? '0',
3 years ago
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
}
const sendAs = options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined;
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,
send_as: sendAs
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,
send_as: sendAs
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
});
return message.promise;
}
/* 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});
3 years ago
this.setDialogTopMessage(message);
setTimeout(() => {
rootScope.dispatchEvent('history_append', {storage, peerId, mid: messageId});
}, 0);
}
3 years ago
this.pendingByRandomId[message.random_id] = {
peerId,
tempId: messageId,
threadId: options.threadId,
storage
};
4 years ago
if(!options.isGroupedItem && message.send) {
setTimeout(() => {
if(options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
}
message.send();
}, 0);
}
}
3 years ago
private generateOutgoingMessage(peerId: PeerId, options: Partial<{
scheduleDate: number,
replyToMsgId: number,
sendAsPeerId: PeerId,
threadId: number,
3 years ago
viaBotId: BotId,
groupId: string,
3 years ago
replyMarkup: ReplyMarkup,
}>) {
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
let postAuthor: string;
const isBroadcast = appPeersManager.isBroadcast(peerId);
if(isBroadcast) {
const chat = appPeersManager.getPeer(peerId) as Chat.channel;
if(chat.pFlags.signatures) {
const user = appUsersManager.getSelf();
const fullName = user.first_name + (user.last_name ? ' ' + user.last_name : '');
postAuthor = fullName;
}
}
const message: Message.message = {
_: 'message',
id: this.generateTempMessageId(peerId),
from_id: options.sendAsPeerId ? appPeersManager.getOutputPeer(options.sendAsPeerId) : this.generateFromId(peerId),
peer_id: appPeersManager.getOutputPeer(peerId),
post_author: postAuthor,
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: isBroadcast && 1,
pending: true,
promise: options.groupId === undefined ? deferredPromise() : undefined
};
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;
}
3 years ago
private generateReplies(peerId: PeerId) {
let replies: MessageReplies.messageReplies;
if(appPeersManager.isBroadcast(peerId)) {
const channelFull = appProfileManager.getCachedFullChat(peerId.toChatId()) 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
*/
3 years ago
private generateFromId(peerId: PeerId) {
if(peerId.isAnyChat() && (peerId.isBroadcast() || this.isAnonymousSending(peerId))) {
return undefined;
} else {
3 years ago
return appPeersManager.getOutputPeer(appUsersManager.getSelf().id.toPeerId());
}
}
3 years ago
private generateFlags(peerId: PeerId) {
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;
}
3 years ago
private generateForwardHeader(peerId: PeerId, originalMessage: Message.message) {
const myId = appUsersManager.getSelf().id.toPeerId();
const fromId = originalMessage.fromId;
if(fromId === myId && originalMessage.peerId === myId && !originalMessage.fwd_from) {
return;
}
const fwdHeader: MessageFwdHeader.messageFwdHeader = {
_: 'messageFwdHeader',
flags: 0,
date: originalMessage.date
};
let isUserHidden = false;
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.post_author = originalMessage.post_author;
if(fromId.isUser()) {
const userFull = appProfileManager.getCachedFullUser(fromId.toUserId());
if(userFull?.private_forward_name) {
fwdHeader.from_name = userFull.private_forward_name;
isUserHidden = true;
}
}
if(!isUserHidden) {
fwdHeader.from_id = appPeersManager.getOutputPeer(fromId);
}
}
if(appPeersManager.isBroadcast(originalMessage.peerId)) {
if(originalMessage.post_author) {
fwdHeader.post_author = originalMessage.post_author;
}
fwdHeader.channel_post = originalMessage.id;
}
if(peerId === myId && !isUserHidden) {
fwdHeader.saved_from_msg_id = originalMessage.id;
fwdHeader.saved_from_peer = appPeersManager.getOutputPeer(originalMessage.peerId);
}
return fwdHeader;
}
3 years ago
public generateFakeAvatarMessage(peerId: PeerId, photo: Photo) {
const maxId = Number.MAX_SAFE_INTEGER;
const message: Message.messageService = {
_: 'messageService',
pFlags: {},
action: {
_: 'messageActionChannelEditPhoto',
photo
},
id: maxId,
peer_id: appPeersManager.getOutputPeer(peerId),
mid: maxId,
peerId,
date: (photo as Photo.photo).date,
fromId: peerId
};
this.getMessagesStorage(peerId).set(maxId, message);
return message;
}
3 years ago
public isAnonymousSending(peerId: PeerId): boolean {
return peerId.isAnyChat() && appPeersManager.getPeer(peerId).admin_rights?.pFlags?.anonymous;
}
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];
storage.delete(tempId);
4 years ago
return true;
}
return false;
}
3 years ago
/* 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;
3 years ago
} */
public async fillConversations(): Promise<void> {
const middleware = this.middleware.get();
while(!this.dialogsStorage.isDialogsLoaded(GLOBAL_FOLDER_ID)) {
const result = await this.getTopMessages(100, GLOBAL_FOLDER_ID);
if(!middleware() || result.isEnd) {
break;
}
}
}
3 years ago
/* 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;
3 years ago
} */
3 years ago
public getConversations(query = '', offsetIndex?: number, limit?: number, folderId = 0, skipMigrated?: boolean) {
return this.dialogsStorage.getDialogs(query, offsetIndex, limit, folderId, skipMigrated);
4 years ago
}
3 years ago
public getReadMaxIdIfUnread(peerId: PeerId, 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);
3 years ago
const readMaxId = peerId.isUser() ? 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;
3 years ago
let offsetPeerId: PeerId;
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
}
3 years ago
const useLimit = 100;
const middleware = this.middleware.get();
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
const params: MessagesGetDialogs = {
folder_id: folderId,
4 years ago
offset_date: offsetDate,
offset_id: offsetId,
offset_peer: appPeersManager.getInputPeerById(offsetPeerId),
3 years ago
limit: useLimit,
hash: '0'
};
return apiManager.invokeApiSingle('messages.getDialogs', params, {
//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
3 years ago
if(!offsetId && !offsetDate && !offsetPeerId && folderId !== GLOBAL_FOLDER_ID) {
this.dialogsStorage.resetPinnedOrder(folderId);
}
if(!offsetDate) {
telegramMeWebManager.setAuthorized(true);
}
4 years ago
appUsersManager.saveApiUsers(dialogsResult.users);
appChatsManager.saveApiChats(dialogsResult.chats);
this.saveMessages(dialogsResult.messages);
3 years ago
/* if(folderId === 0 && !offsetDate) {
const found = dialogsResult.dialogs.find(dialog => appPeersManager.getPeerId(dialog.peer) === -1325963535);
if(!found) {
debugger;
}
} */
let maxSeenIdIncremented = offsetDate ? true : false;
let hasPrepend = false;
3 years ago
const noIdsDialogs: {[peerId: PeerId]: Dialog} = {};
const setFolderId = folderId === GLOBAL_FOLDER_ID ? 0 : folderId;
const saveGlobalOffset = folderId === GLOBAL_FOLDER_ID;
forEachReverse((dialogsResult.dialogs as Dialog[]), dialog => {
//const d = Object.assign({}, dialog);
// ! нужно передавать folderId, так как по папке !== 0 нет свойства folder_id
3 years ago
if(dialog.folder_id === undefined) {
dialog.folder_id = setFolderId;
}
this.dialogsStorage.saveDialog(dialog, undefined, true, saveGlobalOffset);
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, params);
/* if(dialog.peerId === -1213511294) {
this.log.error('lun bot', folderId);
} */
4 years ago
}
});
4 years ago
3 years ago
const keys = Object.keys(noIdsDialogs);
if(keys.length) {
4 years ago
//setTimeout(() => { // test bad situation
3 years ago
const peerIds = keys.map(key => key.toPeerId());
const promises = peerIds.map(peerId => this.reloadConversation(peerId));
Promise.all(promises).then(() => {
rootScope.dispatchEvent('dialogs_multiupdate', noIdsDialogs);
4 years ago
3 years ago
for(let i = 0; i < peerIds.length; ++i) {
rootScope.dispatchEvent('dialog_unread', {peerId: peerIds[i]});
4 years ago
}
});
//}, 10e3);
}
const count = (dialogsResult as MessagesDialogs.messagesDialogsSlice).count;
// exclude empty draft dialogs
3 years ago
const folderDialogs = this.dialogsStorage.getFolderDialogs(folderId, false);
let dialogsLength = 0;
3 years ago
for(let i = 0, length = folderDialogs.length; i < length; ++i) {
if(appMessagesIdsManager.getServerMessageId(folderDialogs[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
}
3 years ago
const dialogs = (dialogsResult as MessagesDialogs.messagesDialogsSlice).dialogs;
const slicedDialogs = limit === useLimit ? dialogs : dialogs.slice(0, limit);
return {
3 years ago
isEnd: isEnd && slicedDialogs[slicedDialogs.length - 1] === dialogs[dialogs.length - 1],
count,
3 years ago
dialogs: slicedDialogs
};
4 years ago
});
}
3 years ago
public forwardMessages(peerId: PeerId, fromPeerId: PeerId, mids: number[], options: Partial<{
withMyScore: true,
silent: true,
scheduleDate: number,
dropAuthor: boolean,
dropCaptions: boolean,
sendAsPeerId: PeerId,
}> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
mids = mids.slice().sort((a, b) => a - b);
for(let i = 0, length = mids.length; i < length; ++i) {
const mid = mids[i];
const originalMessage: Message.message = this.getMessageByPeer(fromPeerId, mid);
if(originalMessage.pFlags.is_outgoing) { // this can happen when forwarding a changelog
this.sendText(peerId, originalMessage.message, {
entities: originalMessage.entities,
scheduleDate: options.scheduleDate,
silent: options.silent
});
mids.splice(i--, 1);
}
}
if(!mids.length) {
return Promise.resolve();
}
if(options.dropCaptions) {
options.dropAuthor = true;
}
const groups: {
[groupId: string]: {
tempId: string,
messages: Message.message[]
}
} = {};
const newMessages = mids.map(mid => {
const originalMessage: Message.message = this.getMessageByPeer(fromPeerId, mid);
const message: Message.message = this.generateOutgoingMessage(peerId, options);
const keys: Array<keyof Message.message> = [
'entities',
'media',
// 'reply_markup'
];
if(!options.dropAuthor) {
message.fwd_from = this.generateForwardHeader(peerId, originalMessage);
keys.push('views', 'forwards');
if(message.fwd_from?.from_name && peerId === rootScope.myId) {
delete message.from_id;
}
}
if(!options.dropCaptions || !originalMessage.media) {
keys.push('message');
}
keys.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,
drop_author: options.dropAuthor,
drop_media_captions: options.dropCaptions,
send_as: options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined
}, 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 generateEmptyMessage(mid: number): Message.messageEmpty {
return {
4 years ago
_: 'messageEmpty',
id: appMessagesIdsManager.getServerMessageId(mid),
mid,
deleted: true,
pFlags: {}
4 years ago
};
}
public getMessageFromStorage(storage: MessagesStorage, mid: number) {
return storage && storage.get(mid) || this.generateEmptyMessage(mid);
}
private createMessageStorage() {
const storage: MessagesStorage = new Map();
/* 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;
}
3 years ago
public getMessagesStorage(peerId: PeerId) {
return this.messagesStorageByPeerId[peerId] ?? (this.messagesStorageByPeerId[peerId] = this.createMessageStorage());
}
public getMessageById(messageId: number) {
for(const peerId in this.messagesStorageByPeerId) {
3 years ago
if(appPeersManager.isChannel(peerId.toPeerId())) {
continue;
}
const message = this.messagesStorageByPeerId[peerId].get(messageId);
if(message) {
return message;
}
}
return this.getMessageFromStorage(null, messageId);
}
3 years ago
public getMessageByPeer(peerId: PeerId, messageId: number) {
if(!peerId) {
return this.getMessageById(messageId);
}
return this.getMessageFromStorage(this.getMessagesStorage(peerId), messageId);
}
3 years ago
public getMessagePeer(message: any): PeerId {
const toId = message.peer_id && appPeersManager.getPeerId(message.peer_id) || NULL_PEER_ID;
4 years ago
return toId;
4 years ago
}
3 years ago
public getDialogByPeerId(peerId: PeerId): [Dialog, number] | [] {
return this.dialogsStorage.getDialog(peerId);
4 years ago
}
3 years ago
public getDialogOnly(peerId: PeerId) {
return this.dialogsStorage.getDialogOnly(peerId);
}
3 years ago
public reloadConversation(inputPeer?: PeerId | InputPeer): CancellablePromise<Dialog>;
public reloadConversation(inputPeer: PeerId | InputPeer) {
let promise: CancellablePromise<Dialog>;
if(inputPeer !== undefined) {
const peerId = appPeersManager.getPeerId(inputPeer);
let obj = this.reloadConversationsPeers.get(peerId);
if(obj) {
promise = obj.promise;
}
if(promise) {
return promise;
}
promise = deferredPromise();
this.reloadConversationsPeers.set(peerId, obj = {
inputDialogPeer: appPeersManager.getInputDialogPeerById(inputPeer),
promise
});
}
4 years ago
3 years ago
if(this.reloadConversationsPromise) {
return promise || this.reloadConversationsPromise;
}
this.reloadConversationsPromise = new Promise((resolve, reject) => {
4 years ago
setTimeout(() => {
3 years ago
const inputDialogPeers: InputDialogPeer[] = [];
const promises: {[peerId: string]: typeof promise} = {};
for(const [peerId, {inputDialogPeer, promise}] of this.reloadConversationsPeers) {
inputDialogPeers.push(inputDialogPeer);
promises[peerId] = promise;
}
this.reloadConversationsPeers.clear();
4 years ago
3 years ago
const fullfillLeft = () => {
for(const peerId in promises) {
promises[peerId].resolve(undefined);
}
};
apiManager.invokeApi('messages.getPeerDialogs', {peers: inputDialogPeers}).then((result) => {
this.dialogsStorage.applyDialogs(result);
3 years ago
result.dialogs.forEach((dialog) => {
const peerId = dialog.peerId;
if(peerId) {
promises[peerId].resolve(dialog as Dialog);
delete promises[peerId];
}
});
fullfillLeft();
4 years ago
resolve();
3 years ago
}, (err) => {
fullfillLeft();
reject(err);
}).finally(() => {
4 years ago
this.reloadConversationsPromise = null;
if(this.reloadConversationsPeers.size) {
this.reloadConversation();
}
4 years ago
});
}, 0);
});
3 years ago
return promise || this.reloadConversationsPromise;
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);
});
}
3 years ago
public async flushHistory(peerId: PeerId, justClear?: boolean, revoke?: boolean) {
if(appPeersManager.isChannel(peerId)) {
const promise = this.getHistory(peerId, 0, 1);
const historyResult = promise instanceof Promise ? await promise : promise;
3 years ago
const channelId = peerId.toChatId();
const maxId = historyResult.history[0] || 0;
return apiManager.invokeApiSingle('channels.deleteHistory', {
channel: appChatsManager.getChannelInput(channelId),
3 years ago
max_id: appMessagesIdsManager.getServerMessageId(maxId)
}).then((bool) => {
if(bool) {
apiUpdatesManager.processLocalUpdate({
_: 'updateChannelAvailableMessages',
channel_id: channelId,
available_min_id: maxId
});
}
return bool;
});
}
return this.doFlushHistory(appPeersManager.getInputPeerById(peerId), justClear, revoke).then(() => {
[
this.historiesStorage,
this.threadsStorage,
this.searchesStorage,
this.pinnedMessages,
this.pendingAfterMsgs,
3 years ago
this.pendingTopMsgs
].forEach(s => {
delete s[peerId];
});
3 years ago
const m = this.needSingleMessages.get(peerId);
if(m) {
m.clear();
}
[
this.messagesStorageByPeerId,
this.scheduledMessagesStorage
].forEach(s => {
const ss = s[peerId];
if(ss) {
ss.clear();
}
});
if(justClear) {
rootScope.dispatchEvent('dialog_flush', {peerId});
} else {
delete this.notificationsToHandle[peerId];
delete this.typings[peerId];
3 years ago
const c = this.reloadConversationsPeers.get(peerId);
if(c) {
this.reloadConversationsPeers.delete(peerId);
c.promise.resolve(undefined);
}
3 years ago
this.dialogsStorage.dropDialogOnDeletion(peerId);
}
});
}
3 years ago
public onPeerDeleted(peerId: number) {
}
3 years ago
public hidePinnedMessages(peerId: PeerId) {
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});
});
}
3 years ago
public getPinnedMessage(peerId: PeerId) {
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: PeerId, 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);
});
}
3 years ago
public unpinAllMessages(peerId: PeerId): 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);
storage.forEach((message) => {
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 [mid, m] of group) {
if(m.message) {
if(++foundMessages > 1) break;
message = m.message;
totalEntities = m.totalEntities;
entities = m.entities;
2 years ago
}
}
if(foundMessages > 1) {
message = undefined;
totalEntities = undefined;
entities = undefined;
}
return {message, entities, totalEntities};
}
2 years ago
public getGroupsFirstMessage(message: Message.message): Message.message {
if(!message.grouped_id) return message;
const storage = this.groupedMessagesStorage[message.grouped_id];
let minMid = Number.MAX_SAFE_INTEGER;
for(const [mid, message] of storage) {
if(message.mid < minMid) {
minMid = message.mid;
}
}
return storage.get(minMid);
}
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: Message) {
if((message as Message.message)?.grouped_id) return this.getMidsByAlbum((message as Message.message).grouped_id);
else return [message.mid];
}
2 years ago
public filterMessages(message: MyMessage, verify: (message: MyMessage) => boolean) {
const out: MyMessage[] = [];
2 years ago
if((message as Message.message).grouped_id) {
const storage = this.groupedMessagesStorage[(message as Message.message).grouped_id];
for(const [mid, message] of storage) {
if(verify(message)) {
out.push(message);
}
}
} else {
if(verify(message)) {
out.push(message);
}
}
return out;
}
3 years ago
public generateTempMessageId(peerId: PeerId) {
const dialog = this.getDialogOnly(peerId);
3 years ago
return appMessagesIdsManager.generateMessageId(dialog?.top_message || 0, true);
}
public saveMessage(message: Message, options: Partial<{
storage: MessagesStorage,
isScheduled: true,
isOutgoing: true,
//isNew: boolean, // * new - from update
}> = {}) {
if(message.pFlags === undefined) {
message.pFlags = {};
}
4 years ago
if(message._ === 'messageEmpty') {
message.deleted = true;
return;
}
4 years ago
// * 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 isBroadcast = isChannel && appChatsManager.isBroadcast(peerId.toChatId());
const isMessage = message._ === 'message';
if(options.isOutgoing) {
message.pFlags.is_outgoing = true;
}
const mid = appMessagesIdsManager.generateMessageId(message.id);
message.mid = mid;
4 years ago
if(isMessage) {
if(options.isScheduled) {
message.pFlags.is_scheduled = true;
}
if(message.grouped_id) {
const storage = this.groupedMessagesStorage[message.grouped_id] ?? (this.groupedMessagesStorage[message.grouped_id] = new Map());
storage.set(mid, message);
}
if(message.via_bot_id) {
// ! WARNING
message.viaBotId = message.via_bot_id as any;
}
}
const dialog = this.getDialogOnly(peerId);
if(dialog && mid) {
if(mid > dialog[message.pFlags.out
? '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) {
message.reply_to.reply_to_msg_id = message.reply_to_mid = appMessagesIdsManager.generateMessageId(message.reply_to.reply_to_msg_id);
}
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(isMessage && message.replies) {
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.toPeerId();
const fwdHeader = isMessage && (message as Message.message).fwd_from as MessageFwdHeader;
message.peerId = peerId;
if(peerId === myId/* && !message.from_id && !message.fwd_from */) {
message.fromId = fwdHeader ? (fwdHeader.from_id ? appPeersManager.getPeerId(fwdHeader.from_id) : NULL_PEER_ID) : 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
if(fwdHeader) {
//if(peerId === myID) {
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);
const savedFromMid = appMessagesIdsManager.generateMessageId(msgId);
message.savedFrom = savedFromPeerId + '_' + savedFromMid;
}
/* if(peerId.isAnyChat() || peerId === myID) {
message.fromId = appPeersManager.getPeerID(!message.from_id || deepEqual(message.from_id, fwdHeader.from_id) ? fwdHeader.from_id : message.from_id);
} */
/* } else {
apiMessage.fwdPostID = fwdHeader.channel_post;
} */
4 years ago
message.fwdFromId = appPeersManager.getPeerId(fwdHeader.from_id);
if(!overwriting) {
fwdHeader.date -= serverTimeManager.serverTimeOffset;
4 years ago
}
}
4 years ago
const mediaContext: ReferenceContext = {
type: 'message',
peerId,
messageId: mid
};
4 years ago
2 years ago
/* if(isMessage) {
const entities = message.entities;
if(entities && entities.find(entity => entity._ === 'messageEntitySpoiler')) {
message.media = {_: 'messageMediaUnsupported'};
}
2 years ago
} */
if(isMessage && message.media) {
let unsupported = false;
switch(message.media._) {
case 'messageMediaEmpty': {
delete message.media;
break;
}
case 'messageMediaPhoto': {
if(message.media.ttl_seconds) {
unsupported = true;
} else {
message.media.photo = appPhotosManager.savePhoto(message.media.photo, mediaContext);
}
if(!(message.media as MessageMedia.messageMediaPhoto).photo) { // * found this bug on test DC
delete message.media;
}
break;
}
case 'messageMediaPoll': {
const result = appPollsManager.savePoll(message.media.poll, message.media.results, message);
message.media.poll = result.poll;
message.media.results = result.results;
break;
}
case 'messageMediaDocument': {
if(message.media.ttl_seconds) {
unsupported = true;
} else {
const originalDoc = message.media.document;
message.media.document = appDocsManager.saveDoc(originalDoc, mediaContext); // 11.04.2020 warning
if(!message.media.document && originalDoc._ !== 'documentEmpty') {
unsupported = true;
}
}
break;
}
case 'messageMediaWebPage': {
const messageKey = appWebPagesManager.getMessageKeyForPendingWebPage(peerId, mid, options.isScheduled);
message.media.webpage = appWebPagesManager.saveWebPage(message.media.webpage, messageKey, mediaContext);
break;
}
/*case 'messageMediaGame':
AppGamesManager.saveGame(apiMessage.media.game, apiMessage.mid, mediaContext);
apiMessage.media.handleMessage = true;
break; */
case 'messageMediaInvoice': {
unsupported = true;
message.media = {_: 'messageMediaUnsupported'};
break;
}
case 'messageMediaUnsupported': {
unsupported = true;
break;
4 years ago
}
}
if(unsupported) {
message.media = {_: 'messageMediaUnsupported'};
message.message = '';
delete message.entities;
delete message.totalEntities;
}
}
4 years ago
if(!isMessage && message.action) {
const action = message.action as MessageAction;
let migrateFrom: PeerId;
let migrateTo: PeerId;
const suffix = message.fromId === appUsersManager.getSelf().id ? 'You' : '';
3 years ago
if((action as MessageAction.messageActionChatEditPhoto).photo) {
(action as MessageAction.messageActionChatEditPhoto).photo = appPhotosManager.savePhoto((action as MessageAction.messageActionChatEditPhoto).photo, mediaContext);
}
3 years ago
if((action as any).document) {
(action as any).document = appDocsManager.saveDoc((action as any).photo, mediaContext);
}
3 years ago
switch(action._) {
//case 'messageActionChannelEditPhoto':
case 'messageActionChatEditPhoto':
// action.photo = appPhotosManager.savePhoto(action.photo, mediaContext);
if((action.photo as Photo.photo)?.video_sizes) {
// @ts-ignore
action._ = isBroadcast ? 'messageActionChannelEditVideo' : 'messageActionChatEditVideo';
} else {
if(isBroadcast) { // ! messageActionChannelEditPhoto не существует в принципе, это используется для перевода.
3 years ago
// @ts-ignore
action._ = 'messageActionChannelEditPhoto';
4 years ago
}
}
break;
case 'messageActionGroupCall': {
//assumeType<MessageAction.messageActionGroupCall>(action);
2 years ago
appGroupCallsManager.saveGroupCall(action.call);
let type: string;
if(action.duration === undefined) {
type = 'started';
} else {
2 years ago
type = 'ended'
}
if(!isBroadcast) {
type += '_by' + suffix;
}
// @ts-ignore
action.type = type;
break;
}
4 years ago
case 'messageActionChatEditTitle':
/* if(options.isNew) {
const chat = appChatsManager.getChat(peerId.toChatId());
chat.title = action.title;
appChatsManager.saveApiChat(chat, true);
} */
if(isBroadcast) {
// @ts-ignore
action._ = 'messageActionChannelEditTitle';
}
break;
4 years ago
case 'messageActionChatDeletePhoto':
if(isBroadcast) {
// @ts-ignore
action._ = 'messageActionChannelDeletePhoto';
}
break;
4 years ago
case 'messageActionChatAddUser':
if(action.users.length === 1) {
// @ts-ignore
action.user_id = action.users[0];
// @ts-ignore
if(message.fromId === action.user_id) {
if(isChannel) {
// @ts-ignore
action._ = 'messageActionChatJoined' + suffix;
} else {
// @ts-ignore
action._ = 'messageActionChatReturn' + suffix;
4 years ago
}
}
} else if(action.users.length > 1) {
// @ts-ignore
action._ = 'messageActionChatAddUsers';
}
break;
4 years ago
case 'messageActionChatDeleteUser':
if(message.fromId === action.user_id) {
// @ts-ignore
action._ = 'messageActionChatLeave' + suffix;
}
break;
4 years ago
case 'messageActionChannelMigrateFrom':
migrateFrom = action.chat_id.toPeerId(true);
migrateTo = peerId;
break
4 years ago
case 'messageActionChatMigrateTo':
migrateFrom = peerId;
migrateTo = action.channel_id.toPeerId(true);
break;
4 years ago
case 'messageActionHistoryClear':
//apiMessage.deleted = true;
message.clear_history = true;
delete message.pFlags.out;
delete message.pFlags.unread;
break;
4 years ago
case 'messageActionPhoneCall':
// @ts-ignore
action.type =
(action.pFlags.video ? 'video_' : '') +
(action.duration !== undefined ? (message.pFlags.out ? 'out_' : 'in_') : '') +
(
action.duration !== undefined ? 'ok' : (
action.reason._ === 'phoneCallDiscardReasonMissed'
? 'missed'
: 'cancelled'
)
);
break;
}
if(migrateFrom &&
migrateTo &&
!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo]) {
this.migrateChecks(migrateFrom, migrateTo);
}
}
/* if(message.grouped_id) {
if(!groups) {
groups = new Set();
4 years ago
}
groups.add(message.grouped_id);
} else {
message.rReply = this.getRichReplyText(message);
} */
if(isMessage && message.message.length && !message.totalEntities) {
this.wrapMessageEntities(message);
}
storage.set(mid, message);
}
4 years ago
public saveMessages(messages: any[], options: Partial<{
storage: MessagesStorage,
isScheduled: true,
isOutgoing: true,
//isNew: boolean, // * new - from update
}> = {}) {
if((messages as any).saved) return;
(messages as any).saved = true;
messages.forEach((message) => {
this.saveMessage(message, options);
});
}
private wrapMessageEntities(message: Message.message) {
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: MyMessage | MyDraftMessage, text: string, usingMids: number[], plain: true, highlightWord?: string, withoutMediaType?: boolean): string;
public wrapMessageForReply(message: MyMessage | MyDraftMessage, text?: string, usingMids?: number[], plain?: false, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment;
public wrapMessageForReply(message: MyMessage | MyDraftMessage, text: string = (message as Message.message).message, usingMids?: number[], plain?: boolean, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment | string {
const parts: (Node | string)[] = [];
2 years ago
let hasAlbumKey = false;
const addPart = (langKey: LangPackKey, part?: string | HTMLElement) => {
if(langKey) {
2 years ago
if(part === undefined && hasAlbumKey) {
return;
}
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);
}
};
const isRestricted = this.isRestricted(message as any);
2 years ago
let entities = (message as Message.message).totalEntities;
if((message as Message.message).media && !isRestricted) {
3 years ago
assumeType<Message.message>(message);
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) {
2 years ago
const albumText = this.getAlbumText(message.grouped_id);
text = albumText.message;
entities = albumText.totalEntities;
if(!withoutMediaType) {
addPart('AttachAlbum');
2 years ago
hasAlbumKey = true;
}
}
} else {
usingFullAlbum = false;
}
if((!usingFullAlbum && !withoutMediaType) || !text) {
const media = message.media;
switch(media._) {
case 'messageMediaPhoto':
addPart('AttachPhoto');
break;
case 'messageMediaDice':
addPart(undefined, plain ? media.emoticon : RichTextProcessor.wrapEmojiText(media.emoticon));
break;
case 'messageMediaVenue': {
text = media.title;
addPart('AttachLocation');
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': {
2 years ago
const f = '🎮' + ' ' + media.game.title;
addPart(undefined, plain ? f : RichTextProcessor.wrapEmojiText(f));
break;
}
3 years ago
case 'messageMediaDocument': {
const document = media.document as MyDocument;
if(document.type === 'video') {
addPart('AttachVideo');
} else if(document.type === 'voice') {
addPart('AttachAudio');
} else if(document.type === 'gif') {
addPart('AttachGif');
} else if(document.type === 'round') {
addPart('AttachRound');
} else if(document.type === 'sticker') {
3 years ago
if(document.stickerEmojiRaw) {
addPart(undefined, (plain ? document.stickerEmojiRaw : document.stickerEmoji) + ' ');
}
3 years ago
addPart('AttachSticker');
text = '';
} else if(document.type === 'audio') {
const attribute = document.attributes.find(attribute => attribute._ === 'documentAttributeAudio' && (attribute.title || attribute.performer)) as DocumentAttribute.documentAttributeAudio;
const f = '🎵' + ' ' + (attribute ? [attribute.title, attribute.performer].filter(Boolean).join(' - ') : document.file_name);
addPart(undefined, plain ? f : RichTextProcessor.wrapEmojiText(f));
} else {
addPart(undefined, plain ? document.file_name : RichTextProcessor.wrapEmojiText(document.file_name));
}
break;
3 years ago
}
case 'messageMediaUnsupported': {
addPart(UNSUPPORTED_LANG_PACK_KEY);
break;
}
default:
//messageText += media._;
///////this.log.warn('Got unknown media type!', message);
break;
}
}
2 years ago
const length = parts.length;
/* for(let i = 1; i < length; i += 2) {
2 years ago
parts.splice(i, 0, ', ');
} */
2 years ago
if(text && length) {
parts.push(', ');
}
}
3 years ago
if((message as Message.messageService).action) {
const actionWrapped = this.wrapMessageActionTextNew((message as Message.messageService), plain);
if(actionWrapped) {
addPart(undefined, actionWrapped);
}
}
if(isRestricted) {
text = getRestrictionReason((message as Message.message).restriction_reason).text;
entities = [];
}
if(text) {
text = limitSymbols(text, 100);
2 years ago
if(!entities) {
entities = [];
}
if(plain) {
2 years ago
parts.push(RichTextProcessor.wrapPlainText(text, entities));
} else {
2 years ago
// let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '));
if(highlightWord) {
highlightWord = highlightWord.trim();
let found = false;
let match: any;
let regExp = new RegExp(escapeRegExp(highlightWord), 'gi');
entities = entities.slice(); // fix leaving highlight entity
while((match = regExp.exec(text)) !== null) {
entities.push({_: 'messageEntityHighlight', length: highlightWord.length, offset: match.index});
found = true;
}
if(found) {
2 years ago
RichTextProcessor.sortEntities(entities);
}
}
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 wrapSenderToPeer(message: MyMessage) {
const senderTitle: HTMLElement = document.createElement('span');
senderTitle.classList.add('sender-title');
const fromMe = message.fromId === rootScope.myId && message.peerId !== rootScope.myId;
senderTitle.append(
fromMe ?
i18n('FromYou') :
new PeerTitle({
...this.getMessageSenderPeerIdOrName(message),
dialog: message.peerId === rootScope.myId
}).element
);
if(appPeersManager.isAnyGroup(message.peerId) || fromMe) {
const peerTitle = new PeerTitle({peerId: message.peerId}).element;
senderTitle.append(' ➝ ', peerTitle);
}
return senderTitle;
}
public getMessageSenderPeerIdOrName(message: MyMessage) {
if(message.fromId) {
return {
peerId: message.fromId
};
} else {
return {
fromName: (message as Message.message).fwd_from?.from_name
};
}
}
public wrapSentTime(message: MyMessage) {
const el: HTMLElement = document.createElement('span');
el.classList.add('sent-time');
el.append(formatDateAccordingToTodayNew(new Date(message.date * 1000)));
return el;
}
private wrapJoinVoiceChatAnchor(message: Message.messageService) {
const action = message.action as MessageAction.messageActionInviteToGroupCall;
const {onclick, url} = RichTextProcessor.wrapUrl(`tg://voicechat?chat_id=${message.peerId.toChatId()}&id=${action.call.id}&access_hash=${action.call.access_hash}`);
if(!onclick) {
return document.createElement('span');
}
const a = document.createElement('a');
a.href = url;
a.setAttribute('onclick', onclick + '(this)');
return a;
}
private wrapMessageActionTextNewUnsafe(message: MyMessage, plain?: boolean) {
3 years ago
const element: HTMLElement = plain ? undefined : document.createElement('span');
const action = 'action' in message && message.action;
3 years ago
// this.log('message action:', action);
3 years ago
if((action as MessageAction.messageActionCustomAction).message) {
const unsafeMessage = (action as MessageAction.messageActionCustomAction).message;
3 years ago
if(plain) {
return RichTextProcessor.wrapPlainText(unsafeMessage);
3 years ago
} else {
element.innerHTML = RichTextProcessor.wrapRichText(unsafeMessage, {noLinebreaks: true});
3 years ago
return element;
}
} else {
let _ = action._;
//let suffix = '';
let langPackKey: LangPackKey;
3 years ago
let args: any[];
3 years ago
const getNameDivHTML = (peerId: PeerId, plain: boolean) => {
2 years ago
return plain ? appPeersManager.getPeerTitle(peerId, plain) : (new PeerTitle({peerId})).element;
3 years ago
};
switch(action._) {
case 'messageActionPhoneCall': {
3 years ago
_ += '.' + (action as any).type;
2 years ago
args = [formatCallDuration(action.duration, plain)];
break;
}
case 'messageActionGroupCall': {
_ += '.' + (action as any).type;
args = [];
2 years ago
if(!_.endsWith('You') && !message.pFlags.post) {
args.push(getNameDivHTML(message.fromId, plain));
}
2 years ago
if(action.duration !== undefined) {
args.push(formatCallDuration(action.duration, plain));
} else {
args.push(this.wrapJoinVoiceChatAnchor(message as any));
2 years ago
}
break;
}
case 'messageActionInviteToGroupCall': {
const peerIds = [message.fromId, action.users[0].toPeerId()];
let a = 'Chat.Service.VoiceChatInvitation';
const myId = appUsersManager.getSelf().id;
if(peerIds[0] === myId) a += 'ByYou';
else if(peerIds[1] === myId) a += 'ForYou';
3 years ago
indexOfAndSplice(peerIds, myId);
langPackKey = a as LangPackKey;
args = peerIds.map(peerId => getNameDivHTML(peerId, plain));
args.push(this.wrapJoinVoiceChatAnchor(message as any));
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: FormatterArguments = [];
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;
}
3 years ago
case 'messageActionPinMessage': {
const peerId = message.peerId;
const pinnedMessage = this.getMessageByPeer(peerId, message.reply_to_mid);
3 years ago
args = [
getNameDivHTML(message.fromId, plain),
];
if(pinnedMessage.deleted/* || true */) {
3 years ago
langPackKey = 'ActionPinnedNoText';
if(message.reply_to_mid) { // refresh original message
this.fetchMessageReplyTo(message).then(originalMessage => {
if(!originalMessage.deleted && !message.deleted) {
rootScope.dispatchEvent('message_edit', {
storage: this.getMessagesStorage(peerId),
peerId: peerId,
mid: message.mid
});
if(this.isMessageIsTopMessage(message)) {
rootScope.dispatchEvent('dialogs_multiupdate', {
[peerId]: this.getDialogOnly(peerId)
});
}
}
});
}
3 years ago
} else {
const a = document.createElement('i');
3 years ago
a.dataset.savedFrom = pinnedMessage.peerId + '_' + pinnedMessage.mid;
a.dir = 'auto';
3 years ago
a.append(this.wrapMessageForReply(pinnedMessage, undefined, undefined, plain as any));
args.push(a);
}
break;
}
case 'messageActionChatJoinedByRequest': {
const isBroadcast = appPeersManager.isBroadcast(message.peerId);
if(message.pFlags.out) {
langPackKey = isBroadcast ? 'RequestToJoinChannelApproved' : 'RequestToJoinGroupApproved';
} else {
langPackKey = isBroadcast ? 'ChatService.UserJoinedChannelByRequest' : 'ChatService.UserJoinedGroupByRequest';
args = [getNameDivHTML(message.fromId, plain)];
}
break;
}
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': {
3 years ago
const users = (action as MessageAction.messageActionChatAddUser).users
3 years ago
|| [(action as MessageAction.messageActionChatDeleteUser).user_id];
args = [getNameDivHTML(message.fromId, plain)];
if(users.length > 1) {
2 years ago
const joined = join(
users.map((userId: UserId) => getNameDivHTML(userId.toPeerId(), plain)),
2 years ago
false,
plain
);
if(plain) {
2 years ago
args.push(...joined);
} else {
const fragment = document.createElement('span');
2 years ago
fragment.append(...joined);
args.push(fragment);
}
} else {
3 years ago
args.push(getNameDivHTML(users[0].toPeerId(), 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 ? ' ' : '');
}
}
public wrapMessageActionTextNew(message: MyMessage, plain: true): string;
public wrapMessageActionTextNew(message: MyMessage, plain?: false): HTMLElement;
public wrapMessageActionTextNew(message: MyMessage, plain: boolean): HTMLElement | string;
public wrapMessageActionTextNew(message: MyMessage, plain?: boolean): HTMLElement | string {
try {
return this.wrapMessageActionTextNewUnsafe(message, plain);
} catch(err) {
this.log.error('wrapMessageActionTextNewUnsafe error:', err);
return plain ? '' : document.createElement('span');
}
}
3 years ago
public reportMessages(peerId: PeerId, mids: number[], reason: ReportReason['_'], message?: string) {
3 years ago
return apiManager.invokeApiSingle('messages.report', {
peer: appPeersManager.getInputPeerById(peerId),
id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid)),
reason: {
_: reason
},
message
});
}
public startBot(botId: BotId, chatId?: ChatId, startParam?: string) {
3 years ago
const peerId = chatId ? chatId.toPeerId(true) : botId.toPeerId();
3 years ago
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);
}
3 years ago
public editPeerFolders(peerIds: PeerId[], 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, и вызывать апдейт для каждого канала отдельно
});
}
3 years ago
public toggleDialogPin(peerId: PeerId, 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
});
}
});
}
3 years ago
public markDialogUnread(peerId: PeerId, 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
});
}
});
}
3 years ago
public migrateChecks(migrateFrom: PeerId, migrateTo: PeerId) {
4 years ago
if(!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo] &&
3 years ago
appChatsManager.hasChat(migrateTo.toChatId())) {
const fromChat = appChatsManager.getChat(migrateFrom.toChatId());
4 years ago
if(fromChat &&
fromChat.migrated_to &&
3 years ago
fromChat.migrated_to.channel_id === migrateTo.toChatId()) {
4 years ago
this.migratedFromTo[migrateFrom] = migrateTo;
this.migratedToFrom[migrateTo] = migrateFrom;
//setTimeout(() => {
rootScope.dispatchEvent('dialog_migrate', {migrateFrom, migrateTo});
3 years ago
this.dialogsStorage.dropDialogWithEvent(migrateFrom);
//}, 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: Message.message | Message.messageService, 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.pFlags.out || (
2 years ago
message.peer_id._ !== 'peerChannel' &&
message.date < (tsNow(true) - rootScope.config.edit_time_limit) &&
(message as Message.message).media?._ !== 'messageMediaPoll'
)
) {
return false;
}
return true;
}
3 years ago
public canDeleteMessage(message: MyMessage) {
return message && (
3 years ago
message.peerId.isUser()
|| message.pFlags.out
3 years ago
|| appChatsManager.getChat(message.peerId.toChatId())._ === 'chat'
|| appChatsManager.hasRights(message.peerId.toChatId(), 'delete_messages')
) && !message.pFlags.is_outgoing;
}
3 years ago
public getReplyKeyboard(peerId: PeerId) {
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;
}
3 years ago
public getSearchStorage(peerId: PeerId, 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: PeerId, filters: MessagesFilter[], canCache = true): Promise<MessagesSearchCounter[]> {
if(appPeersManager.isRestricted(peerId)) {
return Promise.resolve(filters.map((filter) => {
return {
_: 'messages.searchCounter',
pFlags: {},
filter: filter,
count: 0
};
}));
}
const func = (canCache ? apiManager.invokeApiCacheable : apiManager.invokeApi).bind(apiManager);
return func('messages.getSearchCounters', {
peer: appPeersManager.getInputPeerById(peerId),
filters
});
}
public filterMessagesByInputFilter(inputFilter: MyInputMessagesFilter, history: number[], storage: MessagesStorage, limit: number) {
const foundMsgs: MyMessage[] = [];
if(!history.length) {
return foundMsgs;
}
let filtering = true;
const neededContents: Partial<{
[messageMediaType in MessageMedia['_']]: boolean
}> & Partial<{
avatar: boolean,
url: boolean
}> = {},
neededDocTypes: MyDocument['type'][] = [],
excludeDocTypes: MyDocument['type'][] = []/* ,
neededFlags: string[] = [] */;
switch(inputFilter) {
case 'inputMessagesFilterPhotos':
neededContents['messageMediaPhoto'] = true;
break;
case 'inputMessagesFilterPhotoVideo':
neededContents['messageMediaPhoto'] = true;
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('video');
break;
case 'inputMessagesFilterVideo':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('video');
break;
case 'inputMessagesFilterDocument':
neededContents['messageMediaDocument'] = true;
excludeDocTypes.push('video');
break;
case 'inputMessagesFilterVoice':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('voice');
break;
case 'inputMessagesFilterRoundVoice':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('round', 'voice');
break;
case 'inputMessagesFilterRoundVideo':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('round');
break;
case 'inputMessagesFilterMusic':
neededContents['messageMediaDocument'] = true;
neededDocTypes.push('audio');
break;
case 'inputMessagesFilterUrl':
neededContents['url'] = true;
break;
case 'inputMessagesFilterChatPhotos':
neededContents['avatar'] = true;
break;
/* case 'inputMessagesFilterPinned':
neededFlags.push('pinned');
break; */
/* case 'inputMessagesFilterMyMentions':
neededContents['mentioned'] = true;
break; */
default:
filtering = false;
break;
/* return Promise.resolve({
count: 0,
next_rate: 0,
history: [] as number[]
}); */
}
if(!filtering) {
return foundMsgs;
}
for(let i = 0, length = history.length; i < length; ++i) {
const message: Message.message | Message.messageService = storage.get(history[i]);
if(!message) continue;
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ === 'messageEntityMention'));
let found = false;
if(message._ === 'message') {
if(message.media && neededContents[message.media._]/* && !message.fwd_from */) {
const doc = (message.media as MessageMedia.messageMediaDocument).document as MyDocument;
if(doc && ((neededDocTypes.length && !neededDocTypes.includes(doc.type))
|| excludeDocTypes.includes(doc.type))) {
continue;
}
found = true;
} else if(neededContents['url'] && message.message) {
const goodEntities = ['messageEntityTextUrl', 'messageEntityUrl'];
if((message.totalEntities as MessageEntity[]).find(e => goodEntities.includes(e._)) || RichTextProcessor.matchUrl(message.message)) {
found = true;
}
}
} else if(neededContents['avatar'] &&
message.action &&
([
'messageActionChannelEditPhoto' as const,
'messageActionChatEditPhoto' as const,
'messageActionChannelEditVideo' as const,
'messageActionChatEditVideo' as const
] as MessageAction['_'][]).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;
}
}
}
return foundMsgs;
}
public getSearch({peerId, query, inputFilter, maxId, limit, nextRate, backLimit, threadId, folderId, minDate, maxDate}: {
3 years ago
peerId?: PeerId,
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(appPeersManager.isRestricted(peerId)) {
return Promise.resolve({
count: 0,
offset_id_offset: 0,
next_rate: undefined,
history: []
});
}
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;
let foundMsgs: MyMessage[] = [];
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);
foundMsgs = this.filterMessagesByInputFilter(inputFilter._, storage.history.slice, this.getMessagesStorage(peerId), limit);
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,
3 years ago
hash: '',
3 years ago
top_msg_id: appMessagesIdsManager.getServerMessageId(threadId) || 0
4 years ago
}, {
//timeout: APITIMEOUT,
4 years ago
noErrorBox: true
});
} else {
//var offsetDate = 0;
3 years ago
let offsetPeerId: PeerId;
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: MyMessage) => {
const peerId = this.getMessagePeer(message);
3 years ago
if(peerId.isAnyChat()) {
const chat: Chat.chat = appChatsManager.getChat(peerId.toChatId());
4 years ago
if(chat.migrated_to) {
3 years ago
this.migrateChecks(peerId, (chat.migrated_to as InputChannel.inputChannel).channel_id.toPeerId(true));
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
});
}
3 years ago
public subscribeRepliesThread(peerId: PeerId, 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 | Message.messageService) {
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: NULL_PEER_ID}/* 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;
}
3 years ago
public getDiscussionMessage(peerId: PeerId, 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);
2 years ago
const message = this.filterMessages(result.messages[0] as Message.message, 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;
});
}
3 years ago
private handleNewMessage(peerId: PeerId, 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);
}
}
3 years ago
private handleNewMessages = () => {
clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandleTimeout = 0;
4 years ago
rootScope.dispatchEvent('history_multiappend', this.newMessagesToHandle);
4 years ago
this.newMessagesToHandle = {};
};
4 years ago
3 years ago
private handleNewDialogs = () => {
let newMaxSeenId = 0;
const obj = this.newDialogsToHandle;
for(const peerId in obj) {
const dialog = obj[peerId];
if(!dialog) {
3 years ago
this.reloadConversation(peerId.toPeerId());
delete obj[peerId];
4 years ago
} else {
this.dialogsStorage.pushDialog(dialog);
3 years ago
if(!appPeersManager.isChannel(peerId.toPeerId())) {
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
3 years ago
public scheduleHandleNewDialogs(peerId?: PeerId, dialog?: Dialog) {
if(peerId !== undefined) {
this.newDialogsToHandle[peerId] = dialog;
}
if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise;
3 years ago
return this.newDialogsHandlePromise = new Promise<void>((resolve) => {
setTimeout(() => {
3 years ago
resolve();
this.newDialogsHandlePromise = undefined;
this.handleNewDialogs();
}, 0);
});
}
3 years ago
public deleteMessages(peerId: PeerId, mids: number[], revoke?: boolean) {
let promise: Promise<any>;
3 years ago
const localMessageIds = mids.map(mid => appMessagesIdsManager.getServerMessageId(mid));
3 years ago
if(peerId.isAnyChat() && appPeersManager.isChannel(peerId)) {
const channelId = peerId.toChatId();
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;
}
3 years ago
public readHistory(peerId: PeerId, 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',
3 years ago
channel_id: peerId.toChatId(),
top_msg_id: threadId,
read_max_id: maxId
});
3 years ago
} else if(appPeersManager.isChannel(peerId)) {
if(!historyStorage.readPromise) {
apiPromise = apiManager.invokeApi('channels.readHistory', {
3 years ago
channel: appChatsManager.getChannelInput(peerId.toChatId()),
3 years ago
max_id: appMessagesIdsManager.getServerMessageId(maxId)
});
}
apiUpdatesManager.processLocalUpdate({
_: 'updateReadChannelInbox',
max_id: maxId,
3 years ago
channel_id: peerId.toChatId(),
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;
const {readMaxId} = historyStorage;
this.log('readHistory: promise finally', maxId, readMaxId);
if(readMaxId > maxId) {
this.readHistory(peerId, readMaxId, threadId, true);
}
});
4 years ago
return historyStorage.readPromise = apiPromise;
4 years ago
}
3 years ago
public readAllHistory(peerId: PeerId, 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 fixDialogUnreadMentionsIfNoMessage(peerId: PeerId) {
const dialog = this.getDialogOnly(peerId);
if(dialog?.unread_mentions_count) {
this.reloadConversation(peerId);
}
}
public modifyCachedMentions(peerId: PeerId, mid: number, add: boolean) {
3 years ago
const slicedArray = this.unreadMentions[peerId];
if(!slicedArray) return;
if(add) {
if(slicedArray.first.isEnd(SliceEnd.Top)) {
slicedArray.insertSlice([mid]);
}
} else {
slicedArray.delete(mid);
}
}
private fixUnreadMentionsCountIfNeeded(peerId: PeerId, slicedArray: SlicedArray) {
const dialog = this.getDialogOnly(peerId);
if(!slicedArray.length && dialog?.unread_mentions_count) {
this.reloadConversation(peerId);
}
}
3 years ago
public goToNextMention(peerId: PeerId) {
3 years ago
/* 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) {
this.fixUnreadMentionsCountIfNeeded(peerId, slicedArray);
3 years ago
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});
} else {
this.fixUnreadMentionsCountIfNeeded(peerId, slicedArray);
3 years ago
}
}).finally(() => {
delete this.goToNextMentionPromises[peerId];
});
}
3 years ago
public loadNextMentions(peerId: PeerId) {
3 years ago
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);
});
}
3 years ago
public getUnreadMentions(peerId: PeerId, offsetId: number, add_offset: number, limit: number, maxId = 0, minId = 0) {
3 years ago
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;
});
}
3 years ago
public readMessages(peerId: PeerId, 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;
3 years ago
if(peerId.isAnyChat() && appPeersManager.isChannel(peerId)) {
const channelId = peerId.toChatId();
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
}
3 years ago
public getHistoryStorage(peerId: PeerId, 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 getNotifyPeerSettings(peerId: PeerId) {
return Promise.all([
appNotificationsManager.getNotifyPeerTypeSettings(),
appNotificationsManager.getNotifySettings(appPeersManager.getInputNotifyPeerById(peerId, true))
]).then(([_, peerTypeNotifySettings]) => {
return {
muted: appNotificationsManager.isPeerLocalMuted(peerId, true),
peerTypeNotifySettings
};
});
}
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) {
3 years ago
const peerId = _peerId.toPeerId();
if(rootScope.peerId === peerId && !rootScope.idle.isIDLE) {
continue;
}
const notifyPeerToHandle = this.notificationsToHandle[peerId];
this.getNotifyPeerSettings(peerId).then(({muted, peerTypeNotifySettings}) => {
const topMessage = notifyPeerToHandle.topMessage;
if(muted || !topMessage.pFlags.unread) {
return;
}
//setTimeout(() => {
if(topMessage.pFlags.unread) {
this.notifyAboutMessage(topMessage, {
fwdCount: notifyPeerToHandle.fwdCount,
peerTypeNotifySettings
});
}
//}, timeout);
});
}
this.notificationsToHandle = {};
};
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;
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);
});
3 years ago
this.finalizePendingMessageCallbacks(storage, tempId, message);
} 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: new Map()});
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;
3 years ago
if(peerId.isAnyChat()) {
good = appChatsManager.isInChat(peerId.toChatId());
}
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;
}
(update as any).ignoreExisting = true;
set.add(update);
this.scheduleHandleNewDialogs(peerId);
}
return;
}
4 years ago
/* if(update._ === 'updateNewChannelMessage') {
3 years ago
const chat = appChatsManager.getChat(peerId.toChatId());
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
// * so message can exist if reloadConversation came back earlier with mid
const ignoreExisting: boolean = (update as any).ignoreExisting;
const isExisting = !!historyStorage.history.findSlice(message.mid);
if(isExisting) {
if(!ignoreExisting) {
return false;
}
} else {
// * 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;
3 years ago
if(fromId.isUser() && !message.pFlags.out && message.from_id) {
appUsersManager.forceUserOnline(fromId, message.date);
const action: SendMessageAction = {
_: 'sendMessageCancelAction'
};
let update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping;
3 years ago
if(peerId.isUser()) {
update = {
_: 'updateUserTyping',
action,
user_id: fromId
};
} else if(appPeersManager.isChannel(peerId)) {
update = {
_: 'updateChannelUserTyping',
action,
3 years ago
channel_id: peerId.toChatId(),
from_id: appPeersManager.getOutputPeer(fromId),
3 years ago
top_msg_id: threadId ? appMessagesIdsManager.getServerMessageId(threadId) : undefined
};
} else {
update = {
_: 'updateChatUserTyping',
action,
3 years ago
chat_id: peerId.toChatId(),
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 && message.mid > dialog.top_message) {
3 years ago
const releaseUnreadCount = this.dialogsStorage.prepareDialogUnreadCountModifying(dialog);
3 years ago
3 years ago
++dialog.unread_count;
3 years ago
if(message.pFlags.mentioned) {
++dialog.unread_mentions_count;
3 years ago
this.modifyCachedMentions(peerId, message.mid, true);
3 years ago
}
3 years ago
releaseUnreadCount();
}
3 years ago
if(message.mid >= dialog.top_message) {
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,
3 years ago
fromId: NULL_PEER_ID
};
}
if(notifyPeerToHandle.fromId !== fromId) {
notifyPeerToHandle.fromId = fromId;
notifyPeerToHandle.fwdCount = 0;
}
if((message as Message.message).fwd_from) {
3 years ago
++notifyPeerToHandle.fwdCount;
}
notifyPeerToHandle.topMessage = message;
if(!this.notificationsHandlePromise) {
this.notificationsHandlePromise = window.setTimeout(this.handleNotifications, 0);
}
}
};
private onUpdateMessageReactions = (update: Update.updateMessageReactions) => {
const {peer, msg_id, reactions} = update;
const mid = appMessagesIdsManager.generateMessageId(msg_id);
const peerId = appPeersManager.getPeerId(peer);
const message: MyMessage = this.getMessageByPeer(peerId, mid);
if(message._ !== 'message') {
return;
}
2 years ago
const recentReactions = reactions?.recent_reactions;
if(recentReactions?.length && message.pFlags.out) {
const recentReaction = recentReactions[recentReactions.length - 1];
const previousReactions = message.reactions;
2 years ago
const previousRecentReactions = previousReactions?.recent_reactions;
if(
appPeersManager.getPeerId(recentReaction.peer_id) !== rootScope.myId && (
!previousRecentReactions ||
previousRecentReactions.length <= recentReactions.length
) && (
!previousRecentReactions ||
!deepEqual(recentReaction, previousRecentReactions[previousRecentReactions.length - 1])
)
) {
this.getNotifyPeerSettings(peerId).then(({muted, peerTypeNotifySettings}) => {
if(muted || !peerTypeNotifySettings.show_previews) return;
this.notifyAboutMessage(message, {
userReaction: recentReaction,
peerTypeNotifySettings
});
});
}
}
message.reactions = reactions;
const key = message.peerId + '_' + message.mid;
this.pushBatchUpdate('messages_reactions', this.batchUpdateReactions, key, () => copy(message.reactions));
2 years ago
if(!update.local) {
this.setDialogToStateIfMessageIsTop(message);
}
};
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 {
3 years ago
const releaseUnreadCount = this.dialogsStorage.prepareDialogUnreadCountModifying(dialog);
if(!update.pFlags.unread) {
delete dialog.pFlags.unread_mark;
} else {
dialog.pFlags.unread_mark = true;
4 years ago
}
3 years ago
releaseUnreadCount();
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.has(mid)) {
3 years ago
// this.fixDialogUnreadMentionsIfNoMessage(peerId);
return;
}
// console.trace(dT(), 'edit message', message)
3 years ago
const oldMessage: Message = this.getMessageFromStorage(storage, mid);
this.saveMessages([message], {storage});
3 years ago
const newMessage: Message = this.getMessageFromStorage(storage, mid);
4 years ago
this.handleEditedMessage(oldMessage, newMessage);
4 years ago
const dialog = this.getDialogOnly(peerId);
3 years ago
// if sender erased mention
/* if(dialog.unread_mentions_count && (oldMessage as Message.message)?.pFlags?.mentioned && !message.pFlags.mentioned) {
--dialog.unread_mentions_count;
this.modifyCachedMentions(peerId, mid, false);
} */
const isTopMessage = dialog && dialog.top_message === mid;
if((message as Message.messageService).clear_history) {
if(isTopMessage) {
rootScope.dispatchEvent('dialog_flush', {peerId});
4 years ago
}
} else {
// no sense in dispatching message_edit since only reactions have changed
if(oldMessage?._ === 'message' && !deepEqual(oldMessage.reactions, (newMessage as Message.message).reactions)) {
const newReactions = (newMessage as Message.message).reactions;
(newMessage as Message.message).reactions = oldMessage.reactions;
apiUpdatesManager.processLocalUpdate({
_: 'updateMessageReactions',
peer: appPeersManager.getOutputPeer(peerId),
msg_id: message.id,
reactions: newReactions
});
return;
}
rootScope.dispatchEvent('message_edit', {
storage,
peerId,
mid
});
4 years ago
if(isTopMessage || (message as Message.message).grouped_id) {
3 years ago
const updatedDialogs: {[peerId: PeerId]: 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);
3 years ago
const peerId = channelId ? channelId.toPeerId(true) : 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
3 years ago
if(peerId.isUser() && isOut) {
appUsersManager.forceUserOnline(peerId);
}
if(threadId) {
const repliesKey = this.threadsToReplies[peerId + '_' + threadId];
if(repliesKey) {
3 years ago
const [peerId, mid] = repliesKey.split('_');
this.updateMessage(peerId.toPeerId(), +mid, 'replies_updated');
}
}
4 years ago
3 years ago
const releaseUnreadCount = !threadId && foundDialog && this.dialogsStorage.prepareDialogUnreadCountModifying(foundDialog);
for(let i = 0, length = history.length; i < length; i++) {
const mid = history[i];
if(mid > maxId) {
continue;
}
const message: MyMessage = storage.get(mid);
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' + mid);
}
}
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
let setCount: number;
3 years ago
if(stillUnreadCount !== undefined) {
3 years ago
setCount = stillUnreadCount;
3 years ago
} else if(newUnreadCount < 0 || !this.getReadMaxIdIfUnread(peerId)) {
3 years ago
setCount = 0;
} else if(newUnreadCount && foundDialog.top_message > maxId) {
3 years ago
setCount = newUnreadCount;
}
if(setCount !== undefined) {
foundDialog.unread_count = setCount;
4 years ago
}
3 years ago
if(newUnreadMentionsCount < 0 || !foundDialog.unread_count) {
3 years ago
foundDialog.unread_mentions_count = 0;
}
4 years ago
}
3 years ago
if(releaseUnreadCount) {
releaseUnreadCount();
}
this.dialogsStorage.processDialogForFilters(foundDialog);
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) {
3 years ago
const [peerId, mid] = this.threadsToReplies[threadKey].split('_');
rootScope.dispatchEvent('replies_updated', this.getMessageByPeer(peerId.toPeerId(), +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));
3 years ago
const peerId = channelId ? channelId.toPeerId(true) : 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) {
if(message.pFlags.media_unread) {
delete message.pFlags.media_unread;
this.setDialogToStateIfMessageIsTop(message);
if(!message.pFlags.out && message.pFlags.mentioned) {
this.modifyCachedMentions(peerId, mid, false);
}
3 years ago
}
3 years ago
} else {
this.fixDialogUnreadMentionsIfNoMessage(peerId);
}
}
rootScope.dispatchEvent('messages_media_read', {peerId, mids});
};
private onUpdateChannelAvailableMessages = (update: Update.updateChannelAvailableMessages) => {
3 years ago
const peerId = update.channel_id.toPeerId(true);
const history = this.getHistoryStorage(peerId).history.slice;
3 years ago
const availableMinId = appMessagesIdsManager.generateMessageId(update.available_min_id);
const messages = history.filter(mid => mid <= availableMinId);
(update as any as Update.updateDeleteChannelMessages).messages = messages;
this.onUpdateDeleteMessages(update as any as Update.updateDeleteChannelMessages);
};
private onUpdateDeleteMessages = (update: Update.updateDeleteMessages | Update.updateDeleteChannelMessages) => {
3 years ago
const channelId = (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));
3 years ago
const peerId: PeerId = channelId ? channelId.toPeerId(true) : 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 => {
3 years ago
const [peerId, mid] = threadKey.split('_');
return this.getHistoryStorage(peerId.toPeerId(), +mid);
});
4 years ago
3 years ago
const historyStorage = this.getHistoryStorage(peerId);
[historyStorage].concat(threadsStorages).forEach(historyStorage => {
for(const mid of historyUpdated.msgs) {
historyStorage.history.delete(mid);
}
3 years ago
if(historyUpdated.count && historyStorage.count) {
historyStorage.count = Math.max(0, historyStorage.count - historyUpdated.count);
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
const affected = historyUpdated.unreadMentions || historyUpdated.unread;
const releaseUnreadCount = affected && this.dialogsStorage.prepareDialogUnreadCountModifying(foundDialog);
if(historyUpdated.unread) {
3 years ago
foundDialog.unread_count = Math.max(0, foundDialog.unread_count - historyUpdated.unread);
3 years ago
}
if(historyUpdated.unreadMentions) {
foundDialog.unread_mentions_count = !foundDialog.unread_count ? 0 : Math.max(0, foundDialog.unread_mentions_count - historyUpdated.unreadMentions);
}
3 years ago
if(affected) {
releaseUnreadCount();
rootScope.dispatchEvent('dialog_unread', {peerId});
}
3 years ago
if(historyUpdated.msgs.has(foundDialog.top_message)) {
3 years ago
const slice = historyStorage.history.first;
if(slice.isEnd(SliceEnd.Bottom) && slice.length) {
const mid = slice[0];
const message = this.getMessageByPeer(peerId, mid);
this.setDialogTopMessage(message, foundDialog);
} else {
this.reloadConversation(peerId);
}
4 years ago
}
}
};
4 years ago
private onUpdateChannel = (update: Update.updateChannel) => {
3 years ago
const channelId = update.channel_id;
const peerId = channelId.toPeerId(true);
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) {
3 years ago
this.reloadConversation(peerId);
} else {
3 years ago
this.dialogsStorage.dropDialogOnDeletion(peerId);
4 years ago
}
}
rootScope.dispatchEvent('channel_update', channelId);
};
private onUpdateChannelReload = (update: Update.updateChannelReload) => {
3 years ago
const peerId = update.channel_id.toPeerId(true);
this.dialogsStorage.dropDialog(peerId);
delete this.historiesStorage[peerId];
3 years ago
this.reloadConversation(peerId).then(() => {
rootScope.dispatchEvent('history_reload', peerId);
});
};
private onUpdateChannelMessageViews = (update: Update.updateChannelMessageViews) => {
const views = update.views;
3 years ago
const peerId = update.channel_id.toPeerId(true);
3 years ago
const mid = appMessagesIdsManager.generateMessageId(update.id);
const message: Message.message = this.getMessageByPeer(peerId, mid);
3 years ago
if(!message.deleted && message.views !== undefined && message.views < views) {
message.views = views;
this.pushBatchUpdate('messages_views', this.batchUpdateViews, message.peerId + '_' + message.mid);
3 years ago
this.setDialogToStateIfMessageIsTop(message);
}
};
private onUpdateServiceNotification = (update: Update.updateServiceNotification) => {
//this.log('updateServiceNotification', update);
3 years ago
const fromId = SERVICE_PEER_ID;
const peerId = fromId;
const messageId = this.generateTempMessageId(peerId);
3 years ago
const message: Message.message = {
_: '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},
3 years ago
access_hash: '0',
first_name: 'Telegram',
phone: '42777'
}]);
}
this.saveMessages([message], {isOutgoing: true});
if(update.inbox_date) {
this.pendingTopMsgs[peerId] = messageId;
this.onUpdateNewMessage({
_: 'updateNewMessage',
3 years ago
message,
pts: undefined,
pts_count: undefined
});
}
};
private onUpdatePinnedMessages = (update: Update.updatePinnedMessages | Update.updatePinnedChannelMessages) => {
const channelId = update._ === 'updatePinnedChannelMessages' ? update.channel_id : undefined;
3 years ago
const peerId = channelId ? channelId.toPeerId(true) : 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.has(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.get(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.get(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});
}
};
3 years ago
public setDialogToStateIfMessageIsTop(message: MyMessage) {
if(this.isMessageIsTopMessage(message)) {
this.dialogsStorage.setDialogToState(this.getDialogOnly(message.peerId));
}
}
public isMessageIsTopMessage(message: MyMessage) {
const dialog = this.getDialogOnly(message.peerId);
return dialog && dialog.top_message === message.mid;
}
private updateMessageRepliesIfNeeded(threadMessage: MyMessage) {
try { // * на всякий случай, скорее всего это не понадобится
const threadKey = this.getThreadKey(threadMessage);
if(threadKey) {
const repliesKey = this.threadsToReplies[threadKey];
if(repliesKey) {
3 years ago
const [peerId, mid] = repliesKey.split('_');
3 years ago
this.updateMessage(peerId.toPeerId(), +mid, 'replies_updated');
}
}
} catch(err) {
this.log.error('incrementMessageReplies err', err, threadMessage);
}
}
private getThreadKey(threadMessage: MyMessage) {
let threadKey = '';
3 years ago
if(threadMessage.peerId?.isAnyChat() && 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;
}
3 years ago
public updateMessage(peerId: PeerId, 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;
}
3 years ago
private checkPendingMessage(message: MyMessage) {
const randomId = this.pendingByMessageId[message.mid];
3 years ago
let pendingMessage: ReturnType<AppMessagesManager['finalizePendingMessage']>;
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: PeerId, muteUntil: number) {
const settings: InputPeerNotifySettings = {
_: 'inputPeerNotifySettings'
};
settings.mute_until = muteUntil;
return appNotificationsManager.updateNotifySettings({
_: 'inputNotifyPeer',
peer: appPeersManager.getInputPeerById(peerId)
}, settings);
}
public togglePeerMute(peerId: PeerId, mute?: boolean) {
if(mute === undefined) {
mute = !appNotificationsManager.isPeerLocalMuted(peerId, false);
}
return this.mutePeer(peerId, mute ? MUTE_UNTIL : 0);
}
3 years ago
public canSendToPeer(peerId: PeerId, threadId?: number, action: ChatRights = 'send_messages') {
if(appPeersManager.isRestricted(peerId)) {
return false;
}
3 years ago
if(peerId.isAnyChat()) {
//const isChannel = appPeersManager.isChannel(peerId);
3 years ago
const chat: Chat.chat = appChatsManager.getChat(peerId.toChatId());
const hasRights = /* isChannel && */appChatsManager.hasRights(peerId.toChatId(), action, undefined, !!threadId);
return /* !isChannel || */hasRights && (!chat.pFlags.left || !!threadId);
} else {
return appUsersManager.canSendToUser(peerId);
}
4 years ago
}
3 years ago
public finalizePendingMessage(randomId: Long, finalMessage: MyMessage) {
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
3 years ago
const tempMessage: MyMessage = this.getMessageFromStorage(storage, tempId);
if(!tempMessage.deleted) {
delete finalMessage.pFlags.is_outgoing;
delete finalMessage.pending;
delete finalMessage.error;
delete finalMessage.random_id;
delete finalMessage.send;
4 years ago
}
3 years ago
rootScope.dispatchEvent('messages_pending');
delete this.pendingByRandomId[randomId];
4 years ago
3 years ago
this.finalizePendingMessageCallbacks(storage, tempId, finalMessage);
4 years ago
3 years ago
return tempMessage;
4 years ago
}
}
3 years ago
public finalizePendingMessageCallbacks(storage: MessagesStorage, tempId: number, message: MyMessage) {
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
3 years ago
if((message as Message.message).media) {
assumeType<Message.message>(message);
const {photo: newPhoto, document: newDoc} = message.media as any;
if(newPhoto) {
const photo = appPhotosManager.getPhoto('' + tempId);
if(/* photo._ !== 'photoEmpty' */photo) {
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(newDoc) {
const doc = appDocsManager.getDoc('' + tempId);
if(doc) {
if(/* doc._ !== 'documentEmpty' && */doc.type && doc.type !== 'sticker' && doc.mime_type !== 'image/gif') {
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 as MessageMedia.messageMediaPoll).poll) {
delete appPollsManager.polls[tempId];
delete appPollsManager.results[tempId];
}
}
const tempMessage = this.getMessageFromStorage(storage, tempId);
storage.delete(tempId);
3 years ago
this.handleReleasingMessage(tempMessage, storage);
3 years ago
rootScope.dispatchEvent('message_sent', {storage, tempId, tempMessage, mid: message.mid, message});
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 getMessageReactionsListAndReadParticipants(
message: Message.message,
limit?: number,
reaction?: string,
2 years ago
offset?: string,
skipReadParticipants?: boolean,
skipReactionsList?: boolean
) {
const emptyMessageReactionsList = {
reactions: [] as MessagePeerReaction[],
count: 0,
next_offset: undefined as string
};
const canViewMessageReadParticipants = this.canViewMessageReadParticipants(message);
if(canViewMessageReadParticipants && limit === undefined) {
limit = 100;
} else if(limit === undefined) {
limit = 50;
}
return Promise.all([
2 years ago
canViewMessageReadParticipants && !reaction && !skipReadParticipants ? this.getMessageReadParticipants(message.peerId, message.mid).catch(() => [] as UserId[]) : [] as UserId[],
2 years ago
message.reactions?.recent_reactions?.length && !skipReactionsList ? appReactionsManager.getMessageReactionsList(message.peerId, message.mid, limit, reaction, offset).catch(err => emptyMessageReactionsList) : emptyMessageReactionsList
]).then(([userIds, messageReactionsList]) => {
const readParticipantsPeerIds = userIds.map(userId => userId.toPeerId());
const filteredReadParticipants = readParticipantsPeerIds.slice();
forEachReverse(filteredReadParticipants, (peerId, idx, arr) => {
if(messageReactionsList.reactions.some(reaction => appPeersManager.getPeerId(reaction.peer_id) === peerId)) {
arr.splice(idx, 1);
}
});
let combined: {peerId: PeerId, reaction?: string}[] = messageReactionsList.reactions.map(reaction => ({peerId: appPeersManager.getPeerId(reaction.peer_id), reaction: reaction.reaction}));
combined = combined.concat(filteredReadParticipants.map(readPeerId => ({peerId: readPeerId})));
return {
reactions: messageReactionsList.reactions,
2 years ago
reactionsCount: messageReactionsList.count,
readParticipants: readParticipantsPeerIds,
combined: combined,
nextOffset: messageReactionsList.next_offset
};
});
}
public getMessageReadParticipants(peerId: PeerId, mid: number): Promise<UserId[]> {
return apiManager.invokeApiSingle('messages.getMessageReadParticipants', {
peer: appPeersManager.getInputPeerById(peerId),
msg_id: appMessagesIdsManager.getServerMessageId(mid)
}).then(userIds => { // ! convert long to number
return userIds.map(userId => userId.toUserId());
});
}
public canViewMessageReadParticipants(message: Message) {
if(
message._ !== 'message' ||
message.pFlags.is_outgoing ||
!message.pFlags.out ||
!appPeersManager.isAnyGroup(message.peerId)
) {
return false;
}
const chat: Chat.chat | Chat.channel = appChatsManager.getChat(message.peerId.toChatId());
return chat.participants_count < rootScope.appConfig.chat_read_mark_size_threshold &&
(tsNow(true) - message.date) < rootScope.appConfig.chat_read_mark_expire_period;
}
3 years ago
public incrementMessageViews(peerId: PeerId, 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);
3 years ago
const channelId = peerId.toChatId();
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,
userReaction: MessagePeerReaction,
peerTypeNotifySettings: PeerNotifySettings
}> = {}) {
const peerId = this.getMessagePeer(message);
if(appPeersManager.isRestricted(peerId)) {
return;
}
const isAnyChat = peerId.isAnyChat();
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);
if(options.userReaction) {
const langPackKey: LangPackKey = /* isAnyChat ? 'Notification.Group.Reacted' : */'Notification.Contact.Reacted';
const args: FormatterArguments = [
RichTextProcessor.fixEmoji(options.userReaction.reaction), // can be plain heart
notificationMessage
];
/* if(isAnyChat) {
args.unshift(appPeersManager.getPeerTitle(message.fromId, true));
} */
notificationMessage = I18n.format(langPackKey, true, args);
}
}
} else {
notificationMessage = I18n.format('Notifications.New', true);
}
notification.title = appPeersManager.getPeerTitle(peerId, true);
if(isAnyChat && 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 || options.userReaction) {
notification.image = url;
appNotificationsManager.notify(notification);
}
});
} else {
appNotificationsManager.notify(notification);
}
}
3 years ago
public getScheduledMessagesStorage(peerId: PeerId) {
return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = this.createMessageStorage());
}
3 years ago
public getScheduledMessageByPeer(peerId: PeerId, mid: number) {
return this.getMessageFromStorage(this.getScheduledMessagesStorage(peerId), mid);
}
3 years ago
public getScheduledMessages(peerId: PeerId): Promise<number[]> {
if(!this.canSendToPeer(peerId)) return Promise.resolve([]);
const storage = this.getScheduledMessagesStorage(peerId);
if(storage.size) {
return Promise.resolve([...storage.keys()]);
}
return apiManager.invokeApiSingle('messages.getScheduledHistory', {
peer: appPeersManager.getInputPeerById(peerId),
3 years ago
hash: ''
}).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 [...storage.keys()];
}
return [];
});
}
3 years ago
public sendScheduledMessages(peerId: PeerId, 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);
});
}
3 years ago
public deleteScheduledMessages(peerId: PeerId, 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;
3 years ago
if(!(message && message.replies && message.replies.pFlags.comments && message.replies.channel_id !== '777')) {
3 years ago
return;
}
}
return message;
}
3 years ago
public isFetchIntervalNeeded(peerId: PeerId) {
return peerId.isAnyChat() && !appChatsManager.isInChat(peerId.toChatId());
}
public isRestricted(message: Message.message) {
return !!(message.restriction_reason && isRestricted(message.restriction_reason));
}
3 years ago
public async getNewHistory(peerId: PeerId, 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
*/
3 years ago
public getHistory(peerId: PeerId, maxId = 0, limit: number, backLimit?: number, threadId?: number): Promise<HistoryResult> | HistoryResult {
const historyStorage = this.getHistoryStorage(peerId, threadId);
if(appPeersManager.isRestricted(peerId)) {
const first = historyStorage.history.first;
first.setEnd(SliceEnd.Both);
const slice = first.slice(0, 0);
slice.setEnd(SliceEnd.Both);
return {
count: 0,
history: slice,
offsetIdOffset: 0
};
}
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')
3 years ago
// * check that offset_id is not 0
if(offset_id && appMessagesIdsManager.getServerMessageId(offset_id) && !mids.includes(offset_id) && offsetIdOffset < count) {
3 years ago
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};
}
3 years ago
public fillHistoryStorage(peerId: PeerId, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> {
3 years ago
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
});
}
3 years ago
public requestHistory(peerId: PeerId, 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)) {
3 years ago
apiUpdatesManager.addChannelState(peerId.toChatId(), (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':
3 years ago
let channel = appChatsManager.getChat(peerId.toChatId());
4 years ago
channel = {_: 'channelForbidden', access_hash: channel.access_hash, title: channel.title};
apiUpdatesManager.processUpdateMessage({
_: 'updates',
updates: [{
_: 'updateChannel',
3 years ago
channel_id: peerId.toChatId()
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(() => {
3 years ago
const requestPromises: Promise<void>[] = [];
3 years ago
for(const [peerId, map] of this.needSingleMessages) {
const mids = [...map.keys()];
const msgIds: InputMessage[] = mids.map((mid) => {
return {
_: 'inputMessageID',
3 years ago
id: appMessagesIdsManager.getServerMessageId(mid)
};
});
let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>;
3 years ago
if(peerId.isAnyChat() && appPeersManager.isChannel(peerId)) {
promise = apiManager.invokeApiSingle('channels.getMessages', {
3 years ago
channel: appChatsManager.getChannelInput(peerId.toChatId()),
id: msgIds
});
} else {
promise = apiManager.invokeApiSingle('messages.getMessages', {
id: msgIds
});
}
const after = promise.then(getMessagesResult => {
3 years ago
assumeType<Exclude<MessagesMessages.messagesMessages, MessagesMessages.messagesMessagesNotModified>>(getMessagesResult);
appUsersManager.saveApiUsers(getMessagesResult.users);
appChatsManager.saveApiChats(getMessagesResult.chats);
this.saveMessages(getMessagesResult.messages);
for(let i = 0; i < getMessagesResult.messages.length; ++i) {
const message = getMessagesResult.messages[i];
const mid = appMessagesIdsManager.generateMessageId(message.id);
const promise = map.get(mid);
3 years ago
promise.resolve(getMessagesResult.messages[i]);
map.delete(mid);
}
if(map.size) {
for(const [mid, promise] of map) {
promise.resolve(this.generateEmptyMessage(mid));
}
}
}).finally(() => {
3 years ago
rootScope.dispatchEvent('messages_downloaded', {peerId, mids});
});
3 years ago
requestPromises.push(after);
}
4 years ago
3 years ago
this.needSingleMessages.clear();
Promise.all(requestPromises).finally(() => {
this.fetchSingleMessagesPromise = null;
if(this.needSingleMessages.size) this.fetchSingleMessages();
resolve();
});
}, 0);
4 years ago
});
}
3 years ago
public wrapSingleMessage(peerId: PeerId, mid: number, overwrite = false): Promise<Message> {
const message = this.getMessageByPeer(peerId, mid);
if(!message.deleted && !overwrite) {
rootScope.dispatchEvent('messages_downloaded', {peerId, mids: [mid]});
return Promise.resolve(message);
} else {
let map = this.needSingleMessages.get(peerId);
if(!map) {
this.needSingleMessages.set(peerId, map = new Map());
}
let promise = map.get(mid);
if(promise) {
return promise;
}
promise = deferredPromise();
map.set(mid, promise);
this.fetchSingleMessages();
return promise;
}
4 years ago
}
public fetchMessageReplyTo(message: MyMessage): Promise<Message> {
if(!message.reply_to_mid) return Promise.resolve(this.generateEmptyMessage(0));
const replyToPeerId = message.reply_to.reply_to_peer_id ? appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : message.peerId;
return this.wrapSingleMessage(replyToPeerId, message.reply_to_mid).then(originalMessage => {
if(originalMessage.deleted) { // ! чтобы не пыталось бесконечно загрузить удалённое сообщение
delete message.reply_to_mid; // ! WARNING!
}
return originalMessage;
});
}
public setTyping(peerId: PeerId, action: SendMessageAction, force?: boolean): Promise<boolean> {
let typing = this.typings[peerId];
if(!rootScope.myId ||
!peerId ||
!this.canSendToPeer(peerId) ||
peerId === rootScope.myId ||
// (!force && deepEqual(typing?.action, action))
(!force && typing?.action?._ === action._)
) {
return Promise.resolve(false);
}
if(typing?.timeout) {
clearTimeout(typing.timeout);
}
typing = this.typings[peerId] = {
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});
}
3 years ago
if('webpage' in media && media.webpage) {
3 years ago
const isScheduled = this.getScheduledMessagesStorage(message.peerId) === storage;
const messageKey = appWebPagesManager.getMessageKeyForPendingWebPage(message.peerId, message.mid, isScheduled);
appWebPagesManager.deleteWebPageFromPending(media.webpage, messageKey);
}
if((media as MessageMedia.messageMediaPoll).poll) {
appPollsManager.updatePollToMessage(message as Message.message, false);
}
}
}
3 years ago
private handleDeletedMessages(peerId: PeerId, 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);
3 years ago
if(message.deleted) {
this.fixDialogUnreadMentionsIfNoMessage(peerId);
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;
3 years ago
const groupedId = (message as Message.message).grouped_id;
if(groupedId) {
const groupedStorage = this.groupedMessagesStorage[groupedId];
if(groupedStorage) {
groupedStorage.delete(mid);
if(!history.albums) history.albums = {};
3 years ago
(history.albums[groupedId] || (history.albums[groupedId] = new Set())).add(mid);
if(!groupedStorage.size) {
delete history.albums;
3 years ago
delete this.groupedMessagesStorage[groupedId];
}
}
}
storage.delete(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;
}
3 years ago
private handleEditedMessage(oldMessage: Message, newMessage: Message) {
if(oldMessage._ === 'message') {
if((oldMessage.media as MessageMedia.messageMediaWebPage)?.webpage) {
const messageKey = appWebPagesManager.getMessageKeyForPendingWebPage(oldMessage.peerId, oldMessage.mid, !!oldMessage.pFlags.is_scheduled);
appWebPagesManager.deleteWebPageFromPending((oldMessage.media as MessageMedia.messageMediaWebPage).webpage, messageKey);
}
}
}
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
)
)
);
}
public isMentionUnread(message: MyMessage) {
const doc = ((message as Message.message).media as MessageMedia.messageMediaDocument)?.document as MyDocument;
return message.pFlags.media_unread &&
message.pFlags.mentioned &&
(
!doc ||
!(['voice', 'round'] as MyDocument['type'][]).includes(doc.type)
);
}
3 years ago
public getDialogUnreadCount(dialog: Dialog) {
return dialog.unread_count || +!!dialog.pFlags.unread_mark;
}
public isDialogUnread(dialog: Dialog) {
return !!this.getDialogUnreadCount(dialog);
}
public canForward(message: Message.message | Message.messageService) {
return !(message as Message.message).pFlags.noforwards && !appPeersManager.noForwards(message.peerId);
}
private pushBatchUpdate<E extends keyof BatchUpdates, C extends BatchUpdates[E]>(
event: E,
callback: C,
key: string,
getElementCallback?: () => MapValueType<ArgumentTypes<C>[0]>
) {
let details = this.batchUpdates[event];
if(!details) {
// @ts-ignore
details = this.batchUpdates[event] = {
callback,
batch: new Map()
};
}
if(!details.batch.has(key)) {
// @ts-ignore
details.batch.set(key, getElementCallback ? getElementCallback() : undefined);
this.batchUpdatesDebounced();
}
}
private getMessagesFromMap<T extends Map<any, any>>(map: T) {
const newMap: Map<Message.message, MapValueType<T>> = new Map();
for(const [key, value] of map) {
const [peerIdStr, mid] = key.split('_');
const message: Message.message | Message.messageEmpty = this.getMessageByPeer(peerIdStr.toPeerId(), +mid);
if(message._ === 'messageEmpty') {
continue;
}
newMap.set(message, value);
}
return newMap;
}
private batchUpdateViews = (batch: Map<string, undefined>) => {
const toDispatch: {peerId: PeerId, mid: number, views: number}[] = [];
const map = this.getMessagesFromMap(batch);
for(const [message] of map) {
toDispatch.push({
peerId: message.peerId,
mid: message.mid,
views: message.views
})
}
return toDispatch;
};
private batchUpdateReactions = (batch: Map<string, MessageReactions>) => {
const toDispatch: {message: Message.message, changedResults: ReactionCount.reactionCount[]}[] = [];
const map = this.getMessagesFromMap(batch);
for(const [message, reactions] of map) {
const results = reactions?.results ?? [];
const previousResults = message.reactions?.results ?? [];
const changedResults = results.filter(reactionCount => {
const previousReactionCount = previousResults.find(_reactionCount => _reactionCount.reaction === reactionCount.reaction);
return (
message.pFlags.out && (
!previousReactionCount ||
reactionCount.count > previousReactionCount.count
)
) || (
reactionCount.pFlags.chosen && (
!previousReactionCount ||
!previousReactionCount.pFlags.chosen
)
);
});
toDispatch.push({message, changedResults});
}
return toDispatch;
};
4 years ago
}
const appMessagesManager = new AppMessagesManager();
MOUNT_CLASS_TO.appMessagesManager = appMessagesManager;
export default appMessagesManager;