import ProgressivePreloader from "../../components/preloader" ;
import { CancellablePromise , deferredPromise } from "../../helpers/cancellablePromise" ;
import { tsNow } from "../../helpers/date" ;
import { copy , defineNotNumerableProperties , getObjectKeysAndSort } from "../../helpers/object" ;
import { randomLong } from "../../helpers/random" ;
import { splitStringByLength , limitSymbols } from "../../helpers/string" ;
import { Dialog as MTDialog , DialogPeer , DocumentAttribute , InputMedia , InputMessage , InputNotifyPeer , InputPeerNotifySettings , InputSingleMedia , Message , MessageAction , MessageEntity , MessageReplies , MessageReplyHeader , MessagesDialogs , MessagesFilter , MessagesMessages , MessagesPeerDialogs , MethodDeclMap , NotifyPeer , PhotoSize , SendMessageAction , Update } from "../../layer" ;
import { InvokeApiOptions } from "../../types" ;
import { langPack } from "../langPack" ;
import { logger , LogLevels } from "../logger" ;
import type { ApiFileManager } from '../mtproto/apiFileManager' ;
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker' ;
import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config" ;
import referenceDatabase , { ReferenceContext } from "../mtproto/referenceDatabase" ;
import serverTimeManager from "../mtproto/serverTimeManager" ;
import { RichTextProcessor } from "../richtextprocessor" ;
import rootScope from "../rootScope" ;
import searchIndexManager from '../searchIndexManager' ;
import AppStorage from '../storage' ;
import DialogsStorage from "../storages/dialogs" ;
import FiltersStorage from "../storages/filters" ;
//import { telegramMeWebService } from "../mtproto/mtproto";
import apiUpdatesManager from "./apiUpdatesManager" ;
import appChatsManager from "./appChatsManager" ;
import appDocsManager , { MyDocument } from "./appDocsManager" ;
import appDownloadManager from "./appDownloadManager" ;
import appPeersManager from "./appPeersManager" ;
import appPhotosManager , { MyPhoto } from "./appPhotosManager" ;
import appPollsManager from "./appPollsManager" ;
import appStateManager from "./appStateManager" ;
import appUsersManager from "./appUsersManager" ;
import appWebPagesManager from "./appWebPagesManager" ;
//console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
const APITIMEOUT = 0 ;
export type HistoryStorage = {
count : number | null ,
history : number [ ] ,
pending : number [ ] ,
maxId? : number ,
readPromise? : Promise < boolean > ,
readMaxId? : number ,
readOutboxMaxId? : number ,
maxOutId? : number ,
reply_markup? : any
} ;
export type HistoryResult = {
count : number ,
history : number [ ] ,
unreadOffset : number ,
unreadSkip : boolean
} ;
export type Dialog = MTDialog . dialog ;
export type MyMessage = Message . message | Message . messageService ;
type MyInputMessagesFilter = 'inputMessagesFilterEmpty'
| 'inputMessagesFilterPhotos'
| 'inputMessagesFilterPhotoVideo'
| 'inputMessagesFilterVideo'
| 'inputMessagesFilterDocument'
| 'inputMessagesFilterVoice'
| 'inputMessagesFilterRoundVoice'
| 'inputMessagesFilterRoundVideo'
| 'inputMessagesFilterMusic'
| 'inputMessagesFilterUrl'
| 'inputMessagesFilterMyMentions'
| 'inputMessagesFilterChatPhotos'
| 'inputMessagesFilterPinned' ;
export type PinnedStorage = Partial < {
promise : Promise < PinnedStorage > ,
count : number ,
maxId : number
} > ;
export type MessagesStorage = {
//generateIndex: (message: any) => void
[ mid : string ] : any
} ;
export class AppMessagesManager {
public static MESSAGE_ID_INCREMENT = 0x10000 ;
public static MESSAGE_ID_OFFSET = 0xFFFFFFFF ;
public messagesStorageByPeerId : { [ peerId : string ] : MessagesStorage } = { } ;
public groupedMessagesStorage : { [ groupId : string ] : MessagesStorage } = { } ; // will be used for albums
public scheduledMessagesStorage : { [ peerId : string ] : MessagesStorage } = { } ;
public historiesStorage : {
[ peerId : string ] : HistoryStorage
} = { } ;
public threadsStorage : {
[ peerId : string ] : {
[ threadId : string ] : HistoryStorage
}
} = { } ;
public searchesStorage : {
[ peerId : string ] : Partial < {
[ inputFilter in MyInputMessagesFilter ] : {
count? : number ,
history : number [ ]
}
} >
} = { } ;
public pinnedMessages : { [ peerId : string ] : PinnedStorage } = { } ;
public threadsToReplies : {
[ peerId_threadId : string ] : string ;
} = { } ;
public pendingByRandomId : {
[ randomId : string ] : {
peerId : number ,
tempId : number ,
storage : MessagesStorage
}
} = { } ;
public pendingByMessageId : { [ mid : string ] : string } = { } ;
public pendingAfterMsgs : any = { } ;
public pendingTopMsgs : { [ peerId : string ] : number } = { } ;
public sendFilePromise : CancellablePromise < void > = Promise . resolve ( ) ;
public tempNum = 0 ;
public tempFinalizeCallbacks : {
[ tempId : string ] : {
[ callbackName : string ] : Partial < {
deferred : CancellablePromise < void > ,
callback : ( message : any ) = > Promise < any >
} >
}
} = { } ;
public needSingleMessages : { [ peerId : string ] : number [ ] } = { } ;
private fetchSingleMessagesPromise : Promise < void > = null ;
public maxSeenId = 0 ;
public migratedFromTo : { [ peerId : number ] : number } = { } ;
public migratedToFrom : { [ peerId : number ] : number } = { } ;
public newMessagesHandlePromise = 0 ;
public newMessagesToHandle : { [ peerId : string ] : number [ ] } = { } ;
public newDialogsHandlePromise = 0 ;
public newDialogsToHandle : { [ peerId : string ] : { reload : true } | Dialog } = { } ;
public newUpdatesAfterReloadToHandle : any = { } ;
private reloadConversationsPromise : Promise < void > ;
private reloadConversationsPeers : number [ ] = [ ] ;
private dialogsIndex = searchIndexManager . createIndex ( ) ;
private cachedResults : {
query : string ,
count : number ,
dialogs : Dialog [ ] ,
folderId : number
} = {
query : '' ,
count : 0 ,
dialogs : [ ] ,
folderId : 0
} ;
private log = logger ( 'MESSAGES' , LogLevels . error | LogLevels . debug | LogLevels . log | LogLevels . warn ) ;
public dialogsStorage : DialogsStorage ;
public filtersStorage : FiltersStorage ;
constructor ( ) {
this . dialogsStorage = new DialogsStorage ( this , appChatsManager , appPeersManager , serverTimeManager ) ;
this . filtersStorage = new FiltersStorage ( appPeersManager , appUsersManager , /* apiManager, */ rootScope ) ;
rootScope . on ( 'apiUpdate' , ( e ) = > {
this . handleUpdate ( e . detail ) ;
} ) ;
rootScope . on ( 'webpage_updated' , ( e ) = > {
const eventData = e . detail ;
eventData . msgs . forEach ( ( mid ) = > {
const message = this . getMessageById ( mid ) as Message . message ;
if ( ! message ) return ;
message . media = {
_ : 'messageMediaWebPage' ,
webpage : appWebPagesManager.getWebPage ( eventData . id )
} ;
const peerId = this . getMessagePeer ( message ) ;
const storage = this . getMessagesStorage ( peerId ) ;
rootScope . broadcast ( 'message_edit' , {
storage ,
peerId ,
mid
} ) ;
} ) ;
} ) ;
/ * r o o t S c o p e . $ o n ( ' d r a f t _ u p d a t e d ' , ( e ) = > {
let eventData = e . detail ; ;
var peerId = eventData . peerId ;
var draft = eventData . draft ;
var dialog = this . getDialogByPeerID ( peerId ) [ 0 ] ;
if ( dialog ) {
var topDate ;
if ( draft && draft . date ) {
topDate = draft . date ;
} else {
var channelId = appPeersManager . isChannel ( peerId ) ? - peerId : 0
var topDate = this . getMessage ( dialog . top_message ) . date ;
if ( channelId ) {
var channel = appChatsManager . getChat ( channelId ) ;
if ( ! topDate || channel . date && channel . date > topDate ) {
topDate = channel . date ;
}
}
}
if ( ! dialog . pFlags . pinned ) {
dialog . index = this . dialogsStorage . generateDialogIndex ( topDate ) ;
}
this . dialogsStorage . pushDialog ( dialog ) ;
rootScope . $broadcast ( 'dialog_draft' , {
peerId ,
draft ,
index : dialog.index
} ) ;
}
} ) ; * /
function timedChunk ( items : any [ ] , process : ( . . . args : any [ ] ) = > any , context : any , callback : ( . . . args : any [ ] ) = > void ) {
if ( ! items . length ) return callback ( items ) ;
const todo = items . slice ( ) ;
const f = ( ) = > {
const start = + new Date ( ) ;
do {
process . call ( context , todo . shift ( ) ) ;
} while ( todo . length > 0 && ( + new Date ( ) - start < 50 ) ) ;
if ( todo . length > 0 ) {
setTimeout ( f , 25 ) ;
} else {
callback ( items ) ;
}
} ;
setTimeout ( f , 25 ) ;
}
appStateManager . addListener ( 'save' , ( ) = > {
const messages : any [ ] = [ ] ;
const dialogs : Dialog [ ] = [ ] ;
const items : any [ ] = [ ] ;
const processDialog = ( dialog : MTDialog.dialog ) = > {
const historyStorage = this . getHistoryStorage ( dialog . peerId ) ;
const history = [ ] . concat ( historyStorage . pending , historyStorage . history ) ;
dialog = copy ( dialog ) ;
let removeUnread = 0 ;
for ( const mid of history ) {
const message = this . getMessageByPeer ( dialog . peerId , mid ) ;
if ( /* message._ != 'messageEmpty' && */ ! message . pFlags . is_outgoing ) {
messages . push ( message ) ;
if ( message . fromId != dialog . peerId ) {
appStateManager . setPeer ( message . fromId , appPeersManager . getPeer ( message . fromId ) ) ;
}
dialog . top_message = message . mid ;
this . setDialogIndexByMessage ( dialog , message ) ;
break ;
} else if ( message . pFlags && message . pFlags . unread ) {
++ removeUnread ;
}
}
if ( removeUnread && dialog . unread_count ) dialog . unread_count -= removeUnread ;
dialog . unread_count = Math . max ( 0 , dialog . unread_count ) ;
dialogs . push ( dialog ) ;
appStateManager . setPeer ( dialog . peerId , appPeersManager . getPeer ( dialog . peerId ) ) ;
} ;
for ( const folderId in this . dialogsStorage . byFolders ) {
const folder = this . dialogsStorage . getFolder ( + folderId ) ;
for ( let dialog of folder ) {
items . push ( dialog ) ;
}
}
return new Promise ( ( resolve = > timedChunk ( items , processDialog , this , resolve ) ) ) . then ( ( ) = > {
appStateManager . pushToState ( 'dialogs' , dialogs ) ;
appStateManager . pushToState ( 'messages' , messages ) ;
appStateManager . pushToState ( 'filters' , this . filtersStorage . filters ) ;
appStateManager . pushToState ( 'allDialogsLoaded' , this . dialogsStorage . allDialogsLoaded ) ;
appStateManager . pushToState ( 'maxSeenMsgId' , this . maxSeenId ) ;
} ) ;
} ) ;
appStateManager . getState ( ) . then ( state = > {
if ( state . maxSeenMsgId ) {
this . maxSeenId = state . maxSeenMsgId ;
}
const messages = state . messages ;
if ( messages ) {
/ * l e t t e m p I d = t h i s . t e m p I d ;
for ( let message of messages ) {
if ( message . id < tempId ) {
tempId = message . id ;
}
}
if ( tempId != this . tempId ) {
this . log ( 'Set tempId to:' , tempId ) ;
this . tempId = tempId ;
} * /
this . saveMessages ( messages ) ;
}
if ( ! state . dialogs || ! Object . keys ( state . dialogs ) . length ) {
state . allDialogsLoaded = { } ;
}
if ( state . allDialogsLoaded ) {
this . dialogsStorage . allDialogsLoaded = state . allDialogsLoaded ;
}
if ( state . filters ) {
for ( const filterId in state . filters ) {
this . filtersStorage . saveDialogFilter ( state . filters [ filterId ] , false ) ;
}
}
if ( state . dialogs ) {
state . dialogs . forEachReverse ( dialog = > {
this . saveConversation ( dialog ) ;
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись
const message = this . getMessageByPeer ( dialog . peerId , dialog . top_message ) ;
if ( message . deleted ) {
this . reloadConversation ( dialog . peerId ) ;
}
} ) ;
}
} ) ;
}
public getInputEntities ( entities : any ) {
var sendEntites = copy ( entities ) ;
sendEntites . forEach ( ( entity : any ) = > {
if ( entity . _ == 'messageEntityMentionName' ) {
entity . _ = 'inputMessageEntityMentionName' ;
entity . user_id = appUsersManager . getUserInput ( entity . user_id ) ;
}
} ) ;
return sendEntites ;
}
public invokeAfterMessageIsSent ( tempId : number , callbackName : string , callback : ( message : any ) = > Promise < any > ) {
const finalize = this . tempFinalizeCallbacks [ tempId ] ? ? ( this . tempFinalizeCallbacks [ tempId ] = { } ) ;
const obj = finalize [ callbackName ] ? ? ( finalize [ callbackName ] = { deferred : deferredPromise < void > ( ) } ) ;
obj . callback = callback ;
return obj . deferred ;
}
public editMessage ( message : any , text : string , options : Partial < {
noWebPage : true ,
newMedia : any ,
scheduleDate : number ,
entities : MessageEntity [ ]
} > = { } ) : Promise < void > {
/ * i f ( ! t h i s . c a n E d i t M e s s a g e ( m e s s a g e I d ) ) {
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 ( typeof ( text ) === 'string' && ! entities ) {
entities = [ ] ;
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 ? this . getInputEntities ( entities ) : undefined ,
no_webpage : options.noWebPage ,
schedule_date
} ) . then ( ( updates ) = > {
apiUpdatesManager . processUpdateMessage ( updates ) ;
} , ( error ) = > {
this . log . error ( 'editMessage error:' , error ) ;
if ( error && error . type == 'MESSAGE_NOT_MODIFIED' ) {
error . handled = true ;
return ;
}
if ( error && error . type == 'MESSAGE_EMPTY' ) {
error . handled = true ;
}
return Promise . reject ( error ) ;
} ) ;
}
public sendText ( peerId : number , text : string , options : Partial < {
entities : any [ ] ,
replyToMsgId : number ,
threadId : number ,
viaBotId : number ,
queryId : string ,
resultId : string ,
noWebPage : true ,
reply_markup : any ,
clearDraft : true ,
webPage : any ,
scheduleDate : number ,
silent : true
} > = { } ) {
if ( typeof ( text ) != 'string' || ! text . length ) {
return ;
}
//this.checkSendOptions(options);
const MAX_LENGTH = 4096 ;
if ( text . length > MAX_LENGTH ) {
const splitted = splitStringByLength ( text , MAX_LENGTH ) ;
text = splitted [ 0 ] ;
if ( splitted . length > 1 ) {
delete options . webPage ;
}
for ( let i = 1 ; i < splitted . length ; ++ i ) {
setTimeout ( ( ) = > {
this . sendText ( peerId , splitted [ i ] , options ) ;
} , i ) ;
}
}
peerId = appPeersManager . getPeerMigratedTo ( peerId ) || peerId ;
let entities = options . entities || [ ] ;
if ( ! options . viaBotId ) {
text = RichTextProcessor . parseMarkdown ( text , entities ) ;
}
let sendEntites = this . getInputEntities ( entities ) ;
if ( ! sendEntites . length ) {
sendEntites = undefined ;
}
const messageId = this . generateTempMessageId ( peerId ) ;
const randomIdS = randomLong ( ) ;
const replyToMsgId = options . replyToMsgId ? this . getLocalMessageId ( options . replyToMsgId ) : undefined ;
const isChannel = appPeersManager . isChannel ( peerId ) ;
const isBroadcast = appPeersManager . isBroadcast ( peerId ) ;
const message : any = {
_ : 'message' ,
id : messageId ,
from_id : this.generateFromId ( peerId ) ,
peer_id : appPeersManager.getOutputPeer ( peerId ) ,
pFlags : this.generateFlags ( peerId ) ,
date : options.scheduleDate || ( tsNow ( true ) + serverTimeManager . serverTimeOffset ) ,
message : text ,
random_id : randomIdS ,
reply_to : this.generateReplyHeader ( options . replyToMsgId , options . threadId ) ,
via_bot_id : options.viaBotId ,
reply_markup : options.reply_markup ,
entities : entities ,
views : isBroadcast && 1 ,
pending : true
} ;
if ( options . webPage ) {
message . media = {
_ : 'messageMediaWebPage' ,
webpage : options.webPage
} ;
}
const toggleError = ( on : any ) = > {
if ( on ) {
message . error = true ;
} else {
delete message . error ;
}
rootScope . broadcast ( 'messages_pending' ) ;
}
message . send = ( ) = > {
toggleError ( false ) ;
const sentRequestOptions : any = { } ;
if ( this . pendingAfterMsgs [ peerId ] ) {
sentRequestOptions . afterMessageId = this . pendingAfterMsgs [ peerId ] . messageId ;
}
let apiPromise : any ;
if ( options . viaBotId ) {
apiPromise = apiManager . invokeApiAfter ( 'messages.sendInlineBotResult' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
random_id : randomIdS ,
reply_to_msg_id : replyToMsgId || undefined ,
query_id : options.queryId ,
id : options.resultId ,
clear_draft : options.clearDraft
} , sentRequestOptions ) ;
} else {
apiPromise = apiManager . invokeApiAfter ( 'messages.sendMessage' , {
no_webpage : options.noWebPage ,
peer : appPeersManager.getInputPeerById ( peerId ) ,
message : text ,
random_id : randomIdS ,
reply_to_msg_id : replyToMsgId || undefined ,
entities : sendEntites ,
clear_draft : options.clearDraft ,
schedule_date : options.scheduleDate || undefined ,
silent : options.silent
} , sentRequestOptions ) ;
}
// this.log(flags, entities)
apiPromise . then ( ( updates : any ) = > {
if ( updates . _ == 'updateShortSentMessage' ) {
message . date = updates . date ;
message . id = updates . id ;
message . media = updates . media ;
message . entities = updates . entities ;
// * override with new updates
updates = {
_ : 'updates' ,
users : [ ] ,
chats : [ ] ,
seq : 0 ,
updates : [ {
_ : 'updateMessageID' ,
random_id : randomIdS ,
id : updates.id
} , {
_ : options.scheduleDate ? 'updateNewScheduledMessage' : ( isChannel ? 'updateNewChannelMessage' : 'updateNewMessage' ) ,
message : message ,
pts : updates.pts ,
pts_count : updates.pts_count
} ]
} ;
} else if ( updates . updates ) {
updates . updates . forEach ( ( update : any ) = > {
if ( update . _ == 'updateDraftMessage' ) {
update . local = true ;
}
} ) ;
}
// Testing bad situations
// var upd = angular.copy(updates)
// updates.updates.splice(0, 1)
apiUpdatesManager . processUpdateMessage ( updates ) ;
// $timeout(function () {
// ApiUpdatesManager.processUpdateMessage(upd)
// }, 5000)
} , ( /* error: any */ ) = > {
toggleError ( true ) ;
} ) . finally ( ( ) = > {
if ( this . pendingAfterMsgs [ peerId ] === sentRequestOptions ) {
delete this . pendingAfterMsgs [ peerId ] ;
}
} ) ;
this . pendingAfterMsgs [ peerId ] = sentRequestOptions ;
}
this . beforeMessageSending ( message , { isScheduled : ! ! options . scheduleDate || undefined } ) ;
}
public sendFile ( peerId : number , file : File | Blob | MyDocument , options : Partial < {
isRoundMessage : true ,
isVoiceMessage : true ,
isGroupedItem : true ,
isMedia : true ,
replyToMsgId : number ,
threadId : number ,
caption : string ,
entities : MessageEntity [ ] ,
width : number ,
height : number ,
objectURL : string ,
duration : number ,
background : true ,
silent : true ,
scheduleDate : number ,
waveform : Uint8Array
} > = { } ) {
peerId = appPeersManager . getPeerMigratedTo ( peerId ) || peerId ;
//this.checkSendOptions(options);
const messageId = this . generateTempMessageId ( peerId ) ;
const randomIdS = randomLong ( ) ;
const pFlags = this . generateFlags ( peerId ) ;
const replyToMsgId = options . replyToMsgId ? this . getLocalMessageId ( options . replyToMsgId ) : undefined ;
const isChannel = appPeersManager . isChannel ( peerId ) ;
const isMegagroup = isChannel && appPeersManager . isMegagroup ( peerId ) ;
const asChannel = ! ! ( isChannel && ! isMegagroup ) ;
let attachType : string , apiFileName : string ;
const fileType = 'mime_type' in file ? file.mime_type : file.type ;
const fileName = file instanceof File ? file . name : '' ;
const isDocument = ! ( file instanceof File ) && ! ( file instanceof Blob ) ;
let caption = options . caption || '' ;
const date = options . scheduleDate || ( tsNow ( true ) + serverTimeManager . serverTimeOffset ) ;
this . log ( 'sendFile' , file , fileType ) ;
if ( caption ) {
let entities = options . entities || [ ] ;
caption = RichTextProcessor . parseMarkdown ( caption , entities ) ;
}
const attributes : DocumentAttribute [ ] = [ ] ;
const isPhoto = [ 'image/jpeg' , 'image/png' , 'image/bmp' ] . indexOf ( fileType ) >= 0 ;
let photo : MyPhoto , document : MyDocument ;
let actionName = '' ;
if ( isDocument ) { // maybe it's a sticker or gif
attachType = 'document' ;
apiFileName = '' ;
} else if ( fileType . indexOf ( 'audio/' ) === 0 || [ 'video/ogg' ] . indexOf ( fileType ) >= 0 ) {
attachType = 'audio' ;
apiFileName = 'audio.' + ( fileType . split ( '/' ) [ 1 ] == 'ogg' ? 'ogg' : 'mp3' ) ;
actionName = 'sendMessageUploadAudioAction' ;
if ( options . isVoiceMessage ) {
attachType = 'voice' ;
pFlags . media_unread = true ;
}
let attribute : DocumentAttribute.documentAttributeAudio = {
_ : 'documentAttributeAudio' ,
pFlags : {
voice : options.isVoiceMessage
} ,
waveform : options.waveform ,
duration : options.duration || 0
} ;
attributes . push ( attribute ) ;
} else if ( ! options . isMedia ) {
attachType = 'document' ;
apiFileName = 'document.' + fileType . split ( '/' ) [ 1 ] ;
actionName = 'sendMessageUploadDocumentAction' ;
} else if ( isPhoto ) {
attachType = 'photo' ;
apiFileName = 'photo.' + fileType . split ( '/' ) [ 1 ] ;
actionName = 'sendMessageUploadPhotoAction' ;
photo = {
_ : 'photo' ,
id : '' + messageId ,
sizes : [ {
_ : 'photoSize' ,
w : options.width ,
h : options.height ,
type : 'full' ,
location : null ,
size : file.size
} ] ,
w : options.width ,
h : options.height
} as any ;
defineNotNumerableProperties ( photo , [ 'downloaded' , 'url' ] ) ;
photo . downloaded = file . size ;
photo . url = options . objectURL || '' ;
appPhotosManager . savePhoto ( photo ) ;
} else if ( fileType . indexOf ( 'video/' ) === 0 ) {
attachType = 'video' ;
apiFileName = 'video.mp4' ;
actionName = 'sendMessageUploadVideoAction' ;
let videoAttribute : DocumentAttribute.documentAttributeVideo = {
_ : 'documentAttributeVideo' ,
pFlags : {
round_message : options.isRoundMessage
} ,
duration : options.duration ,
w : options.width ,
h : options.height
} ;
attributes . push ( videoAttribute ) ;
} else {
attachType = 'document' ;
apiFileName = 'document.' + fileType . split ( '/' ) [ 1 ] ;
actionName = 'sendMessageUploadDocumentAction' ;
}
attributes . push ( { _ : 'documentAttributeFilename' , file_name : fileName || apiFileName } ) ;
if ( [ 'document' , 'video' , 'audio' , 'voice' ] . indexOf ( attachType ) !== - 1 && ! isDocument ) {
const thumbs : PhotoSize [ ] = [ ] ;
document = {
_ : 'document' ,
id : '' + messageId ,
duration : options.duration ,
attributes ,
w : options.width ,
h : options.height ,
thumbs ,
mime_type : fileType ,
size : file.size
} as any ;
defineNotNumerableProperties ( document , [ 'downloaded' , 'url' ] ) ;
// @ts-ignore
document . downloaded = file . size ;
document . url = options . objectURL || '' ;
if ( isPhoto ) {
attributes . push ( {
_ : 'documentAttributeImageSize' ,
w : options.width ,
h : options.height
} ) ;
thumbs . push ( {
_ : 'photoSize' ,
w : options.width ,
h : options.height ,
type : 'full' ,
location : null ,
size : file.size ,
url : options.objectURL
} ) ;
}
appDocsManager . saveDoc ( document ) ;
}
this . log ( 'AMM: sendFile' , attachType , apiFileName , file . type , options ) ;
const preloader = new ProgressivePreloader ( null , true , false , 'prepend' ) ;
const media = {
_ : 'messageMediaPending' ,
type : options . isGroupedItem && options . isMedia ? 'album' : attachType ,
file_name : fileName || apiFileName ,
size : file.size ,
file ,
preloader ,
photo ,
document ,
w : options.width ,
h : options.height ,
url : options.objectURL
} ;
const message : any = {
_ : 'message' ,
id : messageId ,
from_id : this.generateFromId ( peerId ) ,
peer_id : appPeersManager.getOutputPeer ( peerId ) ,
pFlags ,
date ,
message : caption ,
media : isDocument ? {
_ : 'messageMediaDocument' ,
pFlags : { } ,
document : file
} : media ,
random_id : randomIdS ,
reply_to : this.generateReplyHeader ( options . replyToMsgId , options . threadId ) ,
views : asChannel && 1 ,
pending : true
} ;
const toggleError = ( on : boolean ) = > {
if ( on ) {
message . error = true ;
} else {
delete message . error ;
}
rootScope . broadcast ( 'messages_pending' ) ;
} ;
let uploaded = false ,
uploadPromise : ReturnType < ApiFileManager [ ' uploadFile ' ] > = null ;
const sentDeferred = deferredPromise < InputMedia > ( ) ;
message . send = ( ) = > {
if ( isDocument ) {
const { id , access_hash , file_reference } = file as MyDocument ;
const inputMedia : InputMedia = {
_ : 'inputMediaDocument' ,
id : {
_ : 'inputDocument' ,
id ,
access_hash ,
file_reference
}
} ;
sentDeferred . resolve ( inputMedia ) ;
} else if ( file instanceof File || file instanceof Blob ) {
const deferred = deferredPromise < void > ( ) ;
this . sendFilePromise . then ( ( ) = > {
if ( ! uploaded || message . error ) {
uploaded = false ;
uploadPromise = appDownloadManager . upload ( file ) ;
preloader . attachPromise ( uploadPromise ) ;
}
uploadPromise && uploadPromise . then ( ( inputFile ) = > {
this . log ( 'appMessagesManager: sendFile uploaded:' , inputFile ) ;
inputFile . name = apiFileName ;
uploaded = true ;
let inputMedia : InputMedia ;
switch ( attachType ) {
case 'photo' :
inputMedia = {
_ : 'inputMediaUploadedPhoto' ,
file : inputFile
} ;
break ;
default :
inputMedia = {
_ : 'inputMediaUploadedDocument' ,
file : inputFile ,
mime_type : fileType ,
attributes
} ;
}
sentDeferred . resolve ( inputMedia ) ;
} , ( /* error */ ) = > {
toggleError ( true ) ;
} ) ;
uploadPromise . addNotifyListener ( ( progress : { done : number , total : number } ) = > {
this . log ( 'upload progress' , progress ) ;
const percents = Math . max ( 1 , Math . floor ( 100 * progress . done / progress . total ) ) ;
this . setTyping ( peerId , { _ : actionName , progress : percents | 0 } ) ;
} ) ;
uploadPromise . catch ( err = > {
if ( err . name === 'AbortError' && ! uploaded ) {
this . log ( 'cancelling upload' , media ) ;
deferred . resolve ( ) ;
sentDeferred . reject ( err ) ;
this . cancelPendingMessage ( randomIdS ) ;
this . setTyping ( peerId , 'sendMessageCancelAction' ) ;
}
} ) ;
uploadPromise . finally ( deferred . resolve ) ;
} ) ;
this . sendFilePromise = deferred ;
}
return sentDeferred ;
} ;
this . beforeMessageSending ( message , { isGroupedItem : options.isGroupedItem , isScheduled : ! ! options . scheduleDate || undefined } ) ;
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 : randomIdS ,
reply_to_msg_id : replyToMsgId ,
schedule_date : options.scheduleDate ,
silent : options.silent
} ) . then ( ( updates ) = > {
apiUpdatesManager . processUpdateMessage ( updates ) ;
} , ( error ) = > {
if ( attachType == 'photo' &&
error . code == 400 &&
( error . type == 'PHOTO_INVALID_DIMENSIONS' ||
error . type == 'PHOTO_SAVE_FILE_INVALID' ) ) {
error . handled = true ;
attachType = 'document' ;
message . send ( ) ;
return ;
}
toggleError ( true ) ;
} ) ;
} ) ;
}
return { message , promise : sentDeferred } ;
}
public async sendAlbum ( peerId : number , files : File [ ] , options : Partial < {
isMedia : true ,
entities : MessageEntity [ ] ,
replyToMsgId : number ,
threadId : number ,
caption : string ,
sendFileDetails : Partial < {
duration : number ,
width : number ,
height : number ,
objectURL : string ,
} > [ ] ,
silent : true ,
scheduleDate : number
} > = { } ) {
//this.checkSendOptions(options);
if ( files . length === 1 ) {
return this . sendFile ( peerId , files [ 0 ] , { . . . options , . . . options . sendFileDetails [ 0 ] } ) ;
}
peerId = appPeersManager . getPeerMigratedTo ( peerId ) || peerId ;
const replyToMsgId = options . replyToMsgId ? this . getLocalMessageId ( options . replyToMsgId ) : undefined ;
let caption = options . caption || '' ;
let entities : MessageEntity [ ] ;
if ( caption ) {
entities = options . entities || [ ] ;
caption = RichTextProcessor . parseMarkdown ( caption , entities ) ;
}
this . log ( 'AMM: sendAlbum' , files , options ) ;
const messages = files . map ( ( file , idx ) = > {
const details = options . sendFileDetails [ idx ] ;
const o : any = {
isGroupedItem : true ,
isMedia : options.isMedia ,
scheduleDate : options.scheduleDate ,
silent : options.silent ,
replyToMsgId ,
threadId : options.threadId ,
. . . details
} ;
if ( idx === 0 ) {
o . caption = caption ;
o . entities = entities ;
//o.replyToMsgId = replyToMsgId;
}
return this . sendFile ( peerId , file , o ) . message ;
} ) ;
const message = messages [ messages . length - 1 ] ;
const groupId = message . id ;
messages . forEach ( message = > {
message . grouped_id = groupId ;
} ) ;
const storage = options . scheduleDate ? this . getScheduledMessagesStorage ( peerId ) : this . getMessagesStorage ( peerId ) ;
if ( options . scheduleDate ) {
this . saveMessages ( messages , { storage , isScheduled : true , isOutgoing : true } ) ;
rootScope . broadcast ( 'scheduled_new' , { peerId , mid : groupId } ) ;
} else {
this . saveMessages ( messages , { storage , isOutgoing : true } ) ;
rootScope . broadcast ( 'history_append' , { peerId , messageId : groupId , my : true } ) ;
this . setDialogTopMessage ( message ) ;
}
// * test pending
//return;
const toggleError = ( message : any , on : boolean ) = > {
if ( on ) {
message . error = true ;
} else {
delete message . error ;
}
rootScope . broadcast ( 'messages_pending' ) ;
} ;
const inputPeer = appPeersManager . getInputPeerById ( peerId ) ;
const invoke = ( multiMedia : any [ ] ) = > {
this . setTyping ( peerId , 'sendMessageCancelAction' ) ;
return apiManager . invokeApi ( 'messages.sendMultiMedia' , {
peer : inputPeer ,
multi_media : multiMedia ,
reply_to_msg_id : replyToMsgId ,
schedule_date : options.scheduleDate ,
silent : options.silent
} ) . then ( ( updates ) = > {
apiUpdatesManager . processUpdateMessage ( updates ) ;
} , ( error ) = > {
messages . forEach ( message = > toggleError ( message , true ) ) ;
} ) ;
} ;
const inputs : InputSingleMedia [ ] = [ ] ;
for ( const message of messages ) {
const inputMedia : InputMedia = await message . send ( ) ;
this . log ( 'sendAlbum uploaded item:' , inputMedia ) ;
await apiManager . invokeApi ( 'messages.uploadMedia' , {
peer : inputPeer ,
media : inputMedia
} ) . then ( messageMedia = > {
let inputMedia : any ;
if ( messageMedia . _ == 'messageMediaPhoto' ) {
const photo = appPhotosManager . savePhoto ( messageMedia . photo ) ;
inputMedia = appPhotosManager . getInput ( photo ) ;
} else if ( messageMedia . _ == 'messageMediaDocument' ) {
const doc = appDocsManager . saveDoc ( messageMedia . document ) ;
inputMedia = appDocsManager . getMediaInput ( doc ) ;
}
inputs . push ( {
_ : 'inputSingleMedia' ,
media : inputMedia ,
random_id : message.random_id ,
message : caption ,
entities
} ) ;
// * only 1 caption for all inputs
if ( caption ) {
caption = '' ;
entities = [ ] ;
}
} , ( ) = > {
toggleError ( message , true ) ;
} ) ;
}
invoke ( inputs ) ;
}
public sendOther ( peerId : number , inputMedia : any , options : Partial < {
replyToMsgId : number ,
threadId : number ,
viaBotId : number ,
reply_markup : any ,
clearDraft : true ,
queryId : string
resultId : string ,
scheduleDate : number ,
silent : true
} > = { } ) {
peerId = appPeersManager . getPeerMigratedTo ( peerId ) || peerId ;
//this.checkSendOptions(options);
const messageId = this . generateTempMessageId ( peerId ) ;
const randomIdS = randomLong ( ) ;
const replyToMsgId = options . replyToMsgId ? this . getLocalMessageId ( options . replyToMsgId ) : undefined ;
const isChannel = appPeersManager . isChannel ( peerId ) ;
const isMegagroup = isChannel && appPeersManager . isMegagroup ( peerId ) ;
const asChannel = isChannel && ! isMegagroup ? true : false ;
let fromId = appUsersManager . getSelf ( ) . id ;
let media ;
switch ( inputMedia . _ ) {
case 'inputMediaPoll' : {
inputMedia . poll . id = messageId ;
appPollsManager . savePoll ( inputMedia . poll , {
_ : 'pollResults' ,
flags : 4 ,
total_voters : 0 ,
pFlags : { } ,
} ) ;
const { poll , results } = appPollsManager . getPoll ( '' + messageId ) ;
media = {
_ : 'messageMediaPoll' ,
poll ,
results
} ;
break ;
}
/ * c a s e ' i n p u t M e d i a P h o t o ' :
media = {
_ : 'messageMediaPhoto' ,
photo : appPhotosManager.getPhoto ( inputMedia . id . id ) ,
caption : inputMedia.caption || ''
} ;
break ;
case 'inputMediaDocument' :
var doc = appDocsManager . getDoc ( inputMedia . id . id ) ;
if ( doc . sticker && doc . stickerSetInput ) {
appStickersManager . pushPopularSticker ( doc . id ) ;
}
media = {
_ : 'messageMediaDocument' ,
'document' : doc ,
caption : inputMedia.caption || ''
} ;
break ;
case 'inputMediaContact' :
media = {
_ : 'messageMediaContact' ,
phone_number : inputMedia.phone_number ,
first_name : inputMedia.first_name ,
last_name : inputMedia.last_name ,
user_id : 0
} ;
break ;
case 'inputMediaGeoPoint' :
media = {
_ : 'messageMediaGeo' ,
geo : {
_ : 'geoPoint' ,
'lat' : inputMedia . geo_point [ 'lat' ] ,
'long' : inputMedia . geo_point [ 'long' ]
}
} ;
break ;
case 'inputMediaVenue' :
media = {
_ : 'messageMediaVenue' ,
geo : {
_ : 'geoPoint' ,
'lat' : inputMedia . geo_point [ 'lat' ] ,
'long' : inputMedia . geo_point [ 'long' ]
} ,
title : inputMedia.title ,
address : inputMedia.address ,
provider : inputMedia.provider ,
venue_id : inputMedia.venue_id
} ;
break ;
case 'messageMediaPending' :
media = inputMedia ;
break ; * /
}
const message : any = {
_ : 'message' ,
id : messageId ,
from_id : this.generateFromId ( peerId ) ,
peer_id : appPeersManager.getOutputPeer ( peerId ) ,
pFlags : this.generateFlags ( peerId ) ,
date : options.scheduleDate || ( tsNow ( true ) + serverTimeManager . serverTimeOffset ) ,
message : '' ,
media ,
random_id : randomIdS ,
reply_to : this.generateReplyHeader ( options . replyToMsgId , options . threadId ) ,
via_bot_id : options.viaBotId ,
reply_markup : options.reply_markup ,
views : asChannel && 1 ,
pending : true ,
} ;
let toggleError = ( on : boolean ) = > {
/ * c o n s t h i s t o r y M e s s a g e = t h i s . m e s s a g e s F o r H i s t o r y [ m e s s a g e I d ] ;
if ( on ) {
message . error = true
if ( historyMessage ) {
historyMessage . error = true
}
} else {
delete message . error
if ( historyMessage ) {
delete historyMessage . error
}
} * /
rootScope . broadcast ( 'messages_pending' ) ;
} ;
message . send = ( ) = > {
const sentRequestOptions : any = { } ;
if ( this . pendingAfterMsgs [ peerId ] ) {
sentRequestOptions . afterMessageId = this . pendingAfterMsgs [ peerId ] . messageId ;
}
let apiPromise : Promise < any > ;
if ( options . viaBotId ) {
apiPromise = apiManager . invokeApiAfter ( 'messages.sendInlineBotResult' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
random_id : randomIdS ,
reply_to_msg_id : replyToMsgId || undefined ,
query_id : options.queryId ,
id : options.resultId ,
clear_draft : options.clearDraft
} , sentRequestOptions ) ;
} else {
apiPromise = apiManager . invokeApiAfter ( 'messages.sendMedia' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
media : inputMedia ,
random_id : randomIdS ,
reply_to_msg_id : replyToMsgId || undefined ,
message : '' ,
clear_draft : options.clearDraft ,
schedule_date : options.scheduleDate ,
silent : options.silent
} , sentRequestOptions ) ;
}
apiPromise . then ( ( updates ) = > {
if ( updates . updates ) {
updates . updates . forEach ( ( update : any ) = > {
if ( update . _ == 'updateDraftMessage' ) {
update . local = true
}
} ) ;
}
apiUpdatesManager . processUpdateMessage ( updates ) ;
} , ( error ) = > {
toggleError ( true ) ;
} ) . finally ( ( ) = > {
if ( this . pendingAfterMsgs [ peerId ] === sentRequestOptions ) {
delete this . pendingAfterMsgs [ peerId ] ;
}
} ) ;
this . pendingAfterMsgs [ peerId ] = sentRequestOptions ;
}
this . beforeMessageSending ( message , { isScheduled : ! ! options . scheduleDate || undefined } ) ;
}
/ * p r i v a t e c h e c k S e n d O p t i o n s ( o p t i o n s : P a r t i a l < {
scheduleDate : number
} > ) {
if ( options . scheduleDate ) {
const minTimestamp = ( Date . now ( ) / 1000 | 0 ) + 10 ;
if ( options . scheduleDate <= minTimestamp ) {
delete options . scheduleDate ;
}
}
} * /
private beforeMessageSending ( message : any , options : Partial < { isGroupedItem : true , isScheduled : true } > = { } ) {
const messageId = message . id ;
const peerId = this . getMessagePeer ( message ) ;
const storage = options . isScheduled ? this . getScheduledMessagesStorage ( peerId ) : this . getMessagesStorage ( peerId ) ;
if ( options . isScheduled ) {
if ( ! options . isGroupedItem ) {
this . saveMessages ( [ message ] , { storage , isScheduled : true , isOutgoing : true } ) ;
rootScope . broadcast ( 'scheduled_new' , { peerId , mid : messageId } ) ;
}
} else {
const historyStorage = this . getHistoryStorage ( peerId ) ;
//historyStorage.pending.unshift(messageId);
historyStorage . history . unshift ( messageId ) ;
if ( ! options . isGroupedItem ) {
this . saveMessages ( [ message ] , { storage , isOutgoing : true } ) ;
rootScope . broadcast ( 'history_append' , { peerId , messageId , my : true } ) ;
this . setDialogTopMessage ( message ) ;
}
}
this . pendingByRandomId [ message . random_id ] = { peerId , tempId : messageId , storage } ;
if ( ! options . isGroupedItem ) {
setTimeout ( message . send , 0 ) ;
//setTimeout(message.send, 4000);
//setTimeout(message.send, 7000);
}
}
private generateReplyHeader ( replyToMsgId : number , replyToTopId? : number ) {
const header = {
_ : 'messageReplyHeader' ,
reply_to_msg_id : replyToMsgId ,
} as MessageReplyHeader ;
if ( replyToTopId && replyToTopId !== replyToMsgId ) {
header . reply_to_top_id = replyToTopId ;
}
return header ;
}
/ * *
* Generate correct from_id according to anonymous or broadcast
* /
public generateFromId ( peerId : number ) {
if ( peerId < 0 && ( appPeersManager . isBroadcast ( peerId ) || appPeersManager . getPeer ( peerId ) . admin_rights ? . pFlags ? . anonymous ) ) {
return undefined ;
} else {
return appPeersManager . getOutputPeer ( appUsersManager . getSelf ( ) . id ) ;
}
}
public generateFlags ( peerId : number ) {
const pFlags : any = { } ;
const fromId = appUsersManager . getSelf ( ) . id ;
if ( peerId !== fromId ) {
pFlags . out = true ;
if ( ! appPeersManager . isChannel ( peerId ) && ! appUsersManager . isBot ( peerId ) ) {
pFlags . unread = true ;
}
}
if ( appPeersManager . isBroadcast ( peerId ) ) {
pFlags . post = true ;
}
return pFlags ;
}
private setDialogIndexByMessage ( dialog : MTDialog.dialog , message : MyMessage ) {
if ( ! dialog . pFlags . pinned || ! dialog . index ) {
dialog . index = this . dialogsStorage . generateDialogIndex ( message . date ) ;
}
}
public setDialogTopMessage ( message : MyMessage , dialog : MTDialog.dialog = this . getDialogByPeerId ( message . peerId ) [ 0 ] ) {
if ( dialog ) {
dialog . top_message = message . mid ;
const historyStorage = this . getHistoryStorage ( message . peerId ) ;
historyStorage . maxId = message . mid ;
this . setDialogIndexByMessage ( dialog , message ) ;
this . newDialogsToHandle [ message . peerId ] = dialog ;
this . scheduleHandleNewDialogs ( ) ;
}
}
public cancelPendingMessage ( randomId : string ) {
const pendingData = this . pendingByRandomId [ randomId ] ;
this . log ( 'cancelPendingMessage' , randomId , pendingData ) ;
if ( pendingData ) {
const { peerId , tempId , storage } = pendingData ;
const historyStorage = this . getHistoryStorage ( peerId ) ;
const pos = historyStorage . pending . indexOf ( tempId ) ;
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateDeleteMessages' ,
messages : [ tempId ]
}
} ) ;
if ( pos !== - 1 ) {
historyStorage . pending . splice ( pos , 1 ) ;
}
delete this . pendingByRandomId [ randomId ] ;
delete storage [ tempId ] ;
return true ;
}
return false ;
}
public async getConversationsAll ( query = '' , folderId = 0 ) {
const limit = 100 , outDialogs : Dialog [ ] = [ ] ;
for ( ; folderId < 2 ; ++ folderId ) {
let offsetIndex = 0 ;
for ( ; ; ) {
const { dialogs } = await appMessagesManager . getConversations ( query , offsetIndex , limit , folderId ) ;
if ( dialogs . length ) {
outDialogs . push ( . . . dialogs ) ;
offsetIndex = dialogs [ dialogs . length - 1 ] . index || 0 ;
} else {
break ;
}
}
}
return outDialogs ;
}
public getConversations ( query = '' , offsetIndex? : number , limit = 20 , folderId = 0 ) {
const realFolderId = folderId > 1 ? 0 : folderId ;
let curDialogStorage = this . dialogsStorage . getFolder ( folderId ) ;
if ( query ) {
if ( ! limit || this . cachedResults . query !== query || this . cachedResults . folderId != folderId ) {
this . cachedResults . query = query ;
this . cachedResults . folderId = folderId ;
const results = searchIndexManager . search ( query , this . dialogsIndex ) ;
this . cachedResults . dialogs = [ ] ;
for ( const peerId in this . dialogsStorage . dialogs ) {
const dialog = this . dialogsStorage . dialogs [ peerId ] ;
if ( results [ dialog . peerId ] && dialog . folder_id == folderId ) {
this . cachedResults . dialogs . push ( dialog ) ;
}
}
this . cachedResults . dialogs . sort ( ( d1 , d2 ) = > d2 . index - d1 . index ) ;
this . cachedResults . count = this . cachedResults . dialogs . length ;
}
curDialogStorage = this . cachedResults . dialogs ;
} else {
this . cachedResults . query = '' ;
}
let offset = 0 ;
if ( offsetIndex > 0 ) {
for ( ; offset < curDialogStorage . length ; offset ++ ) {
if ( offsetIndex > curDialogStorage [ offset ] . index ) {
break ;
}
}
}
if ( query || this . dialogsStorage . allDialogsLoaded [ realFolderId ] || curDialogStorage . length >= offset + limit ) {
return Promise . resolve ( {
dialogs : curDialogStorage.slice ( offset , offset + limit ) ,
count : this.dialogsStorage.allDialogsLoaded [ realFolderId ] ? curDialogStorage.length : null ,
isEnd : this.dialogsStorage.allDialogsLoaded [ realFolderId ] && ( offset + limit ) >= curDialogStorage . length
} ) ;
}
return this . getTopMessages ( limit , realFolderId ) . then ( totalCount = > {
//const curDialogStorage = this.dialogsStorage[folderId];
offset = 0 ;
if ( offsetIndex > 0 ) {
for ( ; offset < curDialogStorage . length ; offset ++ ) {
if ( offsetIndex > curDialogStorage [ offset ] . index ) {
break ;
}
}
}
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogsStorage.dialogs.length);
return {
dialogs : curDialogStorage.slice ( offset , offset + limit ) ,
count : totalCount ,
isEnd : this.dialogsStorage.allDialogsLoaded [ realFolderId ] && ( offset + limit ) >= curDialogStorage . length
} ;
} ) ;
}
public isHistoryUnread ( peerId : number , threadId? : number ) {
const historyStorage = this . getHistoryStorage ( peerId , threadId ) ;
if ( threadId ) {
const chatHistoryStorage = this . getHistoryStorage ( peerId ) ;
const readMaxId = Math . max ( chatHistoryStorage . readMaxId , historyStorage . readMaxId ) ;
const message = this . getMessageByPeer ( peerId , historyStorage . maxId ) ;
return ! message . pFlags . out && readMaxId < historyStorage . maxId ;
} else {
const message = this . getMessageByPeer ( peerId , historyStorage . maxId ) ;
return ! message . pFlags . out && ( peerId > 0 ? Math . max ( historyStorage . readMaxId , historyStorage . readOutboxMaxId ) : historyStorage . readMaxId ) < historyStorage . maxId ;
}
}
public getTopMessages ( limit : number , folderId : number ) : Promise < number > {
const dialogs = this . dialogsStorage . getFolder ( folderId ) ;
let offsetId = 0 ;
let offsetDate = 0 ;
let offsetPeerId = 0 ;
let offsetIndex = 0 ;
let flags = 0 ;
if ( this . dialogsStorage . dialogsOffsetDate [ folderId ] ) {
offsetDate = this . dialogsStorage . dialogsOffsetDate [ folderId ] + serverTimeManager . serverTimeOffset ;
offsetIndex = this . dialogsStorage . dialogsOffsetDate [ folderId ] * 0x10000 ;
//flags |= 1; // means pinned already loaded
}
/ * i f ( t h i s . d i a l o g s S t o r a g e . d i a l o g s O f f s e t D a t e [ 0 ] ) {
flags |= 1 ; // means pinned already loaded
} * /
//if(folderId > 0) {
//flags |= 1;
flags |= 2 ;
//}
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
return apiManager . invokeApi ( 'messages.getDialogs' , {
flags ,
folder_id : folderId ,
offset_date : offsetDate ,
offset_id : offsetId ,
offset_peer : appPeersManager.getInputPeerById ( offsetPeerId ) ,
limit ,
hash : 0
} , {
//timeout: APITIMEOUT,
noErrorBox : true
} ) . then ( ( dialogsResult ) = > {
if ( dialogsResult . _ == 'messages.dialogsNotModified' ) return null ;
//this.log.error('messages.getDialogs result:', dialogsResult.dialogs, {...dialogsResult.dialogs[0]});
/ * i f ( ! o f f s e t D a t e ) {
telegramMeWebService . setAuthorized ( true ) ;
} * /
appUsersManager . saveApiUsers ( dialogsResult . users ) ;
appChatsManager . saveApiChats ( dialogsResult . chats ) ;
this . saveMessages ( dialogsResult . messages ) ;
let maxSeenIdIncremented = offsetDate ? true : false ;
let hasPrepend = false ;
const noIdsDialogs : { [ peerId : number ] : Dialog } = { } ;
( dialogsResult . dialogs as Dialog [ ] ) . forEachReverse ( dialog = > {
//const d = Object.assign({}, dialog);
// ! нужно передавать folderId, так как по папке != 0 нет свойства folder_id
this . saveConversation ( dialog , dialog . folder_id ? ? folderId ) ;
/ * i f ( d i a l o g . p e e r I d = = - 1 2 1 3 5 1 1 2 9 4 ) {
this . log . error ( 'lun bot' , folderId , d ) ;
} * /
if ( offsetIndex && dialog . index > offsetIndex ) {
this . newDialogsToHandle [ dialog . peerId ] = dialog ;
hasPrepend = true ;
}
// ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет
// ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога
if ( ! this . getLocalMessageId ( dialog . read_inbox_max_id ) && ! this . getLocalMessageId ( dialog . read_outbox_max_id ) ) {
noIdsDialogs [ dialog . peerId ] = dialog ;
this . log . error ( 'noIdsDialogs' , dialog ) ;
/ * i f ( d i a l o g . p e e r I d = = - 1 2 1 3 5 1 1 2 9 4 ) {
this . log . error ( 'lun bot' , folderId ) ;
} * /
}
if ( ! maxSeenIdIncremented &&
! appPeersManager . isChannel ( appPeersManager . getPeerId ( dialog . peer ) ) ) {
this . incrementMaxSeenId ( dialog . top_message ) ;
maxSeenIdIncremented = true ;
}
} ) ;
if ( Object . keys ( noIdsDialogs ) . length ) {
//setTimeout(() => { // test bad situation
this . reloadConversation ( Object . keys ( noIdsDialogs ) . map ( id = > + id ) ) . then ( ( ) = > {
rootScope . broadcast ( 'dialogs_multiupdate' , noIdsDialogs ) ;
for ( let peerId in noIdsDialogs ) {
rootScope . broadcast ( 'dialog_unread' , { peerId : + peerId } ) ;
}
} ) ;
//}, 10e3);
}
const count = ( dialogsResult as MessagesDialogs . messagesDialogsSlice ) . count ;
if ( ! dialogsResult . dialogs . length ||
! count ||
dialogs . length >= count ) {
this . dialogsStorage . allDialogsLoaded [ folderId ] = true ;
}
if ( hasPrepend ) {
this . scheduleHandleNewDialogs ( ) ;
} else {
rootScope . broadcast ( 'dialogs_multiupdate' , { } ) ;
}
return count ;
} ) ;
}
public forwardMessages ( peerId : number , fromPeerId : number , msgIds : number [ ] , options : Partial < {
withMyScore : true ,
silent : true ,
scheduleDate : number
} > = { } ) {
peerId = appPeersManager . getPeerMigratedTo ( peerId ) || peerId ;
msgIds = msgIds . slice ( ) . sort ( ( a , b ) = > a - b ) . map ( mid = > this . getLocalMessageId ( mid ) ) ;
const randomIds : string [ ] = msgIds . map ( ( ) = > randomLong ( ) ) ;
const sentRequestOptions : InvokeApiOptions = { } ;
if ( this . pendingAfterMsgs [ peerId ] ) {
sentRequestOptions . afterMessageId = this . pendingAfterMsgs [ peerId ] . messageId ;
}
const promise = apiManager . invokeApiAfter ( 'messages.forwardMessages' , {
from_peer : appPeersManager.getInputPeerById ( fromPeerId ) ,
id : msgIds ,
random_id : randomIds ,
to_peer : appPeersManager.getInputPeerById ( peerId ) ,
with_my_score : options.withMyScore ,
silent : options.silent ,
schedule_date : options.scheduleDate
} , sentRequestOptions ) . then ( ( updates ) = > {
apiUpdatesManager . processUpdateMessage ( updates ) ;
} , ( ) = > { } ) . then ( ( ) = > {
if ( this . pendingAfterMsgs [ peerId ] === sentRequestOptions ) {
delete this . pendingAfterMsgs [ peerId ] ;
}
} ) ;
this . pendingAfterMsgs [ peerId ] = sentRequestOptions ;
return promise ;
}
public getMessageFromStorage ( storage : MessagesStorage , messageId : number ) {
return storage && storage [ messageId ] || {
_ : 'messageEmpty' ,
id : messageId ,
deleted : true ,
pFlags : { }
} ;
}
private createMessageStorage() {
const storage : MessagesStorage = { } as any ;
/ * l e t n u m = 0 ;
Object . defineProperty ( storage , 'num' , {
get : ( ) = > ++ num ,
set : ( _num : number ) = > num = _num ,
enumerable : false
} ) ;
Object . defineProperty ( storage , 'generateIndex' , {
value : ( message : any ) = > {
if ( message . index === undefined ) {
message . index = ( message . date * 0x10000 ) + ( storage . num & 0xFFFF ) ;
}
} ,
enumerable : false
} ) ; * /
return storage ;
}
public getMessagesStorage ( peerId : number ) {
return this . messagesStorageByPeerId [ peerId ] ? ? ( this . messagesStorageByPeerId [ peerId ] = this . createMessageStorage ( ) ) ;
}
public getMessageById ( messageId : number ) {
for ( const peerId in this . messagesStorageByPeerId ) {
if ( appPeersManager . isChannel ( + peerId ) ) {
continue ;
}
const message = this . messagesStorageByPeerId [ peerId ] [ messageId ] ;
if ( message ) {
return message ;
}
}
return this . getMessageFromStorage ( null , messageId ) ;
}
public getMessageByPeer ( peerId : number , messageId : number ) {
if ( ! peerId ) {
return this . getMessageById ( messageId ) ;
}
return this . getMessageFromStorage ( this . getMessagesStorage ( peerId ) , messageId ) ;
}
public getMessagePeer ( message : any ) : number {
const toId = message . peer_id && appPeersManager . getPeerId ( message . peer_id ) || 0 ;
return toId ;
}
public getDialogByPeerId ( peerId : number ) : [ Dialog , number ] | [ ] {
return this . dialogsStorage . getDialog ( peerId ) ;
}
public reloadConversation ( peerId : number | number [ ] ) {
[ ] . concat ( peerId ) . forEach ( peerId = > {
if ( ! this . reloadConversationsPeers . includes ( peerId ) ) {
this . reloadConversationsPeers . push ( peerId ) ;
this . log ( 'will reloadConversation' , peerId ) ;
}
} ) ;
if ( this . reloadConversationsPromise ) return this . reloadConversationsPromise ;
return this . reloadConversationsPromise = new Promise ( ( resolve , reject ) = > {
setTimeout ( ( ) = > {
const peers = this . reloadConversationsPeers . map ( peerId = > appPeersManager . getInputDialogPeerById ( peerId ) ) ;
this . reloadConversationsPeers . length = 0 ;
apiManager . invokeApi ( 'messages.getPeerDialogs' , { peers } ) . then ( ( result ) = > {
this . applyConversations ( result ) ;
resolve ( ) ;
} , reject ) . finally ( ( ) = > {
this . reloadConversationsPromise = null ;
} ) ;
} , 0 ) ;
} ) ;
}
private doFlushHistory ( inputPeer : any , justClear? : true ) : Promise < true > {
return apiManager . invokeApi ( 'messages.deleteHistory' , {
just_clear : justClear ,
peer : inputPeer ,
max_id : 0
} ) . then ( ( affectedHistory ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updatePts' ,
pts : affectedHistory.pts ,
pts_count : affectedHistory.pts_count
}
} ) ;
if ( ! affectedHistory . offset ) {
return true ;
}
return this . doFlushHistory ( inputPeer , justClear ) ;
} )
}
public async flushHistory ( peerId : number , justClear? : true ) {
if ( appPeersManager . isChannel ( peerId ) ) {
const promise = this . getHistory ( peerId , 0 , 1 ) ;
const historyResult = promise instanceof Promise ? await promise : promise ;
const channelId = - peerId ;
const maxId = historyResult . history [ 0 ] || 0 ;
return apiManager . invokeApi ( 'channels.deleteHistory' , {
channel : appChatsManager.getChannelInput ( channelId ) ,
max_id : maxId
} ) . then ( ( ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateChannelAvailableMessages' ,
channel_id : channelId ,
available_min_id : maxId
}
} ) ;
return true ;
} ) ;
}
return this . doFlushHistory ( appPeersManager . getInputPeerById ( peerId ) , justClear ) . then ( ( ) = > {
delete this . historiesStorage [ peerId ] ;
delete this . messagesStorageByPeerId [ peerId ] ;
if ( justClear ) {
rootScope . broadcast ( 'dialog_flush' , { peerId } ) ;
} else {
this . dialogsStorage . dropDialog ( peerId ) ;
rootScope . broadcast ( 'dialog_drop' , { peerId } ) ;
}
} ) ;
}
public hidePinnedMessages ( peerId : number ) {
return Promise . all ( [
appStateManager . getState ( ) ,
this . getPinnedMessage ( peerId )
] )
. then ( ( [ state , pinned ] ) = > {
state . hiddenPinnedMessages [ peerId ] = pinned . maxId ;
rootScope . broadcast ( 'peer_pinned_hidden' , { peerId , maxId : pinned.maxId } ) ;
} ) ;
}
public getPinnedMessage ( peerId : number ) {
const p = this . pinnedMessages [ peerId ] ? ? ( this . pinnedMessages [ peerId ] = { } ) ;
if ( p . promise ) return p . promise ;
else if ( p . maxId ) return Promise . resolve ( p ) ;
return p . promise = this . getSearch ( peerId , '' , { _ : 'inputMessagesFilterPinned' } , 0 , 1 ) . then ( result = > {
p . count = result . count ;
p . maxId = result . history [ 0 ] ? . mid ;
return p ;
} ) . finally ( ( ) = > {
delete p . promise ;
} ) ;
}
public updatePinnedMessage ( peerId : number , mid : number , unpin? : true , silent? : true , oneSide? : true ) {
return apiManager . invokeApi ( 'messages.updatePinnedMessage' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
unpin ,
silent ,
pm_oneside : oneSide ,
id : this.getLocalMessageId ( mid )
} ) . then ( updates = > {
this . log ( 'pinned updates:' , updates ) ;
apiUpdatesManager . processUpdateMessage ( updates ) ;
} ) ;
}
public unpinAllMessages ( peerId : number ) : Promise < boolean > {
return apiManager . invokeApi ( 'messages.unpinAllMessages' , {
peer : appPeersManager.getInputPeerById ( peerId )
} ) . then ( affectedHistory = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updatePts' ,
pts : affectedHistory.pts ,
pts_count : affectedHistory.pts_count
}
} ) ;
if ( ! affectedHistory . offset ) {
const storage = this . getMessagesStorage ( peerId ) ;
for ( const mid in storage ) {
const message = storage [ mid ] ;
if ( message . pFlags . pinned ) {
delete message . pFlags . pinned ;
}
}
rootScope . broadcast ( 'peer_pinned_messages' , { peerId , unpinAll : true } ) ;
delete this . pinnedMessages [ peerId ] ;
return true ;
}
return this . unpinAllMessages ( peerId ) ;
} ) ;
}
public getAlbumText ( grouped_id : string ) {
const group = this . groupedMessagesStorage [ grouped_id ] ;
let foundMessages = 0 , message : string , totalEntities : MessageEntity [ ] , entities : MessageEntity [ ] ;
for ( const i in group ) {
const m = group [ i ] ;
if ( m . message ) {
if ( ++ foundMessages > 1 ) break ;
message = m . message ;
totalEntities = m . totalEntities ;
entities = m . entities ;
}
}
if ( foundMessages > 1 ) {
message = undefined ;
totalEntities = undefined ;
entities = undefined ;
}
return { message , entities , totalEntities } ;
}
public getMidsByAlbum ( grouped_id : string ) {
return getObjectKeysAndSort ( this . groupedMessagesStorage [ grouped_id ] , 'asc' ) ;
//return Object.keys(this.groupedMessagesStorage[grouped_id]).map(id => +id).sort((a, b) => a - b);
}
public getMidsByMessage ( message : any ) {
if ( message ? . grouped_id ) return this . getMidsByAlbum ( message . grouped_id ) ;
else return [ message . mid ] ;
}
public generateTempMessageId ( peerId : number ) {
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
return this . generateMessageId ( dialog ? . top_message || 0 , true ) ;
}
public generateMessageId ( messageId : number , temp = false ) {
const q = AppMessagesManager . MESSAGE_ID_OFFSET ;
const num = temp ? ++ this . tempNum : 0 ;
if ( messageId >= q ) {
if ( temp ) {
return messageId + ( num & ( AppMessagesManager . MESSAGE_ID_INCREMENT - 1 ) ) ;
}
return messageId ;
}
return q + ( messageId * AppMessagesManager . MESSAGE_ID_INCREMENT + ( num & ( AppMessagesManager . MESSAGE_ID_INCREMENT - 1 ) ) ) ;
}
/ * *
* * will ignore outgoing offset
* /
public getLocalMessageId ( messageId : number ) {
const q = AppMessagesManager . MESSAGE_ID_OFFSET ;
if ( messageId <= q ) {
return messageId ;
}
const l = AppMessagesManager . MESSAGE_ID_INCREMENT - 1 ;
const used = messageId & l ;
if ( used !== l ) {
messageId -= used + 1 ;
}
return ( messageId - q ) / AppMessagesManager . MESSAGE_ID_INCREMENT ;
}
public incrementMessageId ( messageId : number , increment : number ) {
return this . generateMessageId ( this . getLocalMessageId ( messageId ) + increment ) ;
}
public saveMessages ( messages : any [ ] , options : Partial < {
storage : MessagesStorage ,
isScheduled : true ,
isOutgoing : true
} > = { } ) {
let groups : Set < string > ;
messages . forEach ( ( message ) = > {
if ( message . pFlags === undefined ) {
message . pFlags = { } ;
}
if ( message . _ == 'messageEmpty' ) {
return ;
}
// * exclude from state
// defineNotNumerableProperties(message, ['rReply', 'mid', 'savedFrom', 'fwdFromId', 'fromId', 'peerId', 'reply_to_mid', 'viaBotId']);
const peerId = this . getMessagePeer ( message ) ;
const storage = options . storage || this . getMessagesStorage ( peerId ) ;
const isChannel = message . peer_id . _ == 'peerChannel' ;
const channelId = isChannel ? - peerId : 0 ;
const isBroadcast = isChannel && appChatsManager . isBroadcast ( channelId ) ;
if ( options . isScheduled ) {
message . pFlags . is_scheduled = true ;
}
if ( options . isOutgoing ) {
message . pFlags . is_outgoing = true ;
}
const mid = this . generateMessageId ( message . id ) ;
message . mid = mid ;
if ( message . grouped_id ) {
const storage = this . groupedMessagesStorage [ message . grouped_id ] ? ? ( this . groupedMessagesStorage [ message . grouped_id ] = { } ) ;
storage [ mid ] = message ;
}
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
if ( dialog && mid > 0 ) {
if ( mid > dialog [ message . pFlags . out
? 'read_outbox_max_id'
: 'read_inbox_max_id' ] ) {
message . pFlags . unread = true ;
}
}
// this.log(dT(), 'msg unread', mid, apiMessage.pFlags.out, dialog && dialog[apiMessage.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id'])
if ( message . reply_to ) {
if ( message . reply_to . reply_to_msg_id ) {
message . reply_to . reply_to_msg_id = message . reply_to_mid = this . generateMessageId ( message . reply_to . reply_to_msg_id ) ;
}
if ( message . reply_to . reply_to_top_id ) message . reply_to . reply_to_top_id = this . generateMessageId ( message . reply_to . reply_to_top_id ) ;
}
if ( message . replies ) {
if ( message . replies . max_id ) message . replies . max_id = this . generateMessageId ( message . replies . max_id ) ;
if ( message . replies . read_max_id ) message . replies . read_max_id = this . generateMessageId ( message . replies . read_max_id ) ;
}
const overwriting = ! ! message . peerId ;
if ( ! overwriting ) {
message . date -= serverTimeManager . serverTimeOffset ;
}
//storage.generateIndex(message);
const myId = appUsersManager . getSelf ( ) . id ;
message . peerId = peerId ;
if ( message . peerId == myId /* && !message.from_id && !message.fwd_from */ ) {
message . fromId = message . fwd_from ? ( message . fwd_from . from_id ? appPeersManager . getPeerId ( message . fwd_from . from_id ) : 0 ) : myId ;
} else {
//message.fromId = message.pFlags.post || (!message.pFlags.out && !message.from_id) ? peerId : appPeersManager.getPeerId(message.from_id);
message . fromId = message . pFlags . post || ! message . from_id ? peerId : appPeersManager.getPeerId ( message . from_id ) ;
}
const fwdHeader = message . fwd_from ;
if ( fwdHeader ) {
//if(peerId == myID) {
if ( fwdHeader . saved_from_peer && fwdHeader . saved_from_msg_id ) {
const savedFromPeerId = appPeersManager . getPeerId ( fwdHeader . saved_from_peer ) ;
//const savedFromMid = fwdHeader.saved_from_msg_id;
const savedFromMid = this . generateMessageId ( fwdHeader . saved_from_msg_id ) ;
message . savedFrom = savedFromPeerId + '_' + savedFromMid ;
}
/ * i f ( p e e r I d < 0 | | p e e r I d = = m y I D ) {
message . fromId = appPeersManager . getPeerID ( ! message . from_id || deepEqual ( message . from_id , fwdHeader . from_id ) ? fwdHeader.from_id : message.from_id ) ;
} * /
/ * } e l s e {
apiMessage . fwdPostID = fwdHeader . channel_post ;
} * /
message . fwdFromId = appPeersManager . getPeerId ( fwdHeader . from_id ) ;
if ( ! overwriting ) {
fwdHeader . date -= serverTimeManager . serverTimeOffset ;
}
}
if ( message . via_bot_id > 0 ) {
message . viaBotId = message . via_bot_id ;
}
const mediaContext : ReferenceContext = {
type : 'message' ,
peerId ,
messageId : mid
} ;
if ( message . media ) {
switch ( message . media . _ ) {
case 'messageMediaEmpty' :
delete message . media ;
break ;
case 'messageMediaPhoto' :
if ( message . media . ttl_seconds ) {
message . media = { _ : 'messageMediaUnsupportedWeb' } ;
} else {
message . media . photo = appPhotosManager . savePhoto ( message . media . photo , mediaContext ) ;
}
break ;
case 'messageMediaPoll' :
message . media . poll = appPollsManager . savePoll ( message . media . poll , message . media . results ) ;
break ;
case 'messageMediaDocument' :
if ( message . media . ttl_seconds ) {
message . media = { _ : 'messageMediaUnsupportedWeb' } ;
} else {
message . media . document = appDocsManager . saveDoc ( message . media . document , mediaContext ) ; // 11.04.2020 warning
}
break ;
case 'messageMediaWebPage' :
message . media . webpage = appWebPagesManager . saveWebPage ( message . media . webpage , message . mid , mediaContext ) ;
break ;
/ * c a s e ' m e s s a g e M e d i a G a m e ' :
AppGamesManager . saveGame ( apiMessage . media . game , apiMessage . mid , mediaContext ) ;
apiMessage . media . handleMessage = true ;
break ; * /
case 'messageMediaInvoice' :
message . media = { _ : 'messageMediaUnsupportedWeb' } ;
break ;
case 'messageMediaGeoLive' :
message . media . _ = 'messageMediaGeo' ;
break ;
}
}
if ( message . action ) {
let migrateFrom : number ;
let migrateTo : number ;
switch ( message . action . _ ) {
//case 'messageActionChannelEditPhoto':
case 'messageActionChatEditPhoto' :
message . action . photo = appPhotosManager . savePhoto ( message . action . photo , mediaContext ) ;
if ( isBroadcast ) { // ! messageActionChannelEditPhoto не существует в принципе, это используется для перевода.
message . action . _ = 'messageActionChannelEditPhoto' ;
}
break ;
case 'messageActionChatEditTitle' :
if ( isBroadcast ) {
message . action . _ = 'messageActionChannelEditTitle' ;
}
break ;
case 'messageActionChatDeletePhoto' :
if ( isBroadcast ) {
message . action . _ = 'messageActionChannelDeletePhoto' ;
}
break ;
case 'messageActionChatAddUser' :
if ( message . action . users . length == 1 ) {
message . action . user_id = message . action . users [ 0 ] ;
if ( message . fromId == message . action . user_id ) {
if ( isChannel ) {
message . action . _ = 'messageActionChatJoined' ;
} else {
message . action . _ = 'messageActionChatReturn' ;
}
}
} else if ( message . action . users . length > 1 ) {
message . action . _ = 'messageActionChatAddUsers' ;
}
break ;
case 'messageActionChatDeleteUser' :
if ( message . fromId == message . action . user_id ) {
message . action . _ = 'messageActionChatLeave' ;
}
break ;
case 'messageActionChannelMigrateFrom' :
migrateFrom = - message . action . chat_id ;
migrateTo = - channelId ;
break
case 'messageActionChatMigrateTo' :
migrateFrom = - channelId ;
migrateTo = - message . action . channel_id ;
break ;
case 'messageActionHistoryClear' :
//apiMessage.deleted = true;
message . clear_history = true ;
delete message . pFlags . out ;
delete message . pFlags . unread ;
break ;
case 'messageActionPhoneCall' :
delete message . fromId ;
message . action . type =
( message . pFlags . out ? 'out_' : 'in_' ) +
(
message . action . reason . _ == 'phoneCallDiscardReasonMissed' ||
message . action . reason . _ == 'phoneCallDiscardReasonBusy'
? 'missed'
: 'ok'
) ;
break ;
}
if ( migrateFrom &&
migrateTo &&
! this . migratedFromTo [ migrateFrom ] &&
! this . migratedToFrom [ migrateTo ] ) {
this . migrateChecks ( migrateFrom , migrateTo ) ;
}
}
if ( message . grouped_id ) {
if ( ! groups ) {
groups = new Set ( ) ;
}
groups . add ( message . grouped_id ) ;
} else {
message . rReply = this . getRichReplyText ( message ) ;
}
if ( message . message && message . message . length && ! message . totalEntities ) {
const myEntities = RichTextProcessor . parseEntities ( message . message ) ;
const apiEntities = message . entities || [ ] ;
message . totalEntities = RichTextProcessor . mergeEntities ( apiEntities , myEntities ) ; // ! only in this order, otherwise bold and emoji formatting won't work
}
storage [ mid ] = message ;
} ) ;
if ( groups ) {
for ( const groupId of groups ) {
const mids = this . groupedMessagesStorage [ groupId ] ;
for ( const mid in mids ) {
const message = this . groupedMessagesStorage [ groupId ] [ mid ] ;
message . rReply = this . getRichReplyText ( message ) ;
}
}
}
}
public getRichReplyText ( message : any , text : string = message . message ) {
let messageText = '' ;
if ( message . media ) {
if ( message . grouped_id ) {
text = this . getAlbumText ( message . grouped_id ) . message ;
messageText += '<i>Album' + ( text ? ', ' : '' ) + '</i>' ;
} else switch ( message . media . _ ) {
case 'messageMediaPhoto' :
messageText += '<i>Photo' + ( message . message ? ', ' : '' ) + '</i>' ;
break ;
case 'messageMediaDice' :
messageText += RichTextProcessor . wrapEmojiText ( message . media . emoticon ) ;
break ;
case 'messageMediaGeo' :
messageText += '<i>Geolocation</i>' ;
break ;
case 'messageMediaPoll' :
messageText += '<i>' + message . media . poll . rReply + '</i>' ;
break ;
case 'messageMediaContact' :
messageText += '<i>Contact</i>' ;
break ;
case 'messageMediaDocument' :
let document = message . media . document ;
if ( document . type == 'video' ) {
messageText = '<i>Video' + ( message . message ? ', ' : '' ) + '</i>' ;
} else if ( document . type == 'voice' ) {
messageText = '<i>Voice message</i>' ;
} else if ( document . type == 'gif' ) {
messageText = '<i>GIF' + ( message . message ? ', ' : '' ) + '</i>' ;
} else if ( document . type == 'round' ) {
messageText = '<i>Video message' + ( message . message ? ', ' : '' ) + '</i>' ;
} else if ( document . type == 'sticker' ) {
messageText = ( document . stickerEmoji || '' ) + '<i>Sticker</i>' ;
text = '' ;
} else {
messageText = '<i>' + document . file_name + ( message . message ? ', ' : '' ) + '</i>' ;
}
break ;
default :
//messageText += message.media._;
///////this.log.warn('Got unknown message.media type!', message);
break ;
}
}
if ( message . action ) {
const str = this . wrapMessageActionText ( message ) ;
messageText = str ? '<i>' + str + '</i>' : '' ;
}
let messageWrapped = '' ;
if ( text ) {
// * 80 for chatlist in landscape orientation
text = limitSymbols ( text , 75 , 80 ) ;
const entities = RichTextProcessor . parseEntities ( text . replace ( /\n/g , ' ' ) ) ;
messageWrapped = RichTextProcessor . wrapRichText ( text , {
noLinebreaks : true ,
entities ,
noLinks : true ,
noTextFormat : true
} ) ;
}
return messageText + messageWrapped ;
}
public wrapMessageActionText ( message : any ) {
const action = message . action as MessageAction ;
let str = '' ;
if ( ( action as MessageAction . messageActionCustomAction ) . message ) {
str = RichTextProcessor . wrapRichText ( ( action as MessageAction . messageActionCustomAction ) . message , { noLinebreaks : true } ) ;
} else {
let _ = action . _ ;
let suffix = '' ;
let l = '' ;
const getNameDivHTML = ( peerId : number ) = > {
const title = appPeersManager . getPeerTitle ( peerId ) ;
return title ? ` <div class="name inline" data-peer-id=" ${ peerId } "> ${ title } </div> ` : '' ;
} ;
switch ( action . _ ) {
case "messageActionPhoneCall" : {
_ += '.' + ( action as any ) . type ;
const duration = action . duration ;
if ( duration ) {
const d : string [ ] = [ ] ;
d . push ( duration % 60 + ' s' ) ;
if ( duration >= 60 ) d . push ( ( duration / 60 | 0 ) + ' min' ) ;
//if(duration >= 3600) d.push((duration / 3600 | 0) + ' h');
suffix = ' (' + d . reverse ( ) . join ( ' ' ) + ')' ;
}
return langPack [ _ ] + suffix ;
}
case 'messageActionChatDeleteUser' :
// @ts-ignore
case 'messageActionChatAddUsers' :
case 'messageActionChatAddUser' : {
const users : number [ ] = ( action as MessageAction . messageActionChatAddUser ) . users || [ ( action as MessageAction . messageActionChatDeleteUser ) . user_id ] ;
l = langPack [ _ ] . replace ( '{}' , users . map ( ( userId : number ) = > getNameDivHTML ( userId ) ) . join ( ', ' ) ) ;
break ;
}
case 'messageActionBotAllowed' : {
const anchorHTML = RichTextProcessor . wrapRichText ( action . domain , {
entities : [ {
_ : 'messageEntityUrl' ,
length : action.domain.length ,
offset : 0
} ]
} ) ;
l = langPack [ _ ] . replace ( '{}' , anchorHTML ) ;
break ;
}
default :
str = langPack [ _ ] || ` [ ${ action . _ } ] ` ;
break ;
}
if ( ! l ) {
l = langPack [ _ ] ;
if ( ! l ) {
l = '[' + _ + ']' ;
}
}
str = l [ 0 ] . toUpperCase ( ) == l [ 0 ] ? l : getNameDivHTML ( message . fromId ) + l + ( suffix ? ' ' : '' ) ;
}
//this.log('message action:', action);
return str ;
}
public editPeerFolders ( peerIds : number [ ] , folderId : number ) {
apiManager . invokeApi ( 'folders.editPeerFolders' , {
folder_peers : peerIds.map ( peerId = > {
return {
_ : 'inputFolderPeer' ,
peer : appPeersManager.getInputPeerById ( peerId ) ,
folder_id : folderId
} ;
} )
} ) . then ( updates = > {
this . log ( 'editPeerFolders updates:' , updates ) ;
apiUpdatesManager . processUpdateMessage ( updates ) ; // WARNING! возможно тут нужно добавлять channelId, и вызывать апдейт для каждого канала отдельно
} ) ;
}
public toggleDialogPin ( peerId : number , filterId? : number ) {
if ( filterId > 1 ) {
this . filtersStorage . toggleDialogPin ( peerId , filterId ) ;
return ;
}
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
if ( ! dialog ) return Promise . reject ( ) ;
const pinned = dialog . pFlags ? . pinned ? undefined : true ;
return apiManager . invokeApi ( 'messages.toggleDialogPin' , {
peer : appPeersManager.getInputDialogPeerById ( peerId ) ,
pinned
} ) . then ( bool = > {
if ( bool ) {
const pFlags : Update.updateDialogPinned [ 'pFlags' ] = pinned ? { pinned } : { } ;
this . handleUpdate ( {
_ : 'updateDialogPinned' ,
peer : appPeersManager.getDialogPeer ( peerId ) ,
folder_id : filterId ,
pFlags
} ) ;
}
} ) ;
}
public markDialogUnread ( peerId : number , read? : true ) {
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
if ( ! dialog ) return Promise . reject ( ) ;
const unread = read || dialog . pFlags ? . unread_mark ? undefined : true ;
return apiManager . invokeApi ( 'messages.markDialogUnread' , {
peer : appPeersManager.getInputDialogPeerById ( peerId ) ,
unread
} ) . then ( bool = > {
if ( bool ) {
const pFlags : Update.updateDialogUnreadMark [ 'pFlags' ] = unread ? { unread } : { } ;
this . handleUpdate ( {
_ : 'updateDialogUnreadMark' ,
peer : appPeersManager.getDialogPeer ( peerId ) ,
pFlags
} ) ;
}
} ) ;
}
public migrateChecks ( migrateFrom : number , migrateTo : number ) {
if ( ! this . migratedFromTo [ migrateFrom ] &&
! this . migratedToFrom [ migrateTo ] &&
appChatsManager . hasChat ( - migrateTo ) ) {
const fromChat = appChatsManager . getChat ( - migrateFrom ) ;
if ( fromChat &&
fromChat . migrated_to &&
fromChat . migrated_to . channel_id == - migrateTo ) {
this . migratedFromTo [ migrateFrom ] = migrateTo ;
this . migratedToFrom [ migrateTo ] = migrateFrom ;
setTimeout ( ( ) = > {
const dropped = this . dialogsStorage . dropDialog ( migrateFrom ) ;
if ( dropped . length ) {
rootScope . broadcast ( 'dialog_drop' , { peerId : migrateFrom , dialog : dropped [ 0 ] } ) ;
}
rootScope . broadcast ( 'dialog_migrate' , { migrateFrom , migrateTo } ) ;
} , 100 ) ;
}
}
}
public canMessageBeEdited ( message : any , kind : 'text' | 'poll' ) {
const goodMedias = [
'messageMediaPhoto' ,
'messageMediaDocument' ,
'messageMediaWebPage' ,
'messageMediaPending'
] ;
if ( kind == 'poll' ) {
goodMedias . push ( 'messageMediaPoll' ) ;
}
if ( message . _ != 'message' ||
message . deleted ||
message . fwd_from ||
message . via_bot_id ||
message . media && goodMedias . indexOf ( message . media . _ ) == - 1 ||
message . fromId && appUsersManager . isBot ( message . fromId ) ) {
return false ;
}
if ( message . media &&
message . media . _ == 'messageMediaDocument' &&
message . media . document . sticker ) {
return false ;
}
return true ;
}
public canEditMessage ( message : any , kind : 'text' | 'poll' = 'text' ) {
if ( ! message || ! this . canMessageBeEdited ( message , kind ) ) {
return false ;
}
// * second rule for saved messages, because there is no 'out' flag
if ( message . pFlags . out || this . getMessagePeer ( message ) == appUsersManager . getSelf ( ) . id ) {
return true ;
}
if ( ( message . date < tsNow ( true ) - ( 2 * 86400 ) && message . media ? . _ != 'messageMediaPoll' ) || ! message . pFlags . out ) {
return false ;
}
return true ;
}
public canDeleteMessage ( message : any ) {
return message && (
message . peerId > 0
|| message . fromId == rootScope . myId
|| appChatsManager . getChat ( message . peerId ) . _ == 'chat'
|| appChatsManager . hasRights ( message . peerId , 'deleteRevoke' )
) ;
}
public applyConversations ( dialogsResult : MessagesPeerDialogs.messagesPeerDialogs ) {
// * В эту функцию попадут только те диалоги, в которых есть read_inbox_max_id и read_outbox_max_id, в отличие от тех, что будут в getTopMessages
// ! fix 'dialogFolder', maybe there is better way to do it, this only can happen by 'messages.getPinnedDialogs' by folder_id: 0
dialogsResult . dialogs . forEachReverse ( ( dialog , idx ) = > {
if ( dialog . _ == 'dialogFolder' ) {
dialogsResult . dialogs . splice ( idx , 1 ) ;
}
} ) ;
appUsersManager . saveApiUsers ( dialogsResult . users ) ;
appChatsManager . saveApiChats ( dialogsResult . chats ) ;
this . saveMessages ( dialogsResult . messages ) ;
//this.log('applyConversation', dialogsResult);
const updatedDialogs : { [ peerId : number ] : Dialog } = { } ;
( dialogsResult . dialogs as Dialog [ ] ) . forEach ( ( dialog ) = > {
const peerId = appPeersManager . getPeerId ( dialog . peer ) ;
let topMessage = dialog . top_message ;
const topPendingMessage = this . pendingTopMsgs [ peerId ] ;
if ( topPendingMessage ) {
if ( ! topMessage
|| ( this . getMessageByPeer ( peerId , topPendingMessage ) as MyMessage ) . date > ( this . getMessageByPeer ( peerId , topMessage ) as MyMessage ) . date ) {
dialog . top_message = topMessage = topPendingMessage ;
this . getHistoryStorage ( peerId ) . maxId = topPendingMessage ;
}
}
/ * c o n s t d = O b j e c t . a s s i g n ( { } , d i a l o g ) ;
if ( peerId == 239602833 ) {
this . log . error ( 'applyConversation lun' , dialog , d ) ;
} * /
if ( topMessage ) {
//const wasDialogBefore = this.getDialogByPeerID(peerId)[0];
// here need to just replace, not FULL replace dialog! WARNING
/ * i f ( w a s D i a l o g B e f o r e ? . p F l a g s ? . p i n n e d & & ! d i a l o g ? . p F l a g s ? . p i n n e d ) {
this . log . error ( 'here need to just replace, not FULL replace dialog! WARNING' , wasDialogBefore , dialog ) ;
if ( ! dialog . pFlags ) dialog . pFlags = { } ;
dialog . pFlags . pinned = true ;
} * /
this . saveConversation ( dialog ) ;
/ * i f ( w a s D i a l o g B e f o r e ) {
rootScope . $broadcast ( 'dialog_top' , dialog ) ;
} else { * /
//if(wasDialogBefore?.top_message != topMessage) {
updatedDialogs [ peerId ] = dialog ;
//}
//}
} else {
const dropped = this . dialogsStorage . dropDialog ( peerId ) ;
if ( dropped . length ) {
rootScope . broadcast ( 'dialog_drop' , { peerId : peerId , dialog : dropped [ 0 ] } ) ;
}
}
if ( this . newUpdatesAfterReloadToHandle [ peerId ] !== undefined ) {
for ( const i in this . newUpdatesAfterReloadToHandle [ peerId ] ) {
const update = this . newUpdatesAfterReloadToHandle [ peerId ] [ i ] ;
this . handleUpdate ( update ) ;
}
delete this . newUpdatesAfterReloadToHandle [ peerId ] ;
}
} ) ;
if ( Object . keys ( updatedDialogs ) . length ) {
rootScope . broadcast ( 'dialogs_multiupdate' , updatedDialogs ) ;
}
}
public saveConversation ( dialog : Dialog , folderId = 0 ) {
const peerId = appPeersManager . getPeerId ( dialog . peer ) ;
if ( ! peerId ) {
return false ;
}
if ( dialog . _ != 'dialog' /* || peerId == 239602833 */ ) {
console . error ( 'saveConversation not regular dialog' , dialog , Object . assign ( { } , dialog ) ) ;
}
const channelId = appPeersManager . isChannel ( peerId ) ? - peerId : 0 ;
const peerText = appPeersManager . getPeerSearchText ( peerId ) ;
searchIndexManager . indexObject ( peerId , peerText , this . dialogsIndex ) ;
let mid : number , message ;
if ( dialog . top_message ) {
mid = this . generateMessageId ( dialog . top_message ) ; //dialog.top_message;
message = this . getMessageByPeer ( peerId , mid ) ;
} else {
mid = this . generateTempMessageId ( peerId ) ;
message = {
_ : 'message' ,
id : mid ,
mid : mid ,
from_id : appPeersManager.getOutputPeer ( appUsersManager . getSelf ( ) . id ) ,
peer_id : appPeersManager.getOutputPeer ( peerId ) ,
deleted : true ,
pFlags : { out : true } ,
date : 0 ,
message : ''
} ;
this . saveMessages ( [ message ] , { isOutgoing : true } ) ;
}
if ( ! message ? . pFlags ) {
this . log . error ( 'saveConversation no message:' , dialog , message ) ;
}
if ( ! channelId && peerId < 0 ) {
const chat = appChatsManager . getChat ( - peerId ) ;
if ( chat && chat . migrated_to && chat . pFlags . deactivated ) {
const migratedToPeer = appPeersManager . getPeerId ( chat . migrated_to ) ;
this . migratedFromTo [ peerId ] = migratedToPeer ;
this . migratedToFrom [ migratedToPeer ] = peerId ;
return ;
}
}
const wasDialogBefore = this . getDialogByPeerId ( peerId ) [ 0 ] ;
dialog . top_message = mid ;
dialog . read_inbox_max_id = wasDialogBefore && ! dialog . read_inbox_max_id ? wasDialogBefore.read_inbox_max_id : this.generateMessageId ( dialog . read_inbox_max_id ) ;
dialog . read_outbox_max_id = wasDialogBefore && ! dialog . read_outbox_max_id ? wasDialogBefore.read_outbox_max_id : this.generateMessageId ( dialog . read_outbox_max_id ) ;
if ( ! dialog . hasOwnProperty ( 'folder_id' ) ) {
if ( dialog . _ == 'dialog' ) {
// ! СЛОЖНО ! СМОТРИ В getTopMessages
dialog . folder_id = wasDialogBefore ? wasDialogBefore.folder_id : folderId ;
} / * else if ( dialog . _ == 'dialogFolder' ) {
dialog . folder_id = dialog . folder . id ;
} * /
}
dialog . peerId = peerId ;
// Because we saved message without dialog present
if ( mid > 0 ) {
if ( mid > dialog [ message . pFlags . out ? 'read_outbox_max_id' : 'read_inbox_max_id' ] ) message . pFlags . unread = true ;
else delete message . pFlags . unread ;
}
let historyStorage = this . getHistoryStorage ( peerId ) ;
if ( historyStorage === undefined /* && !message.deleted */ ) { // warning
historyStorage [ mid > 0 ? 'history' : 'pending' ] . push ( mid ) ;
/ * i f ( m i d < 0 & & m e s s a g e . p F l a g s . u n r e a d ) {
dialog . unread_count ++ ;
} * /
if ( this . mergeReplyKeyboard ( historyStorage , message ) ) {
rootScope . broadcast ( 'history_reply_markup' , { peerId } ) ;
}
} else if ( ! historyStorage . history . length && ! historyStorage . pending . length ) {
historyStorage [ mid > 0 ? 'history' : 'pending' ] . push ( mid ) ;
}
historyStorage . maxId = mid ;
historyStorage . readMaxId = dialog . read_inbox_max_id ;
historyStorage . readOutboxMaxId = dialog . read_outbox_max_id ;
if ( channelId && dialog . pts ) {
apiUpdatesManager . addChannelState ( channelId , dialog . pts ) ;
}
this . dialogsStorage . generateIndexForDialog ( dialog ) ;
this . dialogsStorage . pushDialog ( dialog , message . date ) ;
}
public mergeReplyKeyboard ( historyStorage : HistoryStorage , message : any ) {
// this.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup)
if ( ! message . reply_markup &&
! message . pFlags ? . out &&
! message . action ) {
return false ;
}
if ( message . reply_markup &&
message . reply_markup . _ == 'replyInlineMarkup' ) {
return false ;
}
var messageReplyMarkup = message . reply_markup ;
var lastReplyMarkup = historyStorage . reply_markup ;
if ( messageReplyMarkup ) {
if ( lastReplyMarkup && lastReplyMarkup . mid >= message . mid ) {
return false ;
}
if ( messageReplyMarkup . pFlags . selective ) {
return false ;
}
if ( historyStorage . maxOutId &&
message . mid < historyStorage . maxOutId &&
messageReplyMarkup . pFlags . single_use ) {
messageReplyMarkup . pFlags . hidden = true ;
}
messageReplyMarkup = Object . assign ( {
mid : message.mid
} , messageReplyMarkup ) ;
if ( messageReplyMarkup . _ != 'replyKeyboardHide' ) {
messageReplyMarkup . fromId = appPeersManager . getPeerId ( message . from_id ) ;
}
historyStorage . reply_markup = messageReplyMarkup ;
// this.log('set', historyStorage.reply_markup)
return true ;
}
if ( message . pFlags . out ) {
if ( lastReplyMarkup ) {
if ( lastReplyMarkup . pFlags . single_use &&
! lastReplyMarkup . pFlags . hidden &&
( message . mid > lastReplyMarkup . mid || message . pFlags . is_outgoing ) &&
message . message ) {
lastReplyMarkup . pFlags . hidden = true ;
// this.log('set', historyStorage.reply_markup)
return true ;
}
} else if ( ! historyStorage . maxOutId ||
message . mid > historyStorage . maxOutId ) {
historyStorage . maxOutId = message . mid ;
}
}
if ( message . action &&
message . action . _ == 'messageActionChatDeleteUser' &&
( lastReplyMarkup
? message . action . user_id == lastReplyMarkup . fromId
: appUsersManager . isBot ( message . action . user_id )
)
) {
historyStorage . reply_markup = {
_ : 'replyKeyboardHide' ,
mid : message.mid ,
pFlags : { }
} ;
// this.log('set', historyStorage.reply_markup)
return true ;
}
return false ;
}
public getSearchStorage ( peerId : number , inputFilter : MyInputMessagesFilter ) {
if ( ! this . searchesStorage [ peerId ] ) this . searchesStorage [ peerId ] = { } ;
if ( ! this . searchesStorage [ peerId ] [ inputFilter ] ) this . searchesStorage [ peerId ] [ inputFilter ] = { history : [ ] } ;
return this . searchesStorage [ peerId ] [ inputFilter ] ;
}
public getSearchCounters ( peerId : number , filters : MessagesFilter [ ] ) {
return apiManager . invokeApi ( 'messages.getSearchCounters' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
filters
} ) ;
}
public getSearchNew ( { peerId , query , inputFilter , maxId , limit , offsetRate , backLimit , threadId } : {
peerId : number ,
maxId : number ,
limit? : number ,
offsetRate? : number ,
backLimit? : number ,
threadId? : number ,
query? : string ,
inputFilter ? : {
_ : MyInputMessagesFilter
} ,
} ) {
return this . getSearch ( peerId , query , inputFilter , maxId , limit , offsetRate , backLimit , threadId ) ;
}
public getSearch ( peerId = 0 , query : string = '' , inputFilter : {
_ : MyInputMessagesFilter
} = { _ : 'inputMessagesFilterEmpty' } , maxId : number , limit = 20 , offsetRate = 0 , backLimit = 0 , threadId = 0 ) : Promise < {
count : number ,
next_rate : number ,
offset_id_offset : number ,
history : MyMessage [ ]
} > {
const foundMsgs : any [ ] = [ ] ;
//this.log('search', maxId);
if ( backLimit ) {
limit += backLimit ;
}
//const beta = inputFilter._ == 'inputMessagesFilterPinned' && !backLimit;
const beta = false ;
let storage : {
count? : number ;
history : number [ ] ;
} ;
// * костыль для limit 1, если нужно и получить сообщение, и узнать количество сообщений
if ( peerId && ! backLimit && ! maxId && ! query && limit !== 1 && ! threadId /* && inputFilter._ !== 'inputMessagesFilterPinned' */ ) {
storage = beta ?
this . getSearchStorage ( peerId , inputFilter . _ ) :
this . getHistoryStorage ( peerId ) ;
let filtering = true ;
const history = maxId ? storage . history . slice ( storage . history . indexOf ( maxId ) + 1 ) : storage . history ;
if ( storage !== undefined && history . length ) {
const neededContents : {
[ messageMediaType : string ] : boolean
} = { } ,
neededDocTypes : string [ ] = [ ] ,
excludeDocTypes : string [ ] = [ ] / * ,
neededFlags : string [ ] = [ ] * / ;
switch ( inputFilter . _ ) {
case 'inputMessagesFilterPhotos' :
neededContents [ 'messageMediaPhoto' ] = true ;
break ;
case 'inputMessagesFilterPhotoVideo' :
neededContents [ 'messageMediaPhoto' ] = true ;
neededContents [ 'messageMediaDocument' ] = true ;
neededDocTypes . push ( 'video' ) ;
break ;
case 'inputMessagesFilterVideo' :
neededContents [ 'messageMediaDocument' ] = true ;
neededDocTypes . push ( 'video' ) ;
break ;
case 'inputMessagesFilterDocument' :
neededContents [ 'messageMediaDocument' ] = true ;
excludeDocTypes . push ( 'video' ) ;
break ;
case 'inputMessagesFilterVoice' :
neededContents [ 'messageMediaDocument' ] = true ;
neededDocTypes . push ( 'voice' ) ;
break ;
case 'inputMessagesFilterRoundVoice' :
neededContents [ 'messageMediaDocument' ] = true ;
neededDocTypes . push ( 'round' , 'voice' ) ;
break ;
case 'inputMessagesFilterRoundVideo' :
neededContents [ 'messageMediaDocument' ] = true ;
neededDocTypes . push ( 'round' ) ;
break ;
case 'inputMessagesFilterMusic' :
neededContents [ 'messageMediaDocument' ] = true ;
neededDocTypes . push ( 'audio' ) ;
break ;
case 'inputMessagesFilterUrl' :
neededContents [ 'url' ] = true ;
break ;
case 'inputMessagesFilterChatPhotos' :
neededContents [ 'avatar' ] = true ;
break ;
/ * c a s e ' i n p u t M e s s a g e s F i l t e r P i n n e d ' :
neededFlags . push ( 'pinned' ) ;
break ; * /
/ * c a s e ' i n p u t M e s s a g e s F i l t e r M y M e n t i o n s ' :
neededContents [ 'mentioned' ] = true ;
break ; * /
default :
filtering = false ;
break ;
/ * r e t u r n P r o m i s e . r e s o l v e ( {
count : 0 ,
next_rate : 0 ,
history : [ ] as number [ ]
} ) ; * /
}
if ( filtering ) {
const storage = this . getMessagesStorage ( peerId ) ;
for ( let i = 0 , length = history . length ; i < length ; i ++ ) {
const message = storage [ history [ i ] ] ;
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ == 'messageEntityMention'));
let found = false ;
if ( message . media && neededContents [ message . media . _ ] && ! message . fwd_from ) {
if ( message . media . _ == 'messageMediaDocument' ) {
if ( ( neededDocTypes . length && ! neededDocTypes . includes ( message . media . document . type ) )
|| excludeDocTypes . includes ( message . media . document . type ) ) {
continue ;
}
}
found = true ;
} else if ( neededContents [ 'url' ] && message . message ) {
const goodEntities = [ 'messageEntityTextUrl' , 'messageEntityUrl' ] ;
if ( ( message . totalEntities as MessageEntity [ ] ) . find ( e = > goodEntities . includes ( e . _ ) ) || RichTextProcessor . matchUrl ( message . message ) ) {
found = true ;
}
} else if ( neededContents [ 'avatar' ] && message . action && [ 'messageActionChannelEditPhoto' , 'messageActionChatEditPhoto' ] . includes ( message . action . _ ) ) {
found = true ;
} / * else if ( neededFlags . find ( flag = > message . pFlags [ flag ] ) ) {
found = true ;
} * /
if ( found ) {
foundMsgs . push ( message ) ;
if ( foundMsgs . length >= limit ) {
break ;
}
}
}
}
}
}
if ( foundMsgs . length ) {
if ( foundMsgs . length < limit && ( beta ? storage . count !== storage.history.length : true ) ) {
maxId = foundMsgs [ foundMsgs . length - 1 ] ;
limit = limit - foundMsgs . length ;
} else {
return Promise . resolve ( {
count : beta ? storage.count : 0 ,
next_rate : 0 ,
offset_id_offset : 0 ,
history : foundMsgs
} ) ;
}
} else if ( beta && storage ? . count ) {
return Promise . resolve ( {
count : storage.count ,
next_rate : 0 ,
offset_id_offset : 0 ,
history : [ ]
} ) ;
}
let apiPromise : Promise < any > ;
if ( peerId || ! query ) {
apiPromise = apiManager . invokeApi ( 'messages.search' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
q : query || '' ,
filter : inputFilter as any as MessagesFilter ,
min_date : 0 ,
max_date : 0 ,
limit ,
offset_id : this.getLocalMessageId ( maxId ) || 0 ,
add_offset : backLimit ? - backLimit : 0 ,
max_id : 0 ,
min_id : 0 ,
hash : 0 ,
top_msg_id : this.getLocalMessageId ( threadId ) || 0
} , {
//timeout: APITIMEOUT,
noErrorBox : true
} ) ;
} else {
//var offsetDate = 0;
let offsetPeerId = 0 ;
let offsetId = 0 ;
let offsetMessage = maxId && this . getMessageByPeer ( peerId , maxId ) ;
if ( offsetMessage && offsetMessage . date ) {
//offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset;
offsetId = offsetMessage . id ;
offsetPeerId = this . getMessagePeer ( offsetMessage ) ;
}
apiPromise = apiManager . invokeApi ( 'messages.searchGlobal' , {
q : query ,
filter : inputFilter as any as MessagesFilter ,
min_date : 0 ,
max_date : 0 ,
offset_rate : offsetRate ,
offset_peer : appPeersManager.getInputPeerById ( offsetPeerId ) ,
offset_id : offsetId ,
limit ,
} , {
//timeout: APITIMEOUT,
noErrorBox : true
} ) ;
}
return apiPromise . then ( ( searchResult : any ) = > {
appUsersManager . saveApiUsers ( searchResult . users ) ;
appChatsManager . saveApiChats ( searchResult . chats ) ;
this . saveMessages ( searchResult . messages ) ;
if ( beta && storage && ( ! maxId || storage . history [ storage . history . length - 1 ] == maxId ) ) {
const storage = this . getSearchStorage ( peerId , inputFilter . _ ) ;
const add = ( searchResult . messages . map ( ( m : any ) = > m . mid ) as number [ ] ) . filter ( mid = > storage . history . indexOf ( mid ) === - 1 ) ;
storage . history . push ( . . . add ) ;
storage . history . sort ( ( a , b ) = > b - a ) ;
storage . count = searchResult . count ;
}
this . log ( 'messages.search result:' , inputFilter , searchResult ) ;
const foundCount : number = searchResult . count || ( foundMsgs . length + searchResult . messages . length ) ;
searchResult . messages . forEach ( ( message : any ) = > {
const peerId = this . getMessagePeer ( message ) ;
if ( peerId < 0 ) {
const chat = appChatsManager . getChat ( - peerId ) ;
if ( chat . migrated_to ) {
this . migrateChecks ( peerId , - chat . migrated_to . channel_id ) ;
}
}
foundMsgs . push ( message ) ;
} ) ;
return {
count : foundCount ,
offset_id_offset : searchResult.offset_id_offset ,
next_rate : searchResult.next_rate ,
history : foundMsgs
} ;
} ) ;
}
public subscribeRepliesThread ( peerId : number , mid : number ) {
const repliesKey = peerId + '_' + mid ;
for ( const threadKey in this . threadsToReplies ) {
if ( this . threadsToReplies [ threadKey ] === repliesKey ) return ;
}
this . getDiscussionMessage ( peerId , mid ) ;
}
public getDiscussionMessage ( peerId : number , mid : number ) {
return apiManager . invokeApi ( 'messages.getDiscussionMessage' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
msg_id : this.getLocalMessageId ( mid )
} ) . then ( result = > {
appChatsManager . saveApiChats ( result . chats ) ;
appUsersManager . saveApiUsers ( result . users ) ;
this . saveMessages ( result . messages ) ;
const message = result . messages [ 0 ] as MyMessage ;
const historyStorage = this . getHistoryStorage ( message . peerId , message . mid ) ;
result . max_id = historyStorage . maxId = this . generateMessageId ( result . max_id ) || 0 ;
result . read_inbox_max_id = historyStorage . readMaxId = this . generateMessageId ( result . read_inbox_max_id ) || 0 ;
result . read_outbox_max_id = historyStorage . readOutboxMaxId = this . generateMessageId ( result . read_outbox_max_id ) || 0 ;
this . threadsToReplies [ message . peerId + '_' + message . mid ] = peerId + '_' + mid ;
return result ;
} ) ;
}
handleNewMessages = ( ) = > {
clearTimeout ( this . newMessagesHandlePromise ) ;
this . newMessagesHandlePromise = 0 ;
rootScope . broadcast ( 'history_multiappend' , this . newMessagesToHandle ) ;
this . newMessagesToHandle = { } ;
} ;
handleNewDialogs = ( ) = > {
clearTimeout ( this . newDialogsHandlePromise ) ;
this . newDialogsHandlePromise = 0 ;
let newMaxSeenId = 0 ;
for ( const peerId in this . newDialogsToHandle ) {
const dialog = this . newDialogsToHandle [ peerId ] ;
if ( 'reload' in dialog ) {
this . reloadConversation ( + peerId ) ;
delete this . newDialogsToHandle [ peerId ] ;
} else {
this . dialogsStorage . pushDialog ( dialog ) ;
if ( ! appPeersManager . isChannel ( + peerId ) ) {
newMaxSeenId = Math . max ( newMaxSeenId , dialog . top_message || 0 ) ;
}
}
}
//this.log('after order:', this.dialogsStorage[0].map(d => d.peerId));
if ( newMaxSeenId != 0 ) {
this . incrementMaxSeenId ( newMaxSeenId ) ;
}
rootScope . broadcast ( 'dialogs_multiupdate' , this . newDialogsToHandle as any ) ;
this . newDialogsToHandle = { } ;
} ;
public scheduleHandleNewDialogs() {
if ( ! this . newDialogsHandlePromise ) {
this . newDialogsHandlePromise = window . setTimeout ( this . handleNewDialogs , 0 ) ;
}
}
public deleteMessages ( peerId : number , mids : number [ ] , revoke? : true ) {
let promise : Promise < any > ;
const localMessageIds = mids . map ( mid = > this . getLocalMessageId ( mid ) ) ;
if ( peerId < 0 && appPeersManager . isChannel ( peerId ) ) {
const channelId = - peerId ;
const channel = appChatsManager . getChat ( channelId ) ;
if ( ! channel . pFlags . creator && ! ( channel . pFlags . editor && channel . pFlags . megagroup ) ) {
const goodMsgIds : number [ ] = [ ] ;
if ( channel . pFlags . editor || channel . pFlags . megagroup ) {
mids . forEach ( ( msgId , i ) = > {
const message = this . getMessageByPeer ( peerId , mids [ i ] ) ;
if ( message . pFlags . out ) {
goodMsgIds . push ( msgId ) ;
}
} ) ;
}
if ( ! goodMsgIds . length ) {
return ;
}
mids = goodMsgIds ;
}
promise = apiManager . invokeApi ( 'channels.deleteMessages' , {
channel : appChatsManager.getChannelInput ( channelId ) ,
id : localMessageIds
} ) . then ( ( affectedMessages ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateDeleteChannelMessages' ,
channel_id : channelId ,
messages : mids ,
pts : affectedMessages.pts ,
pts_count : affectedMessages.pts_count
}
} ) ;
} ) ;
} else {
promise = apiManager . invokeApi ( 'messages.deleteMessages' , {
revoke ,
id : localMessageIds
} ) . then ( ( affectedMessages ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateDeleteMessages' ,
messages : mids ,
pts : affectedMessages.pts ,
pts_count : affectedMessages.pts_count
}
} ) ;
} ) ;
}
return promise ;
}
public readHistory ( peerId : number , maxId = 0 ) {
return Promise . resolve ( true ) ;
// console.trace('start read')
if ( ! this . isHistoryUnread ( peerId ) ) return Promise . resolve ( true ) ;
const isChannel = appPeersManager . isChannel ( peerId ) ;
const historyStorage = this . getHistoryStorage ( peerId ) ;
if ( ! historyStorage . readMaxId || maxId > historyStorage . readMaxId ) {
historyStorage . readMaxId = maxId ;
}
if ( historyStorage . readPromise ) {
return historyStorage . readPromise ;
}
let apiPromise : Promise < boolean > ;
if ( isChannel ) {
apiPromise = apiManager . invokeApi ( 'channels.readHistory' , {
channel : appChatsManager.getChannelInput ( - peerId ) ,
max_id : this.getLocalMessageId ( maxId )
} ) . then ( ( res ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateReadChannelInbox' ,
max_id : maxId ,
channel_id : - peerId
}
} ) ;
return res ;
} ) ;
} else {
apiPromise = apiManager . invokeApi ( 'messages.readHistory' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
max_id : this.getLocalMessageId ( maxId )
} ) . then ( ( affectedMessages ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updatePts' ,
pts : affectedMessages.pts ,
pts_count : affectedMessages.pts_count
}
} ) ;
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateReadHistoryInbox' ,
max_id : maxId ,
peer : appPeersManager.getOutputPeer ( peerId )
}
} ) ;
return true ;
} ) ;
}
apiPromise . finally ( ( ) = > {
delete historyStorage . readPromise ;
if ( historyStorage . readMaxId > maxId ) {
this . readHistory ( peerId , historyStorage . readMaxId ) ;
}
} ) ;
return historyStorage . readPromise = apiPromise ;
}
public readMessages ( peerId : number , msgIds : number [ ] ) {
msgIds = msgIds . map ( mid = > this . getLocalMessageId ( mid ) ) ;
if ( peerId < 0 && appPeersManager . isChannel ( peerId ) ) {
const channelId = - peerId ;
apiManager . invokeApi ( 'channels.readMessageContents' , {
channel : appChatsManager.getChannelInput ( channelId ) ,
id : msgIds
} ) . then ( ( ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateChannelReadMessagesContents' ,
channel_id : channelId ,
messages : msgIds
}
} ) ;
} ) ;
} else {
apiManager . invokeApi ( 'messages.readMessageContents' , {
id : msgIds
} ) . then ( ( affectedMessages ) = > {
apiUpdatesManager . processUpdateMessage ( {
_ : 'updateShort' ,
update : {
_ : 'updateReadMessagesContents' ,
messages : msgIds ,
pts : affectedMessages.pts ,
pts_count : affectedMessages.pts_count
}
} ) ;
} ) ;
}
}
public getHistoryStorage ( peerId : number , threadId? : number ) {
if ( threadId ) {
//threadId = this.getLocalMessageId(threadId);
if ( ! this . threadsStorage [ peerId ] ) this . threadsStorage [ peerId ] = { } ;
return this . threadsStorage [ peerId ] [ threadId ] ? ? ( this . threadsStorage [ peerId ] [ threadId ] = { count : null , history : [ ] , pending : [ ] } ) ;
}
return this . historiesStorage [ peerId ] ? ? ( this . historiesStorage [ peerId ] = { count : null , history : [ ] , pending : [ ] } ) ;
}
public handleUpdate ( update : Update ) {
this . log . debug ( 'handleUpdate' , update . _ , update ) ;
switch ( update . _ ) {
case 'updateMessageID' : {
const randomId = update . random_id ;
const pendingData = this . pendingByRandomId [ randomId ] ;
//this.log('AMM updateMessageID:', update, pendingData);
if ( pendingData ) {
const { peerId , tempId , storage } = pendingData ;
//const mid = update.id;
const mid = this . generateMessageId ( update . id ) ;
const message = this . getMessageFromStorage ( storage , mid ) ;
if ( ! message . deleted ) {
const historyStorage = this . getHistoryStorage ( peerId ) ;
const pos = historyStorage . pending . indexOf ( tempId ) ;
if ( pos !== - 1 ) {
historyStorage . pending . splice ( pos , 1 ) ;
}
this . finalizePendingMessageCallbacks ( storage , tempId , mid ) ;
} else {
this . pendingByMessageId [ mid ] = randomId ;
}
}
break ;
}
case 'updateNewMessage' :
case 'updateNewChannelMessage' : {
const message = update . message as MyMessage ;
const peerId = this . getMessagePeer ( message ) ;
const storage = this . getMessagesStorage ( peerId ) ;
const foundDialog = this . getDialogByPeerId ( peerId ) ;
if ( ! foundDialog . length && ( peerId > 0 || ! appChatsManager . getChat ( - peerId ) . pFlags . left ) ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
this . scheduleHandleNewDialogs ( ) ;
if ( this . newUpdatesAfterReloadToHandle [ peerId ] === undefined ) {
this . newUpdatesAfterReloadToHandle [ peerId ] = [ ] ;
}
this . newUpdatesAfterReloadToHandle [ peerId ] . push ( update ) ;
break ;
}
/ * i f ( u p d a t e . _ = = ' u p d a t e N e w C h a n n e l M e s s a g e ' ) {
const chat = appChatsManager . getChat ( - peerId ) ;
if ( chat . pFlags && ( chat . pFlags . left || chat . pFlags . kicked ) ) {
break ;
}
} * /
this . saveMessages ( [ message ] , { storage } ) ;
// this.log.warn(dT(), 'message unread', message.mid, message.pFlags.unread)
const pendingMessage = this . checkPendingMessage ( message ) ;
const historyStorage = this . getHistoryStorage ( peerId ) ;
const history = message . mid > 0 ? historyStorage.history : historyStorage.pending ;
if ( history . indexOf ( message . mid ) !== - 1 ) {
return false ;
}
const topMsgId = history [ 0 ] ;
history . unshift ( message . mid ) ;
if ( message . mid > 0 && message . mid < topMsgId ) {
history . sort ( ( a , b ) = > {
return b - a ;
} ) ;
}
if ( message . mid > 0 && historyStorage . count !== null ) {
historyStorage . count ++ ;
}
if ( this . mergeReplyKeyboard ( historyStorage , message ) ) {
rootScope . broadcast ( 'history_reply_markup' , { peerId } ) ;
}
if ( message . fromId > 0 && ! message . pFlags . out && message . from_id ) {
appUsersManager . forceUserOnline ( message . fromId , message . date ) ;
}
if ( ! pendingMessage ) {
if ( this . newMessagesToHandle [ peerId ] === undefined ) {
this . newMessagesToHandle [ peerId ] = [ ] ;
}
this . newMessagesToHandle [ peerId ] . push ( message . mid ) ;
if ( ! this . newMessagesHandlePromise ) {
this . newMessagesHandlePromise = window . setTimeout ( this . handleNewMessages , 0 ) ;
}
}
this . updateMessageRepliesIfNeeded ( message ) ;
const dialog = foundDialog [ 0 ] ;
if ( dialog ) {
const inboxUnread = ! message . pFlags . out && message . pFlags . unread ;
this . setDialogTopMessage ( message , dialog ) ;
if ( inboxUnread ) {
dialog . unread_count ++ ;
}
}
break ;
}
case 'updateDialogUnreadMark' : {
this . log ( 'updateDialogUnreadMark' , update ) ;
const peerId = appPeersManager . getPeerId ( ( update . peer as DialogPeer . dialogPeer ) . peer ) ;
const foundDialog = this . getDialogByPeerId ( peerId ) ;
if ( ! foundDialog . length ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
this . scheduleHandleNewDialogs ( ) ;
} else {
const dialog = foundDialog [ 0 ] ;
if ( ! update . pFlags . unread ) {
delete dialog . pFlags . unread_mark ;
} else {
dialog . pFlags . unread_mark = true ;
}
rootScope . broadcast ( 'dialogs_multiupdate' , { peerId : dialog } ) ;
}
break ;
}
case 'updateFolderPeers' : { // only 0 and 1 folders
this . log ( 'updateFolderPeers' , update ) ;
const peers = update . folder_peers ;
this . scheduleHandleNewDialogs ( ) ;
peers . forEach ( ( folderPeer ) = > {
const { folder_id , peer } = folderPeer ;
const peerId = appPeersManager . getPeerId ( peer ) ;
const dropped = this . dialogsStorage . dropDialog ( peerId ) ;
if ( ! dropped . length ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
} else {
const dialog = dropped [ 0 ] ;
this . newDialogsToHandle [ peerId ] = dialog ;
if ( dialog . pFlags ? . pinned ) {
delete dialog . pFlags . pinned ;
this . dialogsStorage . pinnedOrders [ folder_id ] . findAndSplice ( p = > p == dialog . peerId ) ;
}
dialog . folder_id = folder_id ;
this . dialogsStorage . generateIndexForDialog ( dialog ) ;
this . dialogsStorage . pushDialog ( dialog ) ; // need for simultaneously updatePinnedDialogs
}
} ) ;
break ;
}
case 'updateDialogPinned' : {
const folderId = update . folder_id ? ? 0 ;
this . log ( 'updateDialogPinned' , update ) ;
const peerId = appPeersManager . getPeerId ( ( update . peer as DialogPeer . dialogPeer ) . peer ) ;
const foundDialog = this . getDialogByPeerId ( peerId ) ;
// этот код внизу никогда не сработает, в папках за пиннед отвечает updateDialogFilter
/ * i f ( u p d a t e . f o l d e r _ i d > 1 ) {
const filter = this . filtersStorage . filters [ update . folder_id ] ;
if ( update . pFlags . pinned ) {
filter . pinned_peers . unshift ( peerId ) ;
} else {
filter . pinned_peers . findAndSplice ( p = > p == peerId ) ;
}
} * /
this . scheduleHandleNewDialogs ( ) ;
if ( ! foundDialog . length ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
} else {
const dialog = foundDialog [ 0 ] ;
this . newDialogsToHandle [ peerId ] = dialog ;
if ( ! update . pFlags . pinned ) {
delete dialog . pFlags . pinned ;
this . dialogsStorage . pinnedOrders [ folderId ] . findAndSplice ( p = > p == dialog . peerId ) ;
} else { // means set
dialog . pFlags . pinned = true ;
}
this . dialogsStorage . generateIndexForDialog ( dialog ) ;
}
break ;
}
case 'updatePinnedDialogs' : {
const folderId = update . folder_id ? ? 0 ;
const handleOrder = ( order : number [ ] ) = > {
this . dialogsStorage . pinnedOrders [ folderId ] . length = 0 ;
let willHandle = false ;
order . reverse ( ) ; // index must be higher
order . forEach ( ( peerId ) = > {
newPinned [ peerId ] = true ;
const foundDialog = this . getDialogByPeerId ( peerId ) ;
if ( ! foundDialog . length ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
willHandle = true ;
return ;
}
const dialog = foundDialog [ 0 ] ;
dialog . pFlags . pinned = true ;
this . dialogsStorage . generateIndexForDialog ( dialog ) ;
this . newDialogsToHandle [ peerId ] = dialog ;
willHandle = true ;
} ) ;
this . dialogsStorage . getFolder ( folderId ) . forEach ( dialog = > {
const peerId = dialog . peerId ;
if ( dialog . pFlags . pinned && ! newPinned [ peerId ] ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
willHandle = true ;
}
} ) ;
if ( willHandle ) {
this . scheduleHandleNewDialogs ( ) ;
}
} ;
this . log ( 'updatePinnedDialogs' , update ) ;
const newPinned : { [ peerId : number ] : true } = { } ;
if ( ! update . order ) {
apiManager . invokeApi ( 'messages.getPinnedDialogs' , {
folder_id : folderId
} ) . then ( ( dialogsResult ) = > {
// * for test reordering and rendering
// dialogsResult.dialogs.reverse();
this . applyConversations ( dialogsResult ) ;
handleOrder ( dialogsResult . dialogs . map ( d = > d . peerId ) ) ;
/ * d i a l o g s R e s u l t . d i a l o g s . f o r E a c h ( ( d i a l o g ) = > {
newPinned [ dialog . peerId ] = true ;
} ) ;
this . dialogsStorage . getFolder ( folderId ) . forEach ( ( dialog ) = > {
const peerId = dialog . peerId ;
if ( dialog . pFlags . pinned && ! newPinned [ peerId ] ) {
this . newDialogsToHandle [ peerId ] = { reload : true } ;
this . scheduleHandleNewDialogs ( ) ;
}
} ) ; * /
} ) ;
break ;
}
//this.log('before order:', this.dialogsStorage[0].map(d => d.peerId));
handleOrder ( update . order . map ( peer = > appPeersManager . getPeerId ( ( peer as DialogPeer . dialogPeer ) . peer ) ) ) ;
break ;
}
case 'updateEditMessage' :
case 'updateEditChannelMessage' : {
const message = update . message as MyMessage ;
const peerId = this . getMessagePeer ( message ) ;
const mid = this . generateMessageId ( message . id ) ;
const storage = this . getMessagesStorage ( peerId ) ;
if ( storage [ mid ] === undefined ) {
break ;
}
// console.trace(dT(), 'edit message', message)
const oldMessage = this . getMessageFromStorage ( storage , mid ) ;
this . saveMessages ( [ message ] , { storage } ) ;
const newMessage = this . getMessageFromStorage ( storage , mid ) ;
this . handleEditedMessage ( oldMessage , newMessage ) ;
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
const isTopMessage = dialog && dialog . top_message == mid ;
// @ts-ignore
if ( message . clear_history ) { // that's will never happen
if ( isTopMessage ) {
rootScope . broadcast ( 'dialog_flush' , { peerId } ) ;
}
} else {
rootScope . broadcast ( 'message_edit' , {
storage ,
peerId ,
mid
} ) ;
if ( isTopMessage || ( message as Message . message ) . grouped_id ) {
const updatedDialogs : { [ peerId : number ] : Dialog } = { } ;
updatedDialogs [ peerId ] = dialog ;
rootScope . broadcast ( 'dialogs_multiupdate' , updatedDialogs ) ;
}
}
break ;
}
case 'updateReadChannelDiscussionInbox' :
case 'updateReadChannelDiscussionOutbox' :
case 'updateReadHistoryInbox' :
case 'updateReadHistoryOutbox' :
case 'updateReadChannelInbox' :
case 'updateReadChannelOutbox' : {
const channelId = ( update as Update . updateReadChannelInbox ) . channel_id ;
const maxId = this . generateMessageId ( ( update as Update . updateReadChannelInbox ) . max_id || ( update as Update . updateReadChannelDiscussionInbox ) . read_max_id ) ;
const threadId = this . generateMessageId ( ( update as Update . updateReadChannelDiscussionInbox ) . top_msg_id ) ;
const peerId = channelId ? - channelId : appPeersManager.getPeerId ( ( update as Update . updateReadHistoryInbox ) . peer ) ;
const isOut = update . _ === 'updateReadHistoryOutbox' || update . _ === 'updateReadChannelOutbox' || update . _ === 'updateReadChannelDiscussionOutbox' ? true : undefined ;
const storage = this . getMessagesStorage ( peerId ) ;
const history = getObjectKeysAndSort ( storage , 'desc' ) ;
const foundDialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
const stillUnreadCount = ( update as Update . updateReadChannelInbox ) . still_unread_count ;
let newUnreadCount = 0 ;
let foundAffected = false ;
//this.log.warn(dT(), 'read', peerId, isOut ? 'out' : 'in', maxId)
const historyStorage = this . getHistoryStorage ( peerId , threadId ) ;
if ( peerId > 0 && isOut ) {
appUsersManager . forceUserOnline ( peerId ) ;
}
for ( let i = 0 , length = history . length ; i < length ; i ++ ) {
const messageId = history [ i ] ;
if ( messageId > maxId ) {
continue ;
}
const message = storage [ messageId ] ;
if ( message . pFlags . out != isOut ) {
continue ;
}
if ( ! message . pFlags . unread ) {
break ;
}
if ( threadId ) {
const replyTo = message . reply_to as MessageReplyHeader ;
if ( ! replyTo || ( replyTo . reply_to_top_id || replyTo . reply_to_msg_id ) !== threadId ) {
continue ;
}
}
// this.log.warn('read', messageId, message.pFlags.unread, message)
if ( message . pFlags . unread ) {
delete message . pFlags . unread ;
if ( ! foundAffected ) {
foundAffected = true ;
}
if ( ! message . pFlags . out && ! threadId && stillUnreadCount === undefined ) {
newUnreadCount = -- foundDialog . unread_count ;
//NotificationsManager.cancel('msg' + messageId); // warning
}
}
}
if ( isOut ) historyStorage . readOutboxMaxId = maxId ;
else historyStorage . readMaxId = maxId ;
if ( ! threadId && foundDialog ) {
if ( isOut ) foundDialog . read_outbox_max_id = maxId ;
else foundDialog . read_inbox_max_id = maxId ;
if ( ! isOut ) {
if ( newUnreadCount < 0 || ! this . isHistoryUnread ( peerId ) ) {
foundDialog . unread_count = 0 ;
} else if ( newUnreadCount && foundDialog . top_message > maxId ) {
foundDialog . unread_count = newUnreadCount ;
}
}
rootScope . broadcast ( 'dialog_unread' , { peerId } ) ;
}
if ( foundAffected ) {
rootScope . broadcast ( 'messages_read' ) ;
}
break ;
}
case 'updateChannelReadMessagesContents' :
case 'updateReadMessagesContents' : {
const channelId = ( update as Update . updateChannelReadMessagesContents ) . channel_id ;
const peerId = channelId ? - channelId : this.getMessageById ( update . messages [ 0 ] ) . peerId ;
const messages : number [ ] = update . messages ;
for ( const messageId of messages ) {
const message = this . getMessageByPeer ( peerId , messageId ) ;
if ( ! message . deleted ) {
delete message . pFlags . media_unread ;
}
}
rootScope . broadcast ( 'messages_media_read' , { peerId , mids : messages.map ( id = > this . generateMessageId ( id ) ) } ) ;
break ;
}
case 'updateChannelAvailableMessages' : {
const channelId : number = update . channel_id ;
const messages : number [ ] = [ ] ;
const peerId : number = - channelId ;
const history = this . getHistoryStorage ( peerId ) . history ;
if ( history . length ) {
history . forEach ( ( msgId : number ) = > {
if ( ! update . available_min_id || msgId <= update . available_min_id ) {
messages . push ( msgId ) ;
}
} ) ;
}
( update as any as Update . updateDeleteChannelMessages ) . messages = messages ;
}
case 'updateDeleteMessages' :
case 'updateDeleteChannelMessages' : {
const channelId : number = ( update as Update . updateDeleteChannelMessages ) . channel_id ;
//const messages = (update as any as Update.updateDeleteChannelMessages).messages;
const messages = ( update as any as Update . updateDeleteChannelMessages ) . messages . map ( id = > this . generateMessageId ( id ) ) ;
const peerId = channelId ? - channelId : this.getMessageById ( messages [ 0 ] ) . peerId ;
if ( ! peerId ) {
break ;
}
const historyUpdated = this . handleDeletedMessages ( peerId , this . getMessagesStorage ( peerId ) , messages ) ;
const historyStorage = this . getHistoryStorage ( peerId ) ;
//if(historyStorage !== undefined) {
const newHistory = historyStorage . history . filter ( mid = > ! historyUpdated . msgs [ mid ] ) ;
const newPending = historyStorage . pending . filter ( mid = > ! historyUpdated . msgs [ mid ] ) ;
historyStorage . history = newHistory ;
if ( historyUpdated . count &&
historyStorage . count !== null &&
historyStorage . count > 0 ) {
historyStorage . count -= historyUpdated . count ;
if ( historyStorage . count < 0 ) {
historyStorage . count = 0 ;
}
}
historyStorage . pending = newPending ;
rootScope . broadcast ( 'history_delete' , { peerId , msgs : historyUpdated.msgs } ) ;
//}
const foundDialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
if ( foundDialog ) {
if ( historyUpdated . unread ) {
foundDialog . unread_count -= historyUpdated . unread ;
rootScope . broadcast ( 'dialog_unread' , { peerId } ) ;
}
if ( historyUpdated . msgs [ foundDialog . top_message ] ) {
this . reloadConversation ( peerId ) ;
}
}
break ;
}
case 'updateChannel' : {
const channelId : number = update . channel_id ;
const peerId = - channelId ;
const channel = appChatsManager . getChat ( channelId ) ;
const needDialog = channel . _ == 'channel' && ( ! channel . pFlags . left && ! channel . pFlags . kicked ) ;
const foundDialog = this . getDialogByPeerId ( peerId ) ;
const hasDialog = foundDialog . length > 0 ;
const canViewHistory = channel . _ == 'channel' && ( channel . username || ! channel . pFlags . left && ! channel . pFlags . kicked ) ;
const hasHistory = this . historiesStorage [ peerId ] !== undefined ;
if ( canViewHistory != hasHistory ) {
delete this . historiesStorage [ peerId ] ;
rootScope . broadcast ( 'history_forbidden' , peerId ) ;
}
if ( hasDialog != needDialog ) {
if ( needDialog ) {
this . reloadConversation ( - channelId ) ;
} else {
if ( foundDialog [ 0 ] ) {
this . dialogsStorage . dropDialog ( peerId ) ;
rootScope . broadcast ( 'dialog_drop' , { peerId : peerId , dialog : foundDialog [ 0 ] } ) ;
}
}
}
break ;
}
// @ts-ignore
case 'updateChannelReload' : {
// @ts-ignore
const channelId : number = update . channel_id ;
const peerId = - channelId ;
this . dialogsStorage . dropDialog ( peerId ) ;
delete this . historiesStorage [ peerId ] ;
this . reloadConversation ( - channelId ) . then ( ( ) = > {
rootScope . broadcast ( 'history_reload' , peerId ) ;
} ) ;
break ;
}
case 'updateChannelMessageViews' : {
const views = update . views ;
//const mid = update.id;
const mid = this . generateMessageId ( update . id ) ;
const message = this . getMessageByPeer ( - update . channel_id , mid ) ;
if ( ! message . deleted && message . views && message . views < views ) {
message . views = views ;
rootScope . broadcast ( 'message_views' , { mid , views } ) ;
}
break ;
}
case 'updateServiceNotification' : {
this . log ( 'updateServiceNotification' , update ) ;
const fromId = 777000 ;
const peerId = fromId ;
const messageId = this . generateTempMessageId ( peerId ) ;
const message : any = {
_ : 'message' ,
id : messageId ,
from_id : appPeersManager.getOutputPeer ( fromId ) ,
peer_id : appPeersManager.getOutputPeer ( peerId ) ,
pFlags : { unread : true } ,
date : ( update . inbox_date || tsNow ( true ) ) + serverTimeManager . serverTimeOffset ,
message : update.message ,
media : update.media ,
entities : update.entities
} ;
if ( ! appUsersManager . hasUser ( fromId ) ) {
appUsersManager . saveApiUsers ( [ {
_ : 'user' ,
id : fromId ,
pFlags : { verified : true } ,
access_hash : 0 ,
first_name : 'Telegram' ,
phone : '42777'
} ] ) ;
}
this . saveMessages ( [ message ] , { isOutgoing : true } ) ;
if ( update . inbox_date ) {
this . pendingTopMsgs [ peerId ] = messageId ;
this . handleUpdate ( {
_ : 'updateNewMessage' ,
message : message
} as any ) ;
}
break ;
}
case 'updatePinnedMessages' :
case 'updatePinnedChannelMessages' : {
const channelId = update . _ == 'updatePinnedChannelMessages' ? update.channel_id : undefined ;
const peerId = channelId ? - channelId : appPeersManager.getPeerId ( ( update as Update . updatePinnedMessages ) . peer ) ;
/ * c o n s t s t o r a g e = t h i s . g e t S e a r c h S t o r a g e ( p e e r I d , ' i n p u t M e s s a g e s F i l t e r P i n n e d ' ) ;
if ( storage . count !== storage . history . length ) {
if ( storage . count !== undefined ) {
delete this . searchesStorage [ peerId ] [ 'inputMessagesFilterPinned' ] ;
}
rootScope . broadcast ( 'peer_pinned_messages' , peerId ) ;
break ;
} * /
const messages = update . messages . map ( id = > this . generateMessageId ( id ) ) ;
const storage = this . getMessagesStorage ( peerId ) ;
const missingMessages = messages . filter ( mid = > ! storage [ mid ] ) ;
const getMissingPromise = missingMessages . length ? Promise . all ( missingMessages . map ( mid = > this . wrapSingleMessage ( peerId , mid ) ) ) : Promise . resolve ( ) ;
getMissingPromise . finally ( ( ) = > {
const werePinned = update . pFlags ? . pinned ;
if ( werePinned ) {
for ( const mid of messages ) {
//storage.history.push(mid);
const message = storage [ mid ] ;
message . pFlags . pinned = true ;
}
/ * i f ( t h i s . p i n n e d M e s s a g e s [ p e e r I d ] ? . m a x I d ) {
const maxMid = Math . max ( . . . messages ) ;
this . pinnedMessages
} * /
//storage.history.sort((a, b) => b - a);
} else {
for ( const mid of messages ) {
//storage.history.findAndSplice(_mid => _mid == mid);
const message = storage [ mid ] ;
delete message . pFlags . pinned ;
}
}
/ * c o n s t i n f o = t h i s . p i n n e d M e s s a g e s [ p e e r I d ] ;
if ( info ) {
info . count += messages . length * ( werePinned ? 1 : - 1 ) ;
} * /
delete this . pinnedMessages [ peerId ] ;
appStateManager . getState ( ) . then ( state = > {
delete state . hiddenPinnedMessages [ peerId ] ;
rootScope . broadcast ( 'peer_pinned_messages' , { peerId , mids : messages , pinned : werePinned } ) ;
} ) ;
} ) ;
break ;
}
case 'updateNotifySettings' : {
const { peer , notify_settings } = update ;
const peerId = appPeersManager . getPeerId ( ( peer as NotifyPeer . notifyPeer ) . peer ) ;
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
if ( dialog ) {
dialog . notify_settings = notify_settings ;
rootScope . broadcast ( 'dialog_notify_settings' , peerId ) ;
}
/////this.log('updateNotifySettings', peerId, notify_settings);
break ;
}
case 'updateNewScheduledMessage' : {
const message = update . message as MyMessage ;
const peerId = this . getMessagePeer ( message ) ;
const storage = this . scheduledMessagesStorage [ peerId ] ;
if ( storage ) {
const mid = this . generateMessageId ( message . id ) ;
const oldMessage = this . getMessageFromStorage ( storage , mid ) ;
this . saveMessages ( [ message ] , { storage , isScheduled : true } ) ;
const newMessage = this . getMessageFromStorage ( storage , mid ) ;
if ( ! oldMessage . deleted ) {
this . handleEditedMessage ( oldMessage , newMessage ) ;
rootScope . broadcast ( 'message_edit' , { storage , peerId , mid : message.mid } ) ;
} else {
const pendingMessage = this . checkPendingMessage ( message ) ;
if ( ! pendingMessage ) {
rootScope . broadcast ( 'scheduled_new' , { peerId , mid : message.mid } ) ;
}
}
}
break ;
}
case 'updateDeleteScheduledMessages' : {
const peerId = appPeersManager . getPeerId ( update . peer ) ;
const storage = this . scheduledMessagesStorage [ peerId ] ;
if ( storage ) {
const mids = update . messages . map ( id = > this . generateMessageId ( id ) ) ;
this . handleDeletedMessages ( peerId , storage , mids ) ;
rootScope . broadcast ( 'scheduled_delete' , { peerId , mids } ) ;
}
break ;
}
}
}
private updateMessageRepliesIfNeeded ( threadMessage : MyMessage ) {
try { // * на всякий случай, скорее всего это не понадобится
if ( threadMessage . peerId < 0 && threadMessage . reply_to ) {
const threadId = threadMessage . reply_to . reply_to_top_id || threadMessage . reply_to . reply_to_msg_id ;
const threadKey = threadMessage . peerId + '_' + threadId ;
const repliesKey = this . threadsToReplies [ threadKey ] ;
if ( repliesKey ) {
const [ peerId , mid ] = repliesKey . split ( '_' ) . map ( n = > + n ) ;
this . updateMessage ( peerId , mid , 'replies_updated' ) ;
}
}
} catch ( err ) {
this . log . error ( 'incrementMessageReplies err' , err , threadMessage ) ;
}
}
public updateMessage ( peerId : number , mid : number , broadcastEventName ? : 'replies_updated' ) : Promise < Message.message > {
const promise : Promise < Message.message > = this . wrapSingleMessage ( peerId , mid , true ) . then ( ( ) = > {
const message = this . getMessageByPeer ( peerId , mid ) ;
if ( broadcastEventName ) {
rootScope . broadcast ( broadcastEventName , message ) ;
}
return message ;
} ) ;
return promise ;
}
private checkPendingMessage ( message : any ) {
const randomId = this . pendingByMessageId [ message . mid ] ;
let pendingMessage : any ;
if ( randomId ) {
const pendingData = this . pendingByRandomId [ randomId ] ;
if ( pendingMessage = this . finalizePendingMessage ( randomId , message ) ) {
rootScope . broadcast ( 'history_update' , { storage : pendingData.storage , peerId : message.peerId , mid : message.mid } ) ;
}
delete this . pendingByMessageId [ message . mid ] ;
}
return pendingMessage ;
}
public isPeerMuted ( peerId : number ) {
if ( peerId == rootScope . myId ) return false ;
const dialog = this . getDialogByPeerId ( peerId ) [ 0 ] ;
let muted = false ;
if ( dialog && dialog . notify_settings && dialog . notify_settings . mute_until ) {
muted = new Date ( dialog . notify_settings . mute_until * 1000 ) > new Date ( ) ;
}
return muted ;
}
public mutePeer ( peerId : number ) {
let inputPeer = appPeersManager . getInputPeerById ( peerId ) ;
let inputNotifyPeer : InputNotifyPeer.inputNotifyPeer = {
_ : 'inputNotifyPeer' ,
peer : inputPeer
} ;
let settings : InputPeerNotifySettings = {
_ : 'inputPeerNotifySettings'
} ;
let dialog = appMessagesManager . getDialogByPeerId ( peerId ) [ 0 ] ;
let muted = true ;
if ( dialog && dialog . notify_settings ) {
muted = dialog . notify_settings . mute_until > ( Date . now ( ) / 1000 | 0 ) ;
}
if ( ! muted ) {
settings . mute_until = 2147483647 ;
}
apiManager . invokeApi ( 'account.updateNotifySettings' , {
peer : inputNotifyPeer ,
settings : settings
} ) . then ( bool = > {
if ( bool ) {
this . handleUpdate ( {
_ : 'updateNotifySettings' ,
peer : {
_ : 'notifyPeer' ,
peer : appPeersManager.getOutputPeer ( peerId )
} ,
notify_settings : { // ! WOW, IT WORKS !
. . . settings ,
_ : 'peerNotifySettings' ,
}
} ) ;
}
} ) ;
}
public canWriteToPeer ( peerId : number ) {
const isChannel = appPeersManager . isChannel ( peerId ) ;
const hasRights = isChannel && appChatsManager . hasRights ( - peerId , 'send' ) ;
return ( ! isChannel || hasRights ) && ( peerId < 0 || appUsersManager . canSendToUser ( peerId ) ) ;
}
public finalizePendingMessage ( randomId : string , finalMessage : any ) {
const pendingData = this . pendingByRandomId [ randomId ] ;
// this.log('pdata', randomID, pendingData)
if ( pendingData ) {
const { peerId , tempId , storage } = pendingData ;
const historyStorage = this . getHistoryStorage ( peerId ) ;
// this.log('pending', randomID, historyStorage.pending)
const pos = historyStorage . pending . indexOf ( tempId ) ;
if ( pos !== - 1 ) {
historyStorage . pending . splice ( pos , 1 ) ;
}
const message = this . getMessageFromStorage ( storage , tempId ) ;
if ( ! message . deleted ) {
delete message . pFlags . is_outgoing ;
delete message . pending ;
delete message . error ;
delete message . random_id ;
delete message . send ;
rootScope . broadcast ( 'messages_pending' ) ;
}
delete this . pendingByRandomId [ randomId ] ;
this . finalizePendingMessageCallbacks ( storage , tempId , finalMessage . mid ) ;
return message ;
}
return false ;
}
public finalizePendingMessageCallbacks ( storage : MessagesStorage , tempId : number , mid : number ) {
const message = this . getMessageFromStorage ( storage , mid ) ;
const callbacks = this . tempFinalizeCallbacks [ tempId ] ;
this . log . warn ( callbacks , tempId ) ;
if ( callbacks !== undefined ) {
for ( const name in callbacks ) {
const { deferred , callback } = callbacks [ name ] ;
this . log ( ` finalizePendingMessageCallbacks: will invoke ${ name } callback ` ) ;
callback ( message ) . then ( deferred . resolve , deferred . reject ) ;
}
delete this . tempFinalizeCallbacks [ tempId ] ;
}
// set cached url to media
if ( message . media ) {
if ( message . media . photo ) {
const photo = appPhotosManager . getPhoto ( '' + tempId ) ;
if ( /* photo._ != 'photoEmpty' */ photo ) {
const newPhoto = message . media . photo ;
// костыль
defineNotNumerableProperties ( newPhoto , [ 'downloaded' , 'url' ] ) ;
newPhoto . downloaded = photo . downloaded ;
newPhoto . url = photo . url ;
}
} else if ( message . media . document ) {
const doc = appDocsManager . getDoc ( '' + tempId ) ;
if ( /* doc._ != 'documentEmpty' && */ doc ? . type && doc . type != 'sticker' ) {
const newDoc = message . media . document ;
newDoc . downloaded = doc . downloaded ;
newDoc . url = doc . url ;
}
} else if ( message . media . poll ) {
delete appPollsManager . polls [ tempId ] ;
delete appPollsManager . results [ tempId ] ;
}
}
const tempMessage = this . getMessageFromStorage ( storage , tempId ) ;
delete storage [ tempId ] ;
rootScope . broadcast ( 'message_sent' , { storage , tempId , tempMessage , mid } ) ;
}
public incrementMaxSeenId ( maxId : number ) {
if ( ! maxId || ! ( ! this . maxSeenId || maxId > this . maxSeenId ) ) {
return false ;
}
this . maxSeenId = maxId ;
AppStorage . set ( { max_seen_msg : maxId } ) ;
apiManager . invokeApi ( 'messages.receivedMessages' , {
max_id : maxId
} ) ;
}
public getScheduledMessagesStorage ( peerId : number ) {
return this . scheduledMessagesStorage [ peerId ] ? ? ( this . scheduledMessagesStorage [ peerId ] = this . createMessageStorage ( ) ) ;
}
public getScheduledMessages ( peerId : number ) : Promise < number [ ] > {
if ( ! this . canWriteToPeer ( peerId ) ) return Promise . resolve ( [ ] ) ;
const storage = this . getScheduledMessagesStorage ( peerId ) ;
if ( Object . keys ( storage ) . length ) {
return Promise . resolve ( Object . keys ( storage ) . map ( id = > + id ) ) ;
}
return apiManager . invokeApi ( 'messages.getScheduledHistory' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
hash : 0
} ) . then ( historyResult = > {
if ( historyResult . _ !== 'messages.messagesNotModified' ) {
appUsersManager . saveApiUsers ( historyResult . users ) ;
appChatsManager . saveApiChats ( historyResult . chats ) ;
const storage = this . getScheduledMessagesStorage ( peerId ) ;
this . saveMessages ( historyResult . messages , { storage , isScheduled : true } ) ;
return Object . keys ( storage ) . map ( id = > + id ) ;
}
return [ ] ;
} ) ;
}
public sendScheduledMessages ( peerId : number , mids : number [ ] ) {
return apiManager . invokeApi ( 'messages.sendScheduledMessages' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
id : mids.map ( mid = > this . getLocalMessageId ( mid ) )
} ) . then ( updates = > {
apiUpdatesManager . processUpdateMessage ( updates ) ;
} ) ;
}
public deleteScheduledMessages ( peerId : number , mids : number [ ] ) {
return apiManager . invokeApi ( 'messages.deleteScheduledMessages' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
id : mids.map ( mid = > this . getLocalMessageId ( mid ) )
} ) . then ( updates = > {
apiUpdatesManager . processUpdateMessage ( updates ) ;
} ) ;
}
public getHistory ( peerId : number , maxId = 0 , limit : number , backLimit? : number , threadId? : number ) {
if ( this . migratedFromTo [ peerId ] ) {
peerId = this . migratedFromTo [ peerId ] ;
}
const historyStorage = this . getHistoryStorage ( peerId , threadId ) ;
const unreadOffset = 0 ;
const unreadSkip = false ;
let offset = 0 ;
let offsetNotFound = false ;
let isMigrated = false ;
let reqPeerId = peerId ;
if ( this . migratedToFrom [ peerId ] ) {
isMigrated = true ;
/ * i f ( m a x I d & & m a x I d < a p p M e s s a g e s I d s M a n a g e r . f u l l M s g I d M o d u l u s ) {
reqPeerId = this . migratedToFrom [ peerId ] ;
} * /
}
if ( maxId > 0 ) {
offsetNotFound = true ;
for ( ; offset < historyStorage . history . length ; offset ++ ) {
if ( maxId > historyStorage . history [ offset ] ) {
offsetNotFound = false ;
break ;
}
}
}
if ( ! offsetNotFound && (
historyStorage . count !== null && historyStorage . history . length == historyStorage . count ||
historyStorage . history . length >= offset + limit
) ) {
if ( backLimit ) {
backLimit = Math . min ( offset , backLimit ) ;
offset = Math . max ( 0 , offset - backLimit ) ;
limit += backLimit ;
} else {
limit = limit ;
}
let history = historyStorage . history . slice ( offset , offset + limit ) ;
if ( ! maxId && historyStorage . pending . length ) {
history = historyStorage . pending . slice ( ) . concat ( history ) ;
}
return this . wrapHistoryResult ( peerId , {
count : historyStorage.count ,
history : history ,
unreadOffset : unreadOffset ,
unreadSkip : unreadSkip
} ) ;
}
if ( offsetNotFound ) {
offset = 0 ;
}
if ( ( backLimit || unreadSkip || maxId ) && historyStorage . history . indexOf ( maxId ) == - 1 ) {
if ( backLimit ) {
offset = - backLimit ;
limit += backLimit ;
}
return this . requestHistory ( reqPeerId , maxId , limit , offset , undefined , threadId ) . then ( ( historyResult ) = > {
historyStorage . count = ( historyResult as MessagesMessages . messagesMessagesSlice ) . count || historyResult . messages . length ;
if ( isMigrated ) {
historyStorage . count ++ ;
}
let history : number [ ] = [ ] ;
historyResult . messages . forEach ( ( message : any ) = > {
history . push ( message . mid ) ;
} ) ;
if ( ! maxId && historyStorage . pending . length ) {
history = historyStorage . pending . slice ( ) . concat ( history ) ;
}
return this . wrapHistoryResult ( peerId , {
count : historyStorage.count ,
history : history ,
unreadOffset : unreadOffset ,
unreadSkip : unreadSkip
} ) ;
} ) ;
}
return this . fillHistoryStorage ( peerId , maxId , limit , historyStorage , threadId ) . then ( ( ) = > {
offset = 0 ;
if ( maxId > 0 ) {
for ( offset = 0 ; offset < historyStorage . history . length ; offset ++ ) {
if ( maxId > historyStorage . history [ offset ] ) {
break ;
}
}
}
let history = historyStorage . history . slice ( backLimit ? Math . max ( offset - backLimit , 0 ) : offset , offset + limit ) ;
if ( ! maxId && historyStorage . pending . length ) {
history = historyStorage . pending . slice ( ) . concat ( history ) ;
}
return this . wrapHistoryResult ( peerId , {
count : historyStorage.count ,
history : history ,
unreadOffset : unreadOffset ,
unreadSkip : unreadSkip
} ) ;
} ) ;
}
public fillHistoryStorage ( peerId : number , maxId : number , fullLimit : number , historyStorage : HistoryStorage , threadId? : number ) : Promise < boolean > {
// this.log('fill history storage', peerId, maxId, fullLimit, angular.copy(historyStorage))
const offset = ( this . migratedFromTo [ peerId ] && ! maxId ) ? 1 : 0 ;
return this . requestHistory ( peerId , maxId , fullLimit , offset , undefined , threadId ) . then ( ( historyResult ) = > {
historyStorage . count = ( historyResult as MessagesMessages . messagesMessagesSlice ) . count || historyResult . messages . length ;
if ( ! maxId && historyResult . messages . length ) {
maxId = this . incrementMessageId ( ( historyResult . messages [ 0 ] as MyMessage ) . mid , 1 ) ;
}
let offset = 0 ;
if ( maxId > 0 ) {
for ( ; offset < historyStorage . history . length ; offset ++ ) {
if ( maxId > historyStorage . history [ offset ] ) {
break ;
}
}
}
const wasTotalCount = historyStorage . history . length ;
historyStorage . history . splice ( offset , historyStorage . history . length - offset ) ;
historyResult . messages . forEach ( ( message ) = > {
if ( this . mergeReplyKeyboard ( historyStorage , message ) ) {
rootScope . broadcast ( 'history_reply_markup' , { peerId } ) ;
}
historyStorage . history . push ( ( message as MyMessage ) . mid ) ;
} ) ;
const totalCount = historyStorage . history . length ;
fullLimit -= ( totalCount - wasTotalCount ) ;
const migratedNextPeer = this . migratedFromTo [ peerId ] ;
const migratedPrevPeer = this . migratedToFrom [ peerId ]
const isMigrated = migratedNextPeer !== undefined || migratedPrevPeer !== undefined ;
if ( isMigrated ) {
historyStorage . count = Math . max ( historyStorage . count , totalCount ) + 1 ;
}
if ( fullLimit > 0 ) {
maxId = historyStorage . history [ totalCount - 1 ] ;
if ( isMigrated ) {
if ( ! historyResult . messages . length ) {
if ( migratedPrevPeer ) {
maxId = 0 ;
peerId = migratedPrevPeer ;
} else {
historyStorage . count = totalCount ;
return true ;
}
}
return this . fillHistoryStorage ( peerId , maxId , fullLimit , historyStorage , threadId ) ;
} else if ( totalCount < historyStorage . count ) {
return this . fillHistoryStorage ( peerId , maxId , fullLimit , historyStorage , threadId ) ;
}
}
return true ;
} ) ;
}
public wrapHistoryResult ( peerId : number , result : HistoryResult ) {
if ( result . unreadOffset ) {
for ( let i = result . history . length - 1 ; i >= 0 ; i -- ) {
const message = this . getMessageByPeer ( peerId , result . history [ i ] ) ;
if ( ! message . deleted && ! message . pFlags . out && message . pFlags . unread ) {
result . unreadOffset = i + 1 ;
break ;
}
}
}
return result ;
}
public requestHistory ( peerId : number , maxId : number , limit = 0 , offset = 0 , offsetDate = 0 , threadId = 0 ) : Promise < Exclude < MessagesMessages , MessagesMessages.messagesMessagesNotModified > > {
const isChannel = appPeersManager . isChannel ( peerId ) ;
//console.trace('requestHistory', peerId, maxId, limit, offset);
//rootScope.broadcast('history_request');
const options = {
peer : appPeersManager.getInputPeerById ( peerId ) ,
msg_id : this.getLocalMessageId ( threadId ) || 0 ,
offset_id : this.getLocalMessageId ( maxId ) || 0 ,
offset_date : offsetDate ,
add_offset : offset ,
limit : limit ,
max_id : 0 ,
min_id : 0 ,
hash : 0
} ;
const promise : ReturnType < AppMessagesManager [ ' requestHistory ' ] > = apiManager . invokeApi ( threadId ? 'messages.getReplies' : 'messages.getHistory' , options , {
//timeout: APITIMEOUT,
noErrorBox : true
} ) as any ;
return promise . then ( ( historyResult ) = > {
this . log ( 'requestHistory result:' , peerId , historyResult , maxId , limit , offset ) ;
appUsersManager . saveApiUsers ( historyResult . users ) ;
appChatsManager . saveApiChats ( historyResult . chats ) ;
this . saveMessages ( historyResult . messages ) ;
if ( isChannel ) {
apiUpdatesManager . addChannelState ( - peerId , ( historyResult as MessagesMessages . messagesChannelMessages ) . pts ) ;
}
let length = historyResult . messages . length ;
if ( length && historyResult . messages [ length - 1 ] . deleted ) {
historyResult . messages . splice ( length - 1 , 1 ) ;
length -- ;
( historyResult as MessagesMessages . messagesMessagesSlice ) . count -- ;
}
// will load more history if last message is album grouped (because it can be not last item)
const historyStorage = this . getHistoryStorage ( peerId , threadId ) ;
// historyResult.messages: desc sorted
if ( length && ( historyResult . messages [ length - 1 ] as Message . message ) . grouped_id
&& ( historyStorage . history . length + historyResult . messages . length ) < ( historyResult as MessagesMessages . messagesMessagesSlice ) . count ) {
return this . requestHistory ( peerId , ( historyResult . messages [ length - 1 ] as Message . message ) . mid , 10 , 0 , offsetDate , threadId ) . then ( ( _historyResult ) = > {
return historyResult ;
} ) ;
}
return historyResult as any ;
} , ( error ) = > {
switch ( error . type ) {
case 'CHANNEL_PRIVATE' :
let channel = appChatsManager . getChat ( - peerId ) ;
channel = { _ : 'channelForbidden' , access_hash : channel.access_hash , title : channel.title } ;
apiUpdatesManager . processUpdateMessage ( {
_ : 'updates' ,
updates : [ {
_ : 'updateChannel' ,
channel_id : - peerId
} ] ,
chats : [ channel ] ,
users : [ ]
} ) ;
break ;
}
throw error ;
} ) ;
}
public fetchSingleMessages() {
if ( this . fetchSingleMessagesPromise ) {
return this . fetchSingleMessagesPromise ;
}
return this . fetchSingleMessagesPromise = new Promise ( ( resolve ) = > {
setTimeout ( ( ) = > {
let promises : Promise < void > [ ] = [ ] ;
for ( const peerId in this . needSingleMessages ) {
const mids = this . needSingleMessages [ peerId ] ;
delete this . needSingleMessages [ peerId ] ;
const msgIds : InputMessage [ ] = mids . map ( ( msgId : number ) = > {
return {
_ : 'inputMessageID' ,
id : this.getLocalMessageId ( msgId )
} ;
} ) ;
let promise : Promise < MethodDeclMap [ ' channels.getMessages ' ] [ ' res ' ] | MethodDeclMap [ ' messages.getMessages ' ] [ ' res ' ] > ;
if ( + peerId < 0 && appPeersManager . isChannel ( + peerId ) ) {
promise = apiManager . invokeApi ( 'channels.getMessages' , {
channel : appChatsManager.getChannelInput ( - + peerId ) ,
id : msgIds
} ) ;
} else {
promise = apiManager . invokeApi ( 'messages.getMessages' , {
id : msgIds
} ) ;
}
promises . push ( promise . then ( getMessagesResult = > {
if ( getMessagesResult . _ != 'messages.messagesNotModified' ) {
appUsersManager . saveApiUsers ( getMessagesResult . users ) ;
appChatsManager . saveApiChats ( getMessagesResult . chats ) ;
this . saveMessages ( getMessagesResult . messages ) ;
}
rootScope . broadcast ( 'messages_downloaded' , { peerId : + peerId , mids } ) ;
} ) ) ;
}
Promise . all ( promises ) . finally ( ( ) = > {
this . fetchSingleMessagesPromise = null ;
if ( Object . keys ( this . needSingleMessages ) . length ) this . fetchSingleMessages ( ) ;
resolve ( ) ;
} ) ;
} , 0 ) ;
} ) ;
}
public wrapSingleMessage ( peerId : number , msgId : number , overwrite = false ) : Promise < void > {
if ( ! this . getMessageByPeer ( peerId , msgId ) . deleted && ! overwrite ) {
rootScope . broadcast ( 'messages_downloaded' , { peerId , mids : [ msgId ] } ) ;
return Promise . resolve ( ) ;
} else if ( ! this . needSingleMessages [ peerId ] || this . needSingleMessages [ peerId ] . indexOf ( msgId ) === - 1 ) {
( this . needSingleMessages [ peerId ] ? ? ( this . needSingleMessages [ peerId ] = [ ] ) ) . push ( msgId ) ;
return this . fetchSingleMessages ( ) ;
} else if ( this . fetchSingleMessagesPromise ) {
return this . fetchSingleMessagesPromise ;
}
}
public setTyping ( peerId : number , _action : any ) : Promise < boolean > {
if ( ! rootScope . myId || ! peerId || ! this . canWriteToPeer ( peerId ) ) return Promise . resolve ( false ) ;
const action : SendMessageAction = typeof ( _action ) == 'string' ? { _ : _action } : _action ;
return apiManager . invokeApi ( 'messages.setTyping' , {
peer : appPeersManager.getInputPeerById ( peerId ) ,
action
} ) as Promise < boolean > ;
}
private handleDeletedMessages ( peerId : number , storage : MessagesStorage , messages : number [ ] ) {
const history : {
count : number ,
unread : number ,
msgs : { [ mid : number ] : true } ,
albums ? : { [ groupId : string ] : Set < number > } ,
} = { count : 0 , unread : 0 , msgs : { } } as any ;
for ( const mid of messages ) {
const message : MyMessage = this . getMessageFromStorage ( storage , mid ) ;
if ( message . deleted ) continue ;
if ( ( message as Message . message ) . media ) {
// @ts-ignore
const c = message . media . webpage || message . media ;
const smth = c . photo || c . document ;
if ( smth ? . file_reference ) {
referenceDatabase . deleteContext ( smth . file_reference , { type : 'message' , peerId , messageId : mid } ) ;
}
// @ts-ignore
if ( message . media . webpage ) {
// @ts-ignore
appWebPagesManager . deleteWebPageFromPending ( message . media . webpage , mid ) ;
}
}
this . updateMessageRepliesIfNeeded ( message ) ;
if ( ! message . pFlags . out && message . pFlags . unread ) {
history . unread ++ ;
}
history . count ++ ;
history . msgs [ mid ] = true ;
message . deleted = true ;
if ( message . _ != 'messageService' && message . grouped_id ) {
const groupedStorage = this . groupedMessagesStorage [ message . grouped_id ] ;
if ( groupedStorage ) {
delete groupedStorage [ mid ] ;
if ( ! history . albums ) history . albums = { } ;
( history . albums [ message . grouped_id ] || ( history . albums [ message . grouped_id ] = new Set ( ) ) ) . add ( mid ) ;
if ( ! Object . keys ( groupedStorage ) . length ) {
delete history . albums ;
delete this . groupedMessagesStorage [ message . grouped_id ] ;
}
}
}
delete storage [ mid ] ;
const peerMessagesToHandle = this . newMessagesToHandle [ peerId ] ;
if ( peerMessagesToHandle && peerMessagesToHandle . length ) {
const peerMessagesHandlePos = peerMessagesToHandle . indexOf ( mid ) ;
if ( peerMessagesHandlePos != - 1 ) {
peerMessagesToHandle . splice ( peerMessagesHandlePos ) ;
}
}
}
if ( history . albums ) {
for ( const groupId in history . albums ) {
rootScope . broadcast ( 'album_edit' , { peerId , groupId , deletedMids : [ . . . history . albums [ groupId ] ] } ) ;
/ * c o n s t m i d s = t h i s . g e t M i d s B y A l b u m ( g r o u p I d ) ;
if ( mids . length ) {
const mid = Math . max ( . . . mids ) ;
rootScope . $broadcast ( 'message_edit' , { peerId , mid , justMedia : false } ) ;
} * /
}
}
return history ;
}
private handleEditedMessage ( oldMessage : any , newMessage : any ) {
if ( oldMessage . media ? . webpage ) {
appWebPagesManager . deleteWebPageFromPending ( oldMessage . media . webpage , oldMessage . mid ) ;
}
}
}
const appMessagesManager = new AppMessagesManager ( ) ;
MOUNT_CLASS_TO && ( MOUNT_CLASS_TO . appMessagesManager = appMessagesManager ) ;
export default appMessagesManager ;