/*! * Webogram v0.3.0 - messaging web application for MTProto * https://github.com/zhukov/webogram * Copyright (C) 2014 Igor Zhukov * https://github.com/zhukov/webogram/blob/master/LICENSE */ 'use strict'; /* Services */ angular.module('myApp.services', []) .service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService, Storage) { var users = {}, cachedPhotoLocations = {}, contactsFillPromise, contactsList, contactsIndex = SearchIndexManager.createIndex(), serverTimeOffset = 0; Storage.get('server_time_offset').then(function (to) { if (to) { serverTimeOffset = to; } }); function fillContacts () { if (contactsFillPromise) { return contactsFillPromise; } return contactsFillPromise = MtpApiManager.invokeApi('contacts.getContacts', { hash: '' }).then(function (result) { var userID, searchText, i; contactsList = []; saveApiUsers(result.users); for (var i = 0; i < result.contacts.length; i++) { userID = result.contacts[i].user_id; contactsList.push(userID); SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex); } return contactsList; }); } function getUserSearchText (id) { var user = users[id]; if (!user) { return false; } return (user.first_name || '') + ' ' + (user.last_name || '') + ' ' + (user.phone || ''); } function getContacts (query) { return fillContacts().then(function (contactsList) { if (angular.isString(query) && query.length) { var results = SearchIndexManager.search(query, contactsIndex), filteredContactsList = []; for (var i = 0; i < contactsList.length; i++) { if (results[contactsList[i]]) { filteredContactsList.push(contactsList[i]) } } contactsList = filteredContactsList; } return contactsList; }); }; function saveApiUsers (apiUsers) { angular.forEach(apiUsers, saveApiUser); }; function saveApiUser (apiUser) { if (!angular.isObject(apiUser)) { return; } if (apiUser.phone) { apiUser.rPhone = $filter('phoneNumber')(apiUser.phone); } apiUser.num = (Math.abs(apiUser.id) % 8) + 1; if (apiUser.first_name) { apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true}); apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}); } else { apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || 'DELETED'; apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || 'DELETED'; } apiUser.sortName = SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || '')); apiUser.sortStatus = apiUser.status && (apiUser.status.expires || apiUser.status.was_online) || 0; if (users[apiUser.id] === undefined) { users[apiUser.id] = apiUser; } else { safeReplaceObject(users[apiUser.id], apiUser); } $rootScope.$broadcast('user_update', apiUser.id); if (cachedPhotoLocations[apiUser.id] !== undefined) { safeReplaceObject(cachedPhotoLocations[apiUser.id], apiUser && apiUser.photo && apiUser.photo.photo_small || {empty: true}); } }; function getUser (id) { if (angular.isObject(id)) { return id; } return users[id] || {id: id, deleted: true}; } function hasUser(id) { return angular.isObject(users[id]); } function getUserPhoto(id, placeholder) { var user = getUser(id); if (id == 333000) { return { placeholder: 'img/placeholders/DialogListAvatarSystem@2x.png' } }; if (cachedPhotoLocations[id] === undefined) { cachedPhotoLocations[id] = user && user.photo && user.photo.photo_small || {empty: true}; } return { num: user.num, placeholder: 'img/placeholders/' + placeholder + 'Avatar' + user.num + '@2x.png', location: cachedPhotoLocations[id] }; } function getUserString (id) { var user = getUser(id); return 'u' + id + (user.access_hash ? '_' + user.access_hash : ''); } function getUserInput (id) { var user = getUser(id); if (user._ == 'userSelf') { return {_: 'inputUserSelf'}; } return { _: 'inputUserForeign', user_id: id, access_hash: user.access_hash || 0 }; } function updateUsersStatuses () { var timestampNow = tsNow(true) + serverTimeOffset; angular.forEach(users, function (user) { if (user.status && user.status._ == 'userStatusOnline' && user.status.expires > timestampNow) { user.status = {_: 'userStatusOffline', was_online: user.status.expires}; $rootScope.$broadcast('user_update', user.id); } }); } function wrapForFull (id) { var user = getUser(id); return user; } function openUser (userID, accessHash) { var scope = $rootScope.$new(); scope.userID = userID; var className = 'user_modal_window page_modal'; if (Config.Mobile) { className += ' mobile_modal'; } var modalInstance = $modal.open({ templateUrl: templateUrl('user_modal'), controller: 'UserModalController', scope: scope, windowClass: className }); }; $rootScope.openUser = openUser; function importContact (phone, firstName, lastName) { return MtpApiManager.invokeApi('contacts.importContacts', { contacts: [{ _: 'inputPhoneContact', client_id: '1', phone: phone, first_name: firstName, last_name: lastName }], replace: false }).then(function (importedContactsResult) { saveApiUsers(importedContactsResult.users); var foundUserID = false; angular.forEach(importedContactsResult.imported, function (importedContact) { onContactUpdated(foundUserID = importedContact.user_id, true); }); return foundUserID ? 1 : 0; }); }; function importContacts (contacts) { var inputContacts = [], i, j; for (i = 0; i < contacts.length; i++) { for (j = 0; j < contacts[i].phones.length; j++) { inputContacts.push({ _: 'inputPhoneContact', client_id: (i << 16 | j).toString(10), phone: contacts[i].phones[j], first_name: contacts[i].first_name, last_name: contacts[i].last_name }); } } return MtpApiManager.invokeApi('contacts.importContacts', { contacts: inputContacts, replace: false }).then(function (importedContactsResult) { saveApiUsers(importedContactsResult.users); var result = []; angular.forEach(importedContactsResult.imported, function (importedContact) { onContactUpdated(importedContact.user_id, true); result.push(importedContact.user_id); }); return result; }); }; function deleteContacts (userIDs) { var ids = [] angular.forEach(userIDs, function (userID) { ids.push({_: 'inputUserContact', user_id: userID}) }); return MtpApiManager.invokeApi('contacts.deleteContacts', { id: ids }).then(function () { angular.forEach(userIDs, function (userID) { onContactUpdated(userID, false); }); }) } function onContactUpdated (userID, isContact) { if (angular.isArray(contactsList)) { var curPos = curIsContact = contactsList.indexOf(userID), curIsContact = curPos != -1; if (isContact != curIsContact) { if (isContact) { contactsList.push(userID); SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex); } else { contactsList.splice(curPos, 1); } } } } function openImportContact () { return $modal.open({ templateUrl: templateUrl('import_contact_modal'), controller: 'ImportContactModalController', windowClass: 'import_contact_modal_window' }).result.then(function (foundUserID) { if (!foundUserID) { return $q.reject(); } return foundUserID; }); }; $rootScope.$on('apiUpdate', function (e, update) { // console.log('on apiUpdate', update); switch (update._) { case 'updateUserStatus': var userID = update.user_id, user = users[userID]; if (user) { user.status = update.status; user.sortStatus = update.status && (update.status.expires || update.status.was_online) || 0; $rootScope.$broadcast('user_update', userID); } break; case 'updateUserPhoto': var userID = update.user_id; if (users[userID]) { safeReplaceObject(users[userID].photo, update.photo); if (cachedPhotoLocations[userID] !== undefined) { safeReplaceObject(cachedPhotoLocations[userID], update.photo && update.photo.photo_small || {empty: true}); } $rootScope.$broadcast('user_update', userID); } break; case 'updateContactLink': onContactUpdated(update.user_id, update.my_link._ == 'contacts.myLinkContact'); break; } }); setInterval(updateUsersStatuses, 60000); return { getContacts: getContacts, saveApiUsers: saveApiUsers, saveApiUser: saveApiUser, getUser: getUser, getUserInput: getUserInput, getUserPhoto: getUserPhoto, getUserString: getUserString, getUserSearchText: getUserSearchText, hasUser: hasUser, importContact: importContact, importContacts: importContacts, deleteContacts: deleteContacts, wrapForFull: wrapForFull, openUser: openUser, openImportContact: openImportContact } }) .service('PhonebookContactsService', function ($q, $modal, $sce, FileManager) { return { isAvailable: isAvailable, openPhonebookImport: openPhonebookImport, getPhonebookContacts: getPhonebookContacts }; function isAvailable () { if (Config.Mobile && Config.Navigator.ffos && Config.Modes.packed) { try { return navigator.mozContacts && navigator.mozContacts.getAll; } catch (e) { console.error(dT(), 'phonebook n/a', e); return false; } } return false; } function openPhonebookImport () { return $modal.open({ templateUrl: templateUrl('phonebook_modal'), controller: 'PhonebookModalController', windowClass: 'phonebook_modal_window page_modal mobile_modal' }); } function getPhonebookContacts () { try { var request = window.navigator.mozContacts.getAll({}); } catch (e) { return $q.reject(e); } var deferred = $q.defer(), contacts = [], count = 0; request.onsuccess = function () { if (this.result) { var contact = { id: count, first_name: (this.result.givenName || []).join(' '), last_name: (this.result.familyName || []).join(' '), phones: [] }; if (this.result.tel !== undefined) { for (var i = 0; i < this.result.tel.length; i++) { contact.phones.push(this.result.tel[i].value); } } if (this.result.photo && this.result.photo[0]) { try { contact.photo = FileManager.getUrl(this.result.photo[0]); } catch (e) {} } if (!contact.photo) { contact.photo = 'img/placeholders/UserAvatar' + ((Math.abs(count) % 8) + 1) + '@2x.png'; } contact.photo = $sce.trustAsResourceUrl(contact.photo); count++; contacts.push(contact); } if (!this.result || count >= 1000) { deferred.resolve(contacts); return; } this.continue(); } request.onerror = function (e) { console.log('phonebook error', e, e.type, e.message); deferred.reject(e); } return deferred.promise; } }) .service('AppChatsManager', function ($rootScope, $modal, MtpApiFileManager, MtpApiManager, AppUsersManager, RichTextProcessor) { var chats = {}, cachedPhotoLocations = {}; function saveApiChats (apiChats) { angular.forEach(apiChats, saveApiChat); }; function saveApiChat (apiChat) { if (!angular.isObject(apiChat)) { return; } apiChat.rTitle = RichTextProcessor.wrapRichText(apiChat.title, {noLinks: true, noLinebreaks: true}) || 'DELETED'; if (chats[apiChat.id] === undefined) { chats[apiChat.id] = apiChat; } else { safeReplaceObject(chats[apiChat.id], apiChat); } if (cachedPhotoLocations[apiChat.id] !== undefined) { safeReplaceObject(cachedPhotoLocations[apiChat.id], apiChat && apiChat.photo && apiChat.photo.photo_small || {empty: true}); } }; function getChat (id) { return chats[id] || {id: id, deleted: true}; } function hasChat (id) { return angular.isObject(chats[id]); } function getChatPhoto(id, placeholder) { var chat = getChat(id); if (cachedPhotoLocations[id] === undefined) { cachedPhotoLocations[id] = chat && chat.photo && chat.photo.photo_small || {empty: true}; } return { placeholder: 'img/placeholders/' + placeholder + 'Avatar'+((Math.abs(id) % 4) + 1)+'@2x.png', location: cachedPhotoLocations[id] }; } function getChatString (id) { var chat = getChat(id); return 'g' + id; } function wrapForFull (id, fullChat) { var chatFull = angular.copy(fullChat), chat = getChat(id); if (chatFull.participants && chatFull.participants._ == 'chatParticipants') { MtpApiManager.getUserID().then(function (myID) { angular.forEach(chatFull.participants.participants, function(participant){ participant.user = AppUsersManager.getUser(participant.user_id); participant.inviter = AppUsersManager.getUser(participant.inviter_id); participant.canKick = myID != participant.user_id && (myID == chatFull.participants.admin_id || myID == participant.inviter_id); }); }); } chatFull.thumb = { placeholder: 'img/placeholders/GroupAvatar'+((Math.abs(id) % 4) + 1)+'@2x.png', location: chat && chat.photo && chat.photo.photo_small, width: 120, height: 120, size: 0 }; chatFull.peerString = getChatString(id); chatFull.chat = chat; return chatFull; } function openChat (chatID, accessHash) { var scope = $rootScope.$new(); scope.chatID = chatID; var className = 'chat_modal_window page_modal'; if (Config.Mobile) { className += ' mobile_modal'; } var modalInstance = $modal.open({ templateUrl: templateUrl('chat_modal'), controller: 'ChatModalController', scope: scope, windowClass: className }); } $rootScope.openChat = openChat; return { saveApiChats: saveApiChats, saveApiChat: saveApiChat, getChat: getChat, getChatPhoto: getChatPhoto, getChatString: getChatString, hasChat: hasChat, wrapForFull: wrapForFull, openChat: openChat } }) .service('AppPeersManager', function (AppUsersManager, AppChatsManager) { return { getInputPeer: function (peerString) { var isUser = peerString.charAt(0) == 'u', peerParams = peerString.substr(1).split('_'); return isUser ? {_: 'inputPeerForeign', user_id: peerParams[0], access_hash: peerParams[1]} : {_: 'inputPeerChat', chat_id: peerParams[0]}; }, getInputPeerByID: function (peerID) { if (peerID > 0) { return { _: 'inputPeerForeign', user_id: peerID, access_hash: AppUsersManager.getUser(peerID).access_hash || 0 }; } else if (peerID < 0) { return { _: 'inputPeerChat', chat_id: -peerID }; } }, getPeerSearchText: function (peerID) { var text; if (peerID > 0) { text = AppUsersManager.getUserSearchText(peerID); } else if (peerID < 0) { var chat = AppChatsManager.getChat(-peerID); text = chat.title || ''; } return text; }, getOutputPeer: function (peerID) { return peerID > 0 ? {_: 'peerUser', user_id: peerID} : {_: 'peerChat', chat_id: -peerID}; }, getPeerID: function (peerString) { if (angular.isObject(peerString)) { return peerString.user_id ? peerString.user_id : -peerString.chat_id; } var isUser = peerString.charAt(0) == 'u', peerParams = peerString.substr(1).split('_'); return isUser ? peerParams[0] : -peerParams[0] || 0; }, getPeer: function (peerID) { return peerID > 0 ? AppUsersManager.getUser(peerID) : AppChatsManager.getChat(-peerID); }, getPeerPhoto: function (peerID, userPlaceholder, chatPlaceholder) { return peerID > 0 ? AppUsersManager.getUserPhoto(peerID, userPlaceholder) : AppChatsManager.getChatPhoto(-peerID, chatPlaceholder) } } }) .service('SearchIndexManager', function () { var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g, trimRe = /^\s+|\s$/g, accentsReplace = { a: /[åáâäà]/g, e: /[éêëè]/g, i: /[íîïì]/g, o: /[óôöò]/g, u: /[úûüù]/g, c: /ç/g, ss: /ß/g } return { createIndex: createIndex, indexObject: indexObject, cleanSearchText: cleanSearchText, search: search }; function createIndex () { return { shortIndexes: {}, fullTexts: {} } } function cleanSearchText (text) { text = text.replace(badCharsRe, ' ').replace(trimRe, '').toLowerCase(); for (var key in accentsReplace) { if (accentsReplace.hasOwnProperty(key)) { text = text.replace(accentsReplace[key], key); } } return text; } function indexObject (id, searchText, searchIndex) { if (searchIndex.fullTexts[id] !== undefined) { return false; } searchText = cleanSearchText(searchText); if (!searchText.length) { return false; } var shortIndexes = searchIndex.shortIndexes; searchIndex.fullTexts[id] = searchText; angular.forEach(searchText.split(' '), function(searchWord) { var len = Math.min(searchWord.length, 3), wordPart, i; for (i = 1; i <= len; i++) { wordPart = searchWord.substr(0, i); if (shortIndexes[wordPart] === undefined) { shortIndexes[wordPart] = [id]; } else { shortIndexes[wordPart].push(id); } } }); } function search (query, searchIndex) { var shortIndexes = searchIndex.shortIndexes, fullTexts = searchIndex.fullTexts; query = cleanSearchText(query); var queryWords = query.split(' '), foundObjs = false, newFoundObjs, i, j, searchText, found; for (i = 0; i < queryWords.length; i++) { newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)]; if (!newFoundObjs) { foundObjs = []; break; } if (foundObjs === false || foundObjs.length > newFoundObjs.length) { foundObjs = newFoundObjs; } } newFoundObjs = {}; for (j = 0; j < foundObjs.length; j++) { found = true; searchText = fullTexts[foundObjs[j]]; for (i = 0; i < queryWords.length; i++) { if (searchText.indexOf(queryWords[i]) == -1) { found = false; break; } } if (found) { newFoundObjs[foundObjs[j]] = true; } } return newFoundObjs; } }) .service('AppMessagesManager', function ($q, $rootScope, $location, $filter, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager, PeersSelectService,Storage) { var messagesStorage = {}; var messagesForHistory = {}; var messagesForDialogs = {}; var historiesStorage = {}; var dialogsStorage = {count: null, dialogs: []}; var pendingByRandomID = {}; var pendingByMessageID = {}; var pendingAfterMsgs = {}; var sendFilePromise = $q.when(); var tempID = -1; var dialogsIndex = SearchIndexManager.createIndex(), cachedResults = {query: false}; var lastSearchFilter = {}, lastSearchResults = []; var serverTimeOffset = 0, timestampNow = tsNow(true), midnightNoOffset = timestampNow - (timestampNow % 86400), midnightOffseted = new Date(), midnightOffset; Storage.get('server_time_offset').then(function (to) { if (to) { serverTimeOffset = to; } }); midnightOffseted.setHours(0); midnightOffseted.setMinutes(0); midnightOffseted.setSeconds(0); midnightOffset = midnightNoOffset - (Math.floor(+midnightOffseted / 1000)); NotificationsManager.start(); function getDialogs (query, maxID, limit) { var curDialogStorage = dialogsStorage; if (angular.isString(query) && query.length) { if (!limit || cachedResults.query !== query) { cachedResults.query = query; var results = SearchIndexManager.search(query, dialogsIndex); cachedResults.dialogs = []; angular.forEach(dialogsStorage.dialogs, function (dialog) { if (results[dialog.peerID]) { cachedResults.dialogs.push(dialog); } }); cachedResults.count = cachedResults.dialogs.length; } curDialogStorage = cachedResults; } else { cachedResults.query = false; } var offset = 0; if (maxID > 0) { for (offset = 0; offset < curDialogStorage.dialogs.length; offset++) { if (maxID > curDialogStorage.dialogs[offset].top_message) { break; } } } if (curDialogStorage.count !== null && curDialogStorage.dialogs.length == curDialogStorage.count || curDialogStorage.dialogs.length >= offset + (limit || 1) ) { return $q.when({ count: curDialogStorage.count, dialogs: curDialogStorage.dialogs.slice(offset, offset + (limit || 20)) }); } limit = limit || 20; return MtpApiManager.invokeApi('messages.getDialogs', { offset: offset, limit: limit, max_id: maxID || 0 }).then(function (dialogsResult) { AppUsersManager.saveApiUsers(dialogsResult.users); AppChatsManager.saveApiChats(dialogsResult.chats); saveMessages(dialogsResult.messages); if (maxID > 0) { for (offset = 0; offset < curDialogStorage.dialogs.length; offset++) { if (maxID > curDialogStorage.dialogs[offset].top_message) { break; } } } curDialogStorage.count = dialogsResult.count || dialogsResult.dialogs.length; curDialogStorage.dialogs.splice(offset, curDialogStorage.dialogs.length - offset); angular.forEach(dialogsResult.dialogs, function (dialog) { var peerID = AppPeersManager.getPeerID(dialog.peer), peerText = AppPeersManager.getPeerSearchText(peerID); SearchIndexManager.indexObject(peerID, peerText, dialogsIndex); curDialogStorage.dialogs.push({ peerID: peerID, top_message: dialog.top_message, unread_count: dialog.unread_count }); if (historiesStorage[peerID] === undefined) { historiesStorage[peerID] = {count: null, history: [dialog.top_message], pending: []} } NotificationsManager.savePeerSettings(peerID, dialog.notify_settings); }); return { count: curDialogStorage.count, dialogs: curDialogStorage.dialogs.slice(offset, offset + limit) }; }); } function requestHistory (inputPeer, maxID, limit, offset) { return MtpApiManager.invokeApi('messages.getHistory', { peer: inputPeer, offset: offset || 0, limit: limit || 0, max_id: maxID || 0 }).then(function (historyResult) { AppUsersManager.saveApiUsers(historyResult.users); AppChatsManager.saveApiChats(historyResult.chats); saveMessages(historyResult.messages); return historyResult; }); } function fillHistoryStorage (inputPeer, maxID, fullLimit, historyStorage) { // console.log('fill history storage', inputPeer, maxID, fullLimit, angular.copy(historyStorage)); return requestHistory (inputPeer, maxID, fullLimit).then(function (historyResult) { historyStorage.count = historyResult.count || historyResult.messages.length; var offset = 0; if (maxID > 0) { for (offset = 0; offset < historyStorage.history.length; offset++) { if (maxID > historyStorage.history[offset]) { break; } } } historyStorage.history.splice(offset, historyStorage.history.length - offset); angular.forEach(historyResult.messages, function (message) { historyStorage.history.push(message.id); }); fullLimit -= historyResult.messages.length; if (fullLimit > 0 && historyStorage.history.length < historyStorage.count) { maxID = historyStorage.history[historyStorage.history.length - 1]; return fillHistoryStorage(inputPeer, maxID, fullLimit, historyStorage); } return true; }); }; function getHistory (inputPeer, maxID, limit, backLimit) { var peerID = AppPeersManager.getPeerID(inputPeer), historyStorage = historiesStorage[peerID], offset = 0, offsetNotFound = false, unreadOffset = false, unreadSkip = false, resultPending = []; if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } else if (!maxID && historyStorage.pending.length) { resultPending = historyStorage.pending.slice(); } if (!limit && !maxID) { var foundDialog = getDialogByPeerID(peerID); if (foundDialog && foundDialog[0] && foundDialog[0].unread_count > 1) { var unreadCount = foundDialog[0].unread_count; if (unreadSkip = (unreadCount > 50)) { limit = 10; unreadOffset = 6; offset = unreadCount - unreadOffset; } else { limit = Math.max(10, unreadCount + 2); unreadOffset = unreadCount; } } } else if (maxID > 0) { offsetNotFound = true; for (offset = 0; 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 || 1) )) { if (backLimit) { backLimit = Math.min(offset, backLimit); offset = Math.max(0, offset - backLimit); limit += backLimit; } else { limit = limit || (offset ? 20 : 5); } return $q.when({ count: historyStorage.count, history: resultPending.concat(historyStorage.history.slice(offset, offset + limit)), unreadOffset: unreadOffset, unreadSkip: unreadSkip }); } if (!backLimit && !limit) { limit = 20; } if (offsetNotFound) { offset = 0; } if (backLimit || unreadSkip || maxID && historyStorage.history.indexOf(maxID) == -1) { if (backLimit) { offset = -backLimit; limit += backLimit; } return requestHistory(inputPeer, maxID, limit, offset).then(function (historyResult) { historyStorage.count = historyResult.count || historyResult.messages.length; var history = []; angular.forEach(historyResult.messages, function (message) { history.push(message.id); }); return { count: historyStorage.count, history: resultPending.concat(history), unreadOffset: unreadOffset, unreadSkip: unreadSkip }; }) } return fillHistoryStorage(inputPeer, maxID, limit, historyStorage).then(function () { offset = 0; if (maxID > 0) { for (offset = 0; offset < historyStorage.history.length; offset++) { if (maxID > historyStorage.history[offset]) { break; } } } return { count: historyStorage.count, history: resultPending.concat(historyStorage.history.slice(offset, offset + limit)), unreadOffset: unreadOffset, unreadSkip: unreadSkip }; }); } function getSearch (inputPeer, query, inputFilter, maxID, limit) { var foundMsgs = [], useSearchCache = !query, peerID = AppPeersManager.getPeerID(inputPeer), newSearchFilter = {peer: peerID, filter: inputFilter}, sameSearchCache = useSearchCache && angular.equals(lastSearchFilter, newSearchFilter); if (useSearchCache && !sameSearchCache) { lastSearchFilter = newSearchFilter; lastSearchResults = []; } if (!maxID && !query) { var historyStorage = historiesStorage[peerID]; if (historyStorage !== undefined && historyStorage.history.length) { var neededContents = {}, neededLimit = limit || 20, i, message; switch (inputFilter._) { case 'inputMessagesFilterPhotos': neededContents['messageMediaPhoto'] = true; break; case 'inputMessagesFilterVideo': neededContents['messageMediaVideo'] = true; break; case 'inputMessagesFilterPhotoVideo': neededContents['messageMediaPhoto'] = true; neededContents['messageMediaVideo'] = true; break; case 'inputMessagesFilterDocument': neededContents['messageMediaDocument'] = true; break; case 'inputMessagesFilterAudio': neededContents['messageMediaAudio'] = true; break; } for (i = 0; i < historyStorage.history.length; i++) { message = messagesStorage[historyStorage.history[i]]; if (message.media && neededContents[message.media._]) { foundMsgs.push(message.id); if (foundMsgs.length >= neededLimit) { break; } } } } // console.log(dT(), sameSearchCache, foundMsgs, lastSearchResults); if (foundMsgs.length < neededLimit && lastSearchResults.length && sameSearchCache) { var minID = foundMsgs.length ? foundMsgs[foundMsgs.length - 1] : 0xFFFFFFFF; for (var i = 0; i < lastSearchResults.length; i++) { if (lastSearchResults[i] < minID) { foundMsgs.push(lastSearchResults[i]); if (foundMsgs.length >= neededLimit) { break; } } } } // console.log(dT(), foundMsgs); } if (foundMsgs.length || limit == 1000) { if (useSearchCache) { lastSearchResults = listMergeSorted(lastSearchResults, foundMsgs); } return $q.when({ count: null, history: foundMsgs }); } return MtpApiManager.invokeApi('messages.search', { peer: inputPeer, q: query || '', filter: inputFilter || {_: 'inputMessagesFilterEmpty'}, min_date: 0, max_date: 0, limit: limit || 20, max_id: maxID || 0 }).then(function (searchResult) { AppUsersManager.saveApiUsers(searchResult.users); AppChatsManager.saveApiChats(searchResult.chats); saveMessages(searchResult.messages); var foundCount = searchResult.count || searchResult.messages.length; foundMsgs = []; angular.forEach(searchResult.messages, function (message) { foundMsgs.push(message.id); }); if (useSearchCache) { lastSearchResults = listMergeSorted(lastSearchResults, foundMsgs); } return { count: foundCount, history: foundMsgs }; }); } function getMessage (messageID) { return messagesStorage[messageID] || {deleted: true}; } function deleteMessages (messageIDs) { return MtpApiManager.invokeApi('messages.deleteMessages', { id: messageIDs }).then(function (deletedMessageIDs) { ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateDeleteMessages', messages: deletedMessageIDs } }); return deletedMessageIDs; }); } function processAffectedHistory (inputPeer, affectedHistory, method) { if (!ApiUpdatesManager.processUpdateMessage({ _: 'updates', seq: affectedHistory.seq, updates: [] })) { return false; } if (!affectedHistory.offset) { return $q.when(); } return MtpApiManager.invokeApi(method, { peer: inputPeer, offset: affectedHistory.offset, max_id: 0 }).then(function (affectedHistory) { return processAffectedHistory(inputPeer, affectedHistory, method); }); } function readHistory (inputPeer) { // console.trace('start read'); var peerID = AppPeersManager.getPeerID(inputPeer), historyStorage = historiesStorage[peerID], foundDialog = getDialogByPeerID(peerID); if (!foundDialog[0] || !foundDialog[0].unread_count) { if (!historyStorage || !historyStorage.history.length) { return false; } var messageID, message, foundUnread = false; for (i = historyStorage.history.length; i >= 0; i--) { messageID = historyStorage.history[i]; message = messagesStorage[messageID]; // console.log('ms', message); if (message && !message.out && message.unread) { foundUnread = true; break; } } if (!foundUnread) { return false; } } if (historyStorage.readPromise) { return historyStorage.readPromise; } historyStorage.readPromise = MtpApiManager.invokeApi('messages.readHistory', { peer: inputPeer, offset: 0, max_id: 0 }).then(function (affectedHistory) { return processAffectedHistory(inputPeer, affectedHistory, 'messages.readHistory'); }).then(function () { if (foundDialog[0]) { // console.log('done read history', peerID); foundDialog[0].unread_count = 0; $rootScope.$broadcast('dialog_unread', {peerID: peerID, count: 0}); } })['finally'](function () { delete historyStorage.readPromise; }); if (historyStorage && historyStorage.history.length) { var messageID, message, i, peerID, foundDialog, dialog; for (i = 0; i < historyStorage.history.length; i++) { messageID = historyStorage.history[i]; message = messagesStorage[messageID]; if (message && !message.out) { message.unread = false; if (messagesForHistory[messageID]) { messagesForHistory[messageID].unread = false; } if (messagesForDialogs[messageID]) { messagesForDialogs[messageID].unread = false; } } } } return historyStorage.readPromise; } function flushHistory (inputPeer) { // console.log('start flush'); var peerID = AppPeersManager.getPeerID(inputPeer), historyStorage = historiesStorage[peerID]; return MtpApiManager.invokeApi('messages.deleteHistory', { peer: inputPeer, offset: 0 }).then(function (affectedHistory) { return processAffectedHistory(inputPeer, affectedHistory, 'messages.deleteHistory'); }).then(function () { var foundDialog = getDialogByPeerID(peerID); if (foundDialog[0]) { dialogsStorage.dialogs.splice(foundDialog[1], 1); } delete historiesStorage[peerID]; $rootScope.$broadcast('dialog_flush', {peerID: peerID}); }); } function saveMessages (apiMessages) { angular.forEach(apiMessages, function (apiMessage) { messagesStorage[apiMessage.id] = apiMessage; apiMessage.date -= serverTimeOffset; if (apiMessage.media && apiMessage.media._ == 'messageMediaPhoto') { AppPhotosManager.savePhoto(apiMessage.media.photo); } if (apiMessage.media && apiMessage.media._ == 'messageMediaVideo') { AppVideoManager.saveVideo(apiMessage.media.video); } if (apiMessage.media && apiMessage.media._ == 'messageMediaDocument') { AppDocsManager.saveDoc(apiMessage.media.document); } if (apiMessage.media && apiMessage.media._ == 'messageMediaAudio') { AppAudioManager.saveAudio(apiMessage.media.audio); } if (apiMessage.action && apiMessage.action._ == 'messageActionChatEditPhoto') { AppPhotosManager.savePhoto(apiMessage.action.photo); } }); } function sendText(peerID, text) { var messageID = tempID--, randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)], randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(), historyStorage = historiesStorage[peerID], inputPeer = AppPeersManager.getInputPeerByID(peerID), message; if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } MtpApiManager.getUserID().then(function (fromID) { message = { _: 'message', id: messageID, from_id: fromID, to_id: AppPeersManager.getOutputPeer(peerID), out: true, unread: true, date: tsNow(true) + serverTimeOffset, message: text, media: {_: 'messageMediaEmpty'}, random_id: randomIDS, pending: true }; var toggleError = function (on) { var historyMessage = messagesForHistory[messageID]; if (on) { message.error = true; if (historyMessage) { historyMessage.error = true; } } else { delete message.error; if (historyMessage) { delete historyMessage.error; } } } message.send = function () { toggleError(false); var sentRequestOptions = {}; if (pendingAfterMsgs[peerID]) { sentRequestOptions.afterMessageID = pendingAfterMsgs[peerID].messageID; } MtpApiManager.invokeApi('messages.sendMessage', { peer: inputPeer, message: text, random_id: randomID }, sentRequestOptions).then(function (sentMessage) { message.date = sentMessage.date; message.id = sentMessage.id; ApiUpdatesManager.processUpdateMessage({ _: 'updates', users: [], chats: [], seq: sentMessage.seq, updates: [{ _: 'updateMessageID', random_id: randomIDS, id: sentMessage.id }, { _: 'updateNewMessage', message: message, pts: sentMessage.pts }] }); }, function (error) { toggleError(true); })['finally'](function () { if (pendingAfterMsgs[peerID] === sentRequestOptions) { delete pendingAfterMsgs[peerID]; } }); pendingAfterMsgs[peerID] = sentRequestOptions; }; saveMessages([message]); historyStorage.pending.unshift(messageID); $rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true}); // setTimeout(function () { message.send(); // }, 5000); }); pendingByRandomID[randomIDS] = [peerID, messageID]; }; function sendFile(peerID, file, options) { options = options || {}; var messageID = tempID--, randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)], randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(), historyStorage = historiesStorage[peerID], inputPeer = AppPeersManager.getInputPeerByID(peerID), attachType, fileName; if (!options.isMedia) { attachType = 'document'; fileName = 'document.' + file.type.split('/')[1]; } else if (['image/jpeg', 'image/png', 'image/bmp'].indexOf(file.type) >= 0) { attachType = 'photo'; fileName = 'photo.' + file.type.split('/')[1]; } else if (file.type.substr(0, 6) == 'video/') { attachType = 'video'; fileName = 'video.mp4'; } else if (file.type.substr(0, 6) == 'audio/') { attachType = 'audio'; fileName = 'audio.' + file.type.split('/')[1] || 'mp3'; } else { attachType = 'document'; fileName = 'document.' + file.type.split('/')[1]; } if (!file.name) { file.name = fileName; } if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } MtpApiManager.getUserID().then(function (fromID) { var media = { _: 'messageMediaPending', type: attachType, file_name: file.name, size: file.size, progress: {percent: 1, total: file.size} }; var message = { _: 'message', id: messageID, from_id: fromID, to_id: AppPeersManager.getOutputPeer(peerID), out: true, unread: true, date: tsNow(true) + serverTimeOffset, message: '', media: media, random_id: randomIDS, pending: true }; var toggleError = function (on) { var historyMessage = messagesForHistory[messageID]; if (on) { message.error = true; if (historyMessage) { historyMessage.error = true; } } else { delete message.error; if (historyMessage) { delete historyMessage.error; } } } message.send = function () { var sendFileDeferred = $q.defer(); sendFilePromise.then(function () { var uploaded = false, uploadPromise = MtpApiFileManager.uploadFile(file); uploadPromise.then(function (inputFile) { uploaded = true; var inputMedia; switch (attachType) { case 'photo': inputMedia = {_: 'inputMediaUploadedPhoto', file: inputFile}; break; case 'video': inputMedia = {_: 'inputMediaUploadedVideo', file: inputFile, duration: 0, w: 0, h: 0, mime_type: file.type}; break; case 'audio': inputMedia = {_: 'inputMediaUploadedAudio', file: inputFile, duration: 0, mime_type: file.type}; break; case 'document': default: inputMedia = {_: 'inputMediaUploadedDocument', file: inputFile, file_name: file.name, mime_type: file.type}; } MtpApiManager.invokeApi('messages.sendMedia', { peer: inputPeer, media: inputMedia, random_id: randomID }).then(function (statedMessage) { message.date = statedMessage.message.date; message.id = statedMessage.message.id; message.media = statedMessage.message.media; ApiUpdatesManager.processUpdateMessage({ _: 'updates', users: statedMessage.users, chats: statedMessage.chats, seq: statedMessage.seq, updates: [{ _: 'updateMessageID', random_id: randomIDS, id: statedMessage.message.id }, { _: 'updateNewMessage', message: message, pts: statedMessage.pts }] }); }, function (error) { toggleError(true); }); }, function (error) { toggleError(true); }, function (progress) { // console.log('upload progress', progress); media.progress.done = progress.done; media.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update', {peerID: peerID}); }); media.progress.cancel = function () { if (!uploaded) { sendFileDeferred.resolve(); uploadPromise.cancel(); cancelPendingMessage(randomIDS); } } uploadPromise['finally'](function () { sendFileDeferred.resolve(); }); }); sendFilePromise = sendFileDeferred.promise; }; saveMessages([message]); historyStorage.pending.unshift(messageID); $rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true}); message.send(); }); pendingByRandomID[randomIDS] = [peerID, messageID]; } function sendOther(peerID, inputMedia) { var messageID = tempID--, randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)], randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(), historyStorage = historiesStorage[peerID], inputPeer = AppPeersManager.getInputPeerByID(peerID); if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } MtpApiManager.getUserID().then(function (fromID) { var media; switch (inputMedia._) { case 'inputMediaContact': media = angular.extend({}, inputMedia, {_: 'messageMediaContact'}); break; case 'inputMediaPhoto': media = {photo: AppPhotosManager.getPhoto(inputMedia.id.id)}; break; } var message = { _: 'message', id: messageID, from_id: fromID, to_id: AppPeersManager.getOutputPeer(peerID), out: true, unread: true, date: tsNow(true) + serverTimeOffset, message: '', media: media, random_id: randomIDS, pending: true }; var toggleError = function (on) { var historyMessage = messagesForHistory[messageID]; if (on) { message.error = true; if (historyMessage) { historyMessage.error = true; } } else { delete message.error; if (historyMessage) { delete historyMessage.error; } } } message.send = function () { MtpApiManager.invokeApi('messages.sendMedia', { peer: inputPeer, media: inputMedia, random_id: randomID }).then(function (statedMessage) { message.date = statedMessage.message.date; message.id = statedMessage.message.id; message.media = statedMessage.message.media; ApiUpdatesManager.processUpdateMessage({ _: 'updates', users: statedMessage.users, chats: statedMessage.chats, seq: statedMessage.seq, updates: [{ _: 'updateMessageID', random_id: randomIDS, id: statedMessage.message.id }, { _: 'updateNewMessage', message: message, pts: statedMessage.pts }] }); }, function (error) { toggleError(true); }); }; saveMessages([message]); historyStorage.pending.unshift(messageID); $rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true}); message.send(); }); pendingByRandomID[randomIDS] = [peerID, messageID]; } function forwardMessages (peerID, msgIDs) { msgIDs = msgIDs.sort(); return MtpApiManager.invokeApi('messages.forwardMessages', { peer: AppPeersManager.getInputPeerByID(peerID), id: msgIDs }).then(function (statedMessages) { var updates = []; angular.forEach(statedMessages.messages, function(apiMessage) { updates.push({ _: 'updateNewMessage', message: apiMessage, pts: statedMessages.pts }); }); ApiUpdatesManager.processUpdateMessage({ _: 'updates', users: statedMessages.users, chats: statedMessages.chats, seq: statedMessages.seq, updates: updates }); }); }; function cancelPendingMessage (randomID) { var pendingData = pendingByRandomID[randomID]; console.log('pending', randomID, pendingData); if (pendingData) { var peerID = pendingData[0], tempID = pendingData[1], historyStorage = historiesStorage[peerID], i; ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateDeleteMessages', messages: [tempID] } }); for (i = 0; i < historyStorage.pending.length; i++) { if (historyStorage.pending[i] == tempID) { historyStorage.pending.splice(i, 1); break; } } delete messagesForHistory[tempID]; delete messagesStorage[tempID]; return true; } return false; } function finalizePendingMessage(randomID, finalMessage) { var pendingData = pendingByRandomID[randomID]; // console.log('pdata', randomID, pendingData); if (pendingData) { var peerID = pendingData[0], tempID = pendingData[1], historyStorage = historiesStorage[peerID], index = false, message = false, historyMessage = false, i; // console.log('pending', randomID, historyStorage.pending); for (i = 0; i < historyStorage.pending.length; i++) { if (historyStorage.pending[i] == tempID) { historyStorage.pending.splice(i, 1); break; } } if (message = messagesStorage[tempID]) { delete message.pending; delete message.error; delete message.random_id; delete message.send; } if (historyMessage = messagesForHistory[tempID]) { messagesForHistory[finalMessage.id] = angular.extend(historyMessage, wrapForHistory(finalMessage.id)); delete historyMessage.pending; delete historyMessage.error; delete historyMessage.random_id; delete historyMessage.send; } delete messagesForHistory[tempID]; delete messagesStorage[tempID]; return message; } return false; } function getMessagePeer (message) { var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0; if (toID < 0) { return toID; } else if (message.out) { return toID; } return message.from_id; } function wrapForDialog (msgID, unreadCount) { if (messagesForDialogs[msgID] !== undefined) { return messagesForDialogs[msgID]; } var message = angular.copy(messagesStorage[msgID]) || {id: msgID}; message.fromUser = AppUsersManager.getUser(message.from_id); if (message.chatID = message.to_id.chat_id) { message.peerID = -message.chatID; message.peerData = AppChatsManager.getChat(message.chatID); message.peerString = AppChatsManager.getChatString(message.chatID); } else { message.peerID = message.out ? message.to_id.user_id : message.from_id; message.peerData = AppUsersManager.getUser(message.peerID); message.peerString = AppUsersManager.getUserString(message.peerID); } message.peerPhoto = AppPeersManager.getPeerPhoto(message.peerID, 'User', 'Group'); message.unreadCount = unreadCount; if (message._ == 'messageService' && message.action.user_id) { message.action.user = AppUsersManager.getUser(message.action.user_id); } if (message.message && message.message.length) { message.richMessage = RichTextProcessor.wrapRichText(message.message.substr(0, 64), {noLinks: true, noLinebreaks: true}); } message.dateText = $filter('dateOrTime')(message.date); return messagesForDialogs[msgID] = message; } function wrapForHistory (msgID) { if (messagesForHistory[msgID] !== undefined) { return messagesForHistory[msgID]; } var message = angular.copy(messagesStorage[msgID]) || {id: msgID}; if (message.media && message.media.progress !== undefined) { message.media.progress = messagesStorage[msgID].media.progress; } if (message.media) { switch (message.media._) { case 'messageMediaPhoto': message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id) break; case 'messageMediaVideo': message.media.video = AppVideoManager.wrapForHistory(message.media.video.id); break; case 'messageMediaDocument': message.media.document = AppDocsManager.wrapForHistory(message.media.document.id); break; case 'messageMediaAudio': message.media.audio = AppAudioManager.wrapForHistory(message.media.audio.id); break; case 'messageMediaContact': message.media.rFullName = RichTextProcessor.wrapRichText( message.media.first_name + ' ' + (message.media.last_name || ''), {noLinks: true, noLinebreaks: true} ); break; } } else if (message.action) { switch (message.action._) { case 'messageActionChatEditPhoto': message.action.photo = AppPhotosManager.wrapForHistory(message.action.photo.id); break; case 'messageActionChatCreate': case 'messageActionChatEditTitle': message.action.rTitle = RichTextProcessor.wrapRichText(message.action.title, {noLinks: true, noLinebreaks: true}) || 'DELETED'; break; } } if (message.message && message.message.length) { message.richMessage = RichTextProcessor.wrapRichText(message.message); } return messagesForHistory[msgID] = message; } function regroupWrappedHistory (history, limit) { if (!history || !history.length) { return; } var start = 0, len = history.length, end = len, i, curDay, prevDay, curMessage, prevMessage; if (limit > 0) { end = Math.min(limit, len); } else if (limit < 0) { start = Math.max(0, end + limit); } for (i = start; i < end; i++) { curMessage = history[i]; curDay = Math.floor((curMessage.date + midnightOffset) / 86400); if (curDay === prevDay) { delete curMessage.needDate; } else if (!i || prevMessage) { curMessage.needDate = true; } if (prevMessage && curMessage.from_id == prevMessage.from_id && !prevMessage.fwd_from_id == !curMessage.fwd_from_id && !prevMessage.action && !curMessage.action && curMessage.date < prevMessage.date + 900) { var singleLine = curMessage.message && curMessage.message.length < 70 && curMessage.message.indexOf("\n") == -1; if (curMessage.fwd_from_id && curMessage.fwd_from_id == prevMessage.fwd_from_id) { curMessage.grouped = singleLine ? 'im_grouped_fwd_short' : 'im_grouped_fwd'; } else { curMessage.grouped = !curMessage.fwd_from_id && singleLine ? 'im_grouped_short' : 'im_grouped'; } if (curMessage.fwd_from_id) { if (!prevMessage.grouped) { prevMessage.grouped = 'im_grouped_fwd_start'; } if (curMessage.grouped && i == len - 1) { curMessage.grouped += ' im_grouped_fwd_end'; } } } else if (prevMessage || !i) { delete curMessage.grouped; if (prevMessage && prevMessage.grouped && prevMessage.fwd_from_id) { prevMessage.grouped += ' im_grouped_fwd_end'; } } prevMessage = curMessage; prevDay = curDay; } } function getDialogByPeerID (peerID) { for (var i = 0; i < dialogsStorage.dialogs.length; i++) { if (dialogsStorage.dialogs[i].peerID == peerID) { return [dialogsStorage.dialogs[i], i]; } } return []; } function notifyAboutMessage (message) { var peerID = getMessagePeer(message); var fromUser = AppUsersManager.getUser(message.from_id); var fromPhoto = AppUsersManager.getUserPhoto(message.from_id, 'User'); var peerString; var notification = {}, notificationMessage = false, notificationPhoto; if (message.message) { notificationMessage = RichTextProcessor.wrapPlainText(message.message); } else if (message.media && message.media._ != 'messageMediaEmpty') { switch (message.media._) { case 'messageMediaPhoto': notificationMessage = 'Photo'; break; case 'messageMediaVideo': notificationMessage = 'Video'; break; case 'messageMediaDocument': notificationMessage = 'Document'; break; case 'messageMediaAudio': notificationMessage = 'Voice message'; break; case 'messageMediaGeo': notificationMessage = 'Location'; break; case 'messageMediaContact': notificationMessage = 'Contact'; break; default: notificationMessage = 'Attachment'; break; } } else if (message._ == 'messageService') { switch (message.action._) { case 'messageActionChatCreate': notificationMessage = 'created the group'; break; case 'messageActionChatEditTitle': notificationMessage = 'changed group name'; break; case 'messageActionChatEditPhoto': notificationMessage = 'changed group photo'; break; case 'messageActionChatDeletePhoto': notificationMessage = 'removed group photo'; break; case 'messageActionChatAddUser': notificationMessage = message.action.user_id == message.from_id ? 'returned to group' : 'invited user'; break; case 'messageActionChatDeleteUser': notificationMessage = message.action.user_id == message.from_id ? 'left group' : 'kicked user'; break; } } if (peerID > 0) { notification.title = (fromUser.first_name || '') + (fromUser.first_name && fromUser.last_name ? ' ' : '') + (fromUser.last_name || ''); notificationPhoto = fromPhoto; peerString = AppUsersManager.getUserString(peerID); } else { notification.title = (fromUser.first_name || fromUser.last_name || 'Somebody') + ' @ ' + (AppChatsManager.getChat(-peerID).title || 'Unknown chat'); notificationPhoto = AppChatsManager.getChatPhoto(-peerID, 'Group'); peerString = AppChatsManager.getChatString(-peerID); } notification.title = RichTextProcessor.wrapPlainText(notification.title); notification.onclick = function () { $rootScope.$broadcast('history_focus', {peerString: peerString}); }; notification.message = notificationMessage; notification.image = notificationPhoto.placeholder; notification.key = 'msg' + message.id; notification.tag = peerString; if (notificationPhoto.location && !notificationPhoto.location.empty) { MtpApiFileManager.downloadSmallFile(notificationPhoto.location, notificationPhoto.size).then(function (url) { notification.image = url; if (message.unread) { NotificationsManager.notify(notification); } }); } else { NotificationsManager.notify(notification); } } if (window.navigator.mozSetMessageHandler) { window.navigator.mozSetMessageHandler('activity', function(activityRequest) { var source = activityRequest.source; console.log(dT(), 'Received activity', source.name, source.data); if (source.name === 'share' && source.data.blobs.length > 0) { PeersSelectService.selectPeer({confirm_type: 'EXT_SHARE_PEER'}).then(function (peerString) { var peerID = AppPeersManager.getPeerID(peerString); angular.forEach(source.data.blobs, function (blob) { sendFile(peerID, blob, {isMedia: true}); }); $rootScope.$broadcast('history_focus', {peerString: peerString}); }); } }); } $rootScope.$on('apiUpdate', function (e, update) { // if (update._ != 'updateUserStatus') { // console.log('on apiUpdate', update); // } switch (update._) { case 'updateMessageID': pendingByMessageID[update.id] = update.random_id; break; case 'updateNewMessage': var message = update.message, peerID = getMessagePeer(message), historyStorage = historiesStorage[peerID]; if (historyStorage !== undefined) { var topMsgID = historiesStorage[peerID].history[0]; if (historiesStorage[peerID].history.indexOf(message.id) != -1) { return false; } else { historyStorage.history.unshift(message.id); if (message.id > 0 && message.id < topMsgID || true) { historyStorage.history.sort(function (a, b) { return b - a; }); } } } else { historyStorage = historiesStorage[peerID] = {count: null, history: [message.id], pending: []}; } saveMessages([message]); if (historyStorage.count !== null) { historyStorage.count++; } var randomID = pendingByMessageID[message.id], pendingMessage; if (randomID) { if (pendingMessage = finalizePendingMessage(randomID, message)) { $rootScope.$broadcast('history_update', {peerID: peerID}); } delete pendingByMessageID[message.id]; } if (!pendingMessage) { $rootScope.$broadcast('history_append', {peerID: peerID, messageID: message.id}); } var foundDialog = getDialogByPeerID(peerID), dialog; if (foundDialog.length) { dialog = foundDialog[0]; dialogsStorage.dialogs.splice(foundDialog[1], 1); } else { dialog = {peerID: peerID, unread_count: 0, top_message: false} } if (!message.out && message.unread) { // console.log('inc unread count', dialog.unread_count); dialog.unread_count++; } dialog.top_message = message.id; // console.log('new message', message, peerID, historyStorage, foundDialog, dialog); SearchIndexManager.indexObject(peerID, AppPeersManager.getPeerSearchText(peerID), dialogsIndex); dialogsStorage.dialogs.unshift(dialog); $rootScope.$broadcast('dialogs_update', dialog); if ((Config.Mobile && $rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE) && !message.out && message.unread) { NotificationsManager.getPeerMuted(peerID).then(function (muted) { if (!message.unread || muted) { return; } notifyAboutMessage(message); }); } break; case 'updateReadMessages': var dialogsUpdated = {}, messageID, message, i, peerID, foundDialog, dialog; for (i = 0; i < update.messages.length; i++) { messageID = update.messages[i]; message = messagesStorage[messageID]; // console.log('read', messageID, message.unread, message); if (message && message.unread) { message.unread = false; if (messagesForHistory[messageID]) { messagesForHistory[messageID].unread = false; } if (messagesForDialogs[messageID]) { messagesForDialogs[messageID].unread = false; } peerID = getMessagePeer(message); if (!message.out) { foundDialog = getDialogByPeerID(peerID); if (foundDialog) { dialogsUpdated[peerID] = --foundDialog[0].unread_count; } NotificationsManager.cancel('msg' + messageID); } } } angular.forEach(dialogsUpdated, function(count, peerID) { $rootScope.$broadcast('dialog_unread', {peerID: peerID, count: count}); }); break; case 'updateDeleteMessages': var dialogsUpdated = {}, historiesUpdated = {}, messageID, message, i, peerID, foundDialog, dialog, history; for (i = 0; i < update.messages.length; i++) { messageID = update.messages[i]; message = messagesStorage[messageID]; if (message) { peerID = getMessagePeer(message); history = historiesUpdated[peerID] || (historiesUpdated[peerID] = {count: 0, unread: 0, msgs: {}}); if (!message.out && message.unread) { history.unread++; NotificationsManager.cancel('msg' + messageID); } history.count++; history.msgs[messageID] = true; if (messagesForHistory[messageID]) { messagesForHistory[messageID].deleted = true; delete messagesForHistory[messageID]; } if (messagesForDialogs[messageID]) { messagesForDialogs[messageID].deleted = true; delete messagesForDialogs[messageID]; } message.deleted = true; messagesStorage[messageID] = { deleted: true, id: messageID, from_id: message.from_id, to_id: message.to_id, out: message.out, unread: message.unread, date: message.date }; } } angular.forEach(historiesUpdated, function (updatedData, peerID) { var foundDialog = getDialogByPeerID(peerID); if (foundDialog) { if (updatedData.unread) { foundDialog[0].unread_count -= updatedData.unread; $rootScope.$broadcast('dialog_unread', {peerID: peerID, count: foundDialog[0].unread_count}); } } var historyStorage = historiesStorage[peerID]; if (historyStorage !== undefined) { var newHistory = [], newPending = []; for (var i = 0; i < historyStorage.history.length; i++) { if (!updatedData.msgs[historyStorage.history[i]]) { newHistory.push(historyStorage.history[i]); } } historyStorage.history = newHistory; for (var i = 0; i < historyStorage.pending.length; i++) { if (!updatedData.msgs[historyStorage.pending[i]]) { newPending.push(historyStorage.pending[i]); } } historyStorage.pending = newPending; $rootScope.$broadcast('history_delete', {peerID: peerID, msgs: updatedData.msgs}); } }); break; } }); return { getDialogs: getDialogs, getHistory: getHistory, getSearch: getSearch, getMessage: getMessage, readHistory: readHistory, flushHistory: flushHistory, deleteMessages: deleteMessages, saveMessages: saveMessages, sendText: sendText, sendFile: sendFile, sendOther: sendOther, forwardMessages: forwardMessages, getMessagePeer: getMessagePeer, wrapForDialog: wrapForDialog, wrapForHistory: wrapForHistory, regroupWrappedHistory: regroupWrappedHistory } }) .service('AppPhotosManager', function ($modal, $window, $timeout, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager, FileManager) { var photos = {}, windowW = $(window).width(), windowH = $(window).height(); function savePhoto (apiPhoto) { photos[apiPhoto.id] = apiPhoto; angular.forEach(apiPhoto.sizes, function (photoSize) { if (photoSize._ == 'photoCachedSize') { MtpApiFileManager.saveSmallFile(photoSize.location, photoSize.bytes); // Memory photoSize.size = photoSize.bytes.length; delete photoSize.bytes; photoSize._ = 'photoSize'; } }); }; function choosePhotoSize (photo, width, height) { var bestPhotoSize = {_: 'photoSizeEmpty'}, bestDiff = 0xFFFFFF; angular.forEach(photo.sizes, function (photoSize) { var diff = Math.abs(photoSize.w * photoSize.h - width * height); if (diff < bestDiff) { bestPhotoSize = photoSize; bestDiff = diff; } }); // console.log('choosing', photo, width, height, bestPhotoSize); return bestPhotoSize; } function getUserPhotos (inputUser, maxID, limit) { return MtpApiManager.invokeApi('photos.getUserPhotos', { user_id: inputUser, offset: 0, limit: limit || 20, max_id: maxID || 0 }).then(function (photosResult) { AppUsersManager.saveApiUsers(photosResult.users); var photoIDs = []; for (var i = 0; i < photosResult.photos.length; i++) { savePhoto(photosResult.photos[i]); photoIDs.push(photosResult.photos[i].id) } return { count: photosResult.count || photosResult.photos.length, photos: photoIDs }; }); } function preloadPhoto (photoID) { if (!photos[photoID]) { return; } var photo = photos[photoID], fullWidth = $(window).width() - 36, fullHeight = $($window).height() - 150, fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight); if (fullPhotoSize && !fullPhotoSize.preloaded) { fullPhotoSize.preloaded = true; if (fullPhotoSize.size) { MtpApiFileManager.downloadFile(fullPhotoSize.location.dc_id, { _: 'inputFileLocation', volume_id: fullPhotoSize.location.volume_id, local_id: fullPhotoSize.location.local_id, secret: fullPhotoSize.location.secret }, fullPhotoSize.size); } else { MtpApiFileManager.downloadSmallFile(fullPhotoSize.location); } } }; $rootScope.preloadPhoto = preloadPhoto; function getPhoto (photoID) { return photos[photoID] || {_: 'photoEmpty'}; } function wrapForHistory (photoID) { var photo = angular.copy(photos[photoID]) || {_: 'photoEmpty'}, width = Math.min(windowW - 80, 260), height = Math.min(windowH - 100, 260), thumbPhotoSize = choosePhotoSize(photo, width, height), thumb = { placeholder: 'img/placeholders/PhotoThumbConversation.gif', width: width, height: height }; // console.log('chosen photo size', photoID, thumbPhotoSize); if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { if ((thumbPhotoSize.w / thumbPhotoSize.h) > (width / height)) { thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w); } else { thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h); if (thumb.width > width) { thumb.height = parseInt(thumb.height * width / thumb.width); thumb.width = width; } } thumb.location = thumbPhotoSize.location; thumb.size = thumbPhotoSize.size; } else { thumb.width = 100; thumb.height = 100; } photo.thumb = thumb; return photo; } function wrapForFull (photoID) { var photo = wrapForHistory(photoID), fullWidth = $(window).width() - (Config.Mobile ? 20 : 36), fullHeight = $($window).height() - 150, fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight), full = { placeholder: 'img/placeholders/PhotoThumbModal.gif' }; if (fullWidth > 800) { fullWidth -= 200; } full.width = fullWidth; full.height = fullHeight; if (fullPhotoSize && fullPhotoSize._ != 'photoSizeEmpty') { if ((fullPhotoSize.w / fullPhotoSize.h) > (fullWidth / fullHeight)) { full.height = parseInt(fullPhotoSize.h * fullWidth / fullPhotoSize.w); } else { full.width = parseInt(fullPhotoSize.w * fullHeight / fullPhotoSize.h); if (full.width > fullWidth) { full.height = parseInt(full.height * fullWidth / full.width); full.width = fullWidth; } } if (!Config.Mobile && full.width >= fullPhotoSize.w && full.height >= fullPhotoSize.h) { full.width = fullPhotoSize.w; full.height = fullPhotoSize.h; } full.modalWidth = Math.max(full.width, Math.min(400, fullWidth)); full.location = fullPhotoSize.location; full.size = fullPhotoSize.size; } photo.full = full; photo.fromUser = AppUsersManager.getUser(photo.user_id); return photo; } function openPhoto (photoID, peerListID) { if (!photoID || photoID === '0') { return false; } var scope = $rootScope.$new(true); scope.photoID = photoID; if (peerListID < 0) { scope.userID = -peerListID; } else{ scope.messageID = peerListID; } var modalInstance = $modal.open({ templateUrl: templateUrl('photo_modal'), controller: scope.userID ? 'UserpicModalController' : 'PhotoModalController', scope: scope, windowClass: 'photo_modal_window' }); } function downloadPhoto (photoID) { var photo = photos[photoID], ext = 'jpg', mimeType = 'image/jpeg', fileName = 'photo' + photoID + '.' + ext, fullWidth = $(window).width() - 36, fullHeight = $($window).height() - 150, fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight), inputFileLocation = { _: 'inputFileLocation', volume_id: fullPhotoSize.location.volume_id, local_id: fullPhotoSize.location.local_id, secret: fullPhotoSize.location.secret }; FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) { MtpApiFileManager.downloadFile( fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, { mime: mimeType, toFileEntry: writableFileEntry }).then(function (url) { console.log('file save done'); }, function (e) { console.log('photo download failed', e); }); }, function () { MtpApiFileManager.downloadFile( fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType} ).then(function (url) { FileManager.download(url, mimeType, fileName); }, function (e) { console.log('photo download failed', e); }); }); }; $rootScope.openPhoto = openPhoto; return { savePhoto: savePhoto, preloadPhoto: preloadPhoto, getUserPhotos: getUserPhotos, getPhoto: getPhoto, wrapForHistory: wrapForHistory, wrapForFull: wrapForFull, openPhoto: openPhoto, downloadPhoto: downloadPhoto } }) .service('AppVideoManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager, FileManager) { var videos = {}, videosForHistory = {}, windowW = $(window).width(), windowH = $(window).height(); function saveVideo (apiVideo) { videos[apiVideo.id] = apiVideo; if (apiVideo.thumb && apiVideo.thumb._ == 'photoCachedSize') { MtpApiFileManager.saveSmallFile(apiVideo.thumb.location, apiVideo.thumb.bytes); // Memory apiVideo.thumb.size = apiVideo.thumb.bytes.length; delete apiVideo.thumb.bytes; apiVideo.thumb._ = 'photoSize'; } }; function wrapForHistory (videoID) { if (videosForHistory[videoID] !== undefined) { return videosForHistory[videoID]; } var video = angular.copy(videos[videoID]), width = Math.min(windowW - 80, windowW <= 479 ? 260 : 200), height = Math.min(windowH - 100, windowW <= 479 ? 260 : 200), thumbPhotoSize = video.thumb, thumb = { placeholder: 'img/placeholders/VideoThumbConversation.gif', width: width, height: height }; if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { if ((thumbPhotoSize.w / thumbPhotoSize.h) > (width / height)) { thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w); } else { thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h); if (thumb.width > width) { thumb.height = parseInt(thumb.height * width / thumb.width); thumb.width = width; } } thumb.location = thumbPhotoSize.location; thumb.size = thumbPhotoSize.size; } video.thumb = thumb; return videosForHistory[videoID] = video; } function wrapForFull (videoID) { var video = wrapForHistory(videoID), fullWidth = Math.min($(window).width() - 60, 542), fullHeight = $($window).height() - 150, fullPhotoSize = video, full = { placeholder: 'img/placeholders/VideoThumbModal.gif', width: fullWidth, height: fullHeight, }; if (!video.w || !video.h) { full.height = full.width = Math.min(fullWidth, fullHeight); } else if (video.w > video.h) { full.height = parseInt(video.h * fullWidth / video.w); } else { full.width = parseInt(video.w * fullHeight / video.h); if (full.width > fullWidth) { full.height = parseInt(full.height * fullWidth / full.width); full.width = fullWidth; } } // console.log(222, video.w, video.h, full.width, full.height); video.full = full; video.fullThumb = angular.copy(video.thumb); video.fullThumb.width = full.width; video.fullThumb.height = full.height; video.fromUser = AppUsersManager.getUser(video.user_id); return video; } function openVideo (videoID, messageID) { var scope = $rootScope.$new(true); scope.videoID = videoID; scope.messageID = messageID; var modalInstance = $modal.open({ templateUrl: templateUrl('video_modal'), controller: 'VideoModalController', scope: scope, windowClass: 'video_modal_window' }); } function downloadVideo (videoID, accessHash, popup) { var video = videos[videoID], historyVideo = videosForHistory[videoID] || video || {}, inputFileLocation = { _: 'inputVideoFileLocation', id: videoID, access_hash: accessHash || video.access_hash }; historyVideo.progress = {enabled: true, percent: 1, total: video.size}; function updateDownloadProgress (progress) { console.log('dl progress', progress); historyVideo.progress.done = progress.done; historyVideo.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update'); } var ext = 'mp4', mimeType = 'video/mpeg4', fileName = 'video' + videoID + '.' + ext; FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) { var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, { mime: mimeType, toFileEntry: writableFileEntry }); downloadPromise.then(function (url) { delete historyVideo.progress; console.log('file save done'); }, function (e) { console.log('video download failed', e); historyVideo.progress.enabled = false; }, updateDownloadProgress); historyVideo.progress.cancel = downloadPromise.cancel; }, function () { var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {mime: mimeType}); downloadPromise.then(function (url) { delete historyVideo.progress; if (popup) { window.open(url, '_blank'); return } FileManager.download(url, mimeType, fileName); }, function (e) { console.log('video download failed', e); historyVideo.progress.enabled = false; }, updateDownloadProgress); historyVideo.progress.cancel = downloadPromise.cancel; }); }; $rootScope.openVideo = openVideo; $rootScope.downloadVideo = downloadVideo; return { saveVideo: saveVideo, wrapForHistory: wrapForHistory, wrapForFull: wrapForFull, openVideo: openVideo } }) .service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, FileManager) { var docs = {}, docsForHistory = {}, windowW = $(window).width(), windowH = $(window).height(); function saveDoc (apiDoc) { docs[apiDoc.id] = apiDoc; if (apiDoc.thumb && apiDoc.thumb._ == 'photoCachedSize') { MtpApiFileManager.saveSmallFile(apiDoc.thumb.location, apiDoc.thumb.bytes); // Memory apiDoc.thumb.size = apiDoc.thumb.bytes.length; delete apiDoc.thumb.bytes; apiDoc.thumb._ = 'photoSize'; } }; function wrapForHistory (docID) { if (docsForHistory[docID] !== undefined) { return docsForHistory[docID]; } var doc = angular.copy(docs[docID]), isGif = doc.mime_type == 'image/gif', width = isGif ? Math.min(windowW - 80, 260) : 100, height = isGif ? Math.min(windowH - 100, 260) : 100, thumbPhotoSize = doc.thumb, thumb = { width: width, height: height }; if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { if ((thumbPhotoSize.w / thumbPhotoSize.h) > (width / height)) { thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w); } else { thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h); if (thumb.width > width) { thumb.height = parseInt(thumb.height * width / thumb.width); thumb.width = width; } } thumb.location = thumbPhotoSize.location; thumb.size = thumbPhotoSize.size; } else { thumb = false; } doc.thumb = thumb; doc.canDownload = !(window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry); doc.withPreview = doc.canDownload && doc.thumb && doc.mime_type.match(/^(image\/|application\/pdf)/) ? 1 : 0; if (doc.withPreview && isGif) { doc.isSpecial = 'gif'; } return docsForHistory[docID] = doc; } function downloadDoc (docID, action) { var doc = docs[docID], historyDoc = docsForHistory[docID] || doc || {}, inputFileLocation = { _: 'inputDocumentFileLocation', id: docID, access_hash: doc.access_hash }; historyDoc.progress = {enabled: true, percent: 1, total: doc.size}; function updateDownloadProgress (progress) { console.log('dl progress', progress); historyDoc.progress.done = progress.done; historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update'); } var ext = (doc.file_name.split('.', 2) || [])[1] || ''; FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) { var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, { mime: doc.mime_type, toFileEntry: writableFileEntry }); downloadPromise.then(function (url) { delete historyDoc.progress; console.log('file save done'); }, function (e) { console.log('document download failed', e); historyDoc.progress.enabled = false; }, updateDownloadProgress); historyDoc.progress.cancel = downloadPromise.cancel; }, function () { var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {mime: doc.mime_type}); downloadPromise.then(function (url) { delete historyDoc.progress; historyDoc.url = url; switch (action) { case 1: window.open(url, '_blank'); break; default: FileManager.download(url, doc.mime_type, doc.file_name); } }, function (e) { console.log('document download failed', e); historyDoc.progress.enabled = false; }, updateDownloadProgress); historyDoc.progress.cancel = downloadPromise.cancel; }); } $rootScope.downloadDoc = downloadDoc; return { saveDoc: saveDoc, wrapForHistory: wrapForHistory, downloadDoc: downloadDoc } }) .service('AppAudioManager', function ($rootScope, $modal, $window, $timeout, $sce, MtpApiFileManager) { var audios = {}; var audiosForHistory = {}; function saveAudio (apiAudio) { audios[apiAudio.id] = apiAudio; }; function wrapForHistory (audioID) { if (audiosForHistory[audioID] !== undefined) { return audiosForHistory[audioID]; } var audio = angular.copy(audios[audioID]); return audiosForHistory[audioID] = audio; } function openAudio (audioID, accessHash) { var audio = audios[audioID], historyAudio = audiosForHistory[audioID] || audio || {}, inputFileLocation = { _: 'inputAudioFileLocation', id: audioID, access_hash: accessHash || audio.access_hash }; historyAudio.progress = {enabled: true, percent: 1, total: audio.size}; function updateDownloadProgress (progress) { console.log('dl progress', progress); historyAudio.progress.done = progress.done; historyAudio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update'); } var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, {mime: 'audio/ogg'}); downloadPromise.then(function (url) { delete historyAudio.progress; historyAudio.url = $sce.trustAsResourceUrl(url); historyAudio.autoplay = true; $timeout(function () { console.log('disable autoplay'); delete historyAudio.autoplay; $rootScope.$broadcast('history_update'); }, 1000); }, function (e) { console.log('audio download failed', e); historyAudio.progress.enabled = false; }, updateDownloadProgress); historyAudio.progress.cancel = downloadPromise.cancel; } $rootScope.openAudio = openAudio; return { saveAudio: saveAudio, wrapForHistory: wrapForHistory, openAudio: openAudio } }) .service('ExternalResourcesManager', function ($q, $http) { var urlPromises = {}; function downloadImage (url) { if (urlPromises[url] !== undefined) { return urlPromises[url]; } return urlPromises[url] = $http.get(url, {responseType: 'blob', transformRequest: null}) .then(function (response) { window.URL = window.URL || window.webkitURL; return window.URL.createObjectURL(response.data); }); } return { downloadImage: downloadImage } }) .service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) { var isSynchronizing = true, getDifferencePending = false, curState = {}, pendingUpdates = {}; function popPendingUpdate () { var nextSeq = curState.seq + 1, updateMessage = pendingUpdates[nextSeq]; if (updateMessage) { console.log(dT(), 'pop pending update', nextSeq, updateMessage); if (processUpdateMessage(updateMessage)) { delete pendingUpdates[nextSeq]; } } } function forceGetDifference () { if (!isSynchronizing) { getDifference(); } } function processUpdateMessage (updateMessage) { if (updateMessage.seq) { if (!saveSeq(updateMessage.seq, updateMessage.seq_start)) { pendingUpdates[updateMessage.seq_start || updateMessage.seq] = updateMessage; return false; } if (updateMessage.date) { curState.date = updateMessage.date; } } switch (updateMessage._) { case 'updatesTooLong': forceGetDifference(); break; case 'updateShort': saveUpdate(updateMessage.update); break; case 'updateShortMessage': if (!AppUsersManager.hasUser(updateMessage.from_id)) { console.log('User not found', updateMessage.from_id, 'getDiff'); forceGetDifference(); break; } saveUpdate({ _: 'updateNewMessage', message: { _: 'message', id: updateMessage.id, from_id: updateMessage.from_id, to_id: AppPeersManager.getOutputPeer(MtpApiManager.getUserID()), out: false, unread: true, date: updateMessage.date, message: updateMessage.message, media: {_: 'messageMediaEmpty'} }, pts: updateMessage.pts }); break; case 'updateShortChatMessage': if (!AppUsersManager.hasUser(updateMessage.from_id) || !AppChatsManager.hasChat(updateMessage.chat_id)) { console.log('User or chat not found', updateMessage.from_id, updateMessage.chat_id, 'getDiff'); forceGetDifference(); break; } saveUpdate({ _: 'updateNewMessage', message: { _: 'message', id: updateMessage.id, from_id: updateMessage.from_id, to_id: AppPeersManager.getOutputPeer(-updateMessage.chat_id), out: false, unread: true, date: updateMessage.date, message: updateMessage.message, media: {_: 'messageMediaEmpty'} }, pts: updateMessage.pts }); break; case 'updatesCombined': case 'updates': AppUsersManager.saveApiUsers(updateMessage.users); AppChatsManager.saveApiChats(updateMessage.chats); var i, update, message; for (var i = 0; i < updateMessage.updates.length; i++) { update = updateMessage.updates[i]; switch (update._) { case 'updateNewMessage': message = update.message; if (message.from_id && !AppUsersManager.hasUser(message.from_id)) { console.log('User not found', message.from_id, 'getDiff'); forceGetDifference(); return false; } if (message.to_id.chat_id && !AppChatsManager.hasChat(message.to_id.chat_id)) { console.log('Chat not found', message.to_id.chat_id, 'getDiff'); forceGetDifference(); return false; } break; } } angular.forEach(updateMessage.updates, function (update) { saveUpdate(update); }); break; default: console.warn(dT(), 'Unknown update message', updateMessage); } popPendingUpdate(); if (getDifferencePending && curState.seq >= getDifferencePending.seqAwaiting) { console.log(dT(), 'cancel pending getDiff', getDifferencePending.seqAwaiting); clearTimeout(getDifferencePending.timeout); getDifferencePending = false; } return true; } function getDifference () { isSynchronizing = true; if (getDifferencePending) { clearTimeout(getDifferencePending.timeout); getDifferencePending = false; } MtpApiManager.invokeApi('updates.getDifference', {pts: curState.pts, date: curState.date, qts: 0}).then(function (differenceResult) { if (differenceResult._ == 'updates.differenceEmpty') { console.log(dT(), 'apply empty diff', differenceResult.seq); curState.date = differenceResult.date; curState.seq = differenceResult.seq; isSynchronizing = false; popPendingUpdate(); return false; } AppUsersManager.saveApiUsers(differenceResult.users); AppChatsManager.saveApiChats(differenceResult.chats); // Should be first because of updateMessageID angular.forEach(differenceResult.other_updates, function(update){ saveUpdate(update); }); angular.forEach(differenceResult.new_messages, function (apiMessage) { saveUpdate({ _: 'updateNewMessage', message: apiMessage, pts: curState.pts }); }); var nextState = differenceResult.intermediate_state || differenceResult.state; curState.seq = nextState.seq; curState.pts = nextState.pts; curState.date = nextState.date; console.log(dT(), 'apply diff', curState.seq, curState.pts); if (differenceResult._ == 'updates.differenceSlice') { getDifference(true); } else { isSynchronizing = false; } }); } function saveUpdate (update) { if (update.pts) { curState.pts = update.pts; } $rootScope.$broadcast('apiUpdate', update); } function saveSeq (seq, seqStart) { seqStart = seqStart || seq; if (!seqStart) { return true; } if (isSynchronizing) { console.log(dT(), 'Seq decline', seqStart); return false; } if (seqStart != curState.seq + 1) { if (seqStart > curState.seq) { console.warn(dT(), 'Seq hole', seqStart, getDifferencePending && getDifferencePending.seqAwaiting); if (!getDifferencePending) { getDifferencePending = { seqAwaiting: seqStart, timeout: setTimeout(function () { getDifference(); }, 5000) }; } } return false; } else { // console.log(dT(), 'Seq apply', seqStart); } curState.seq = seq; return true; } function attach () { MtpNetworkerFactory.setUpdatesProcessor(processUpdateMessage); MtpApiManager.invokeApi('updates.getState').then(function (stateResult) { curState.seq = stateResult.seq; curState.pts = stateResult.pts; curState.date = stateResult.date; isSynchronizing = false; }) } return { processUpdateMessage: processUpdateMessage, attach: attach } }) .service('RichTextProcessor', function ($sce, $sanitize) { var emojiUtf = [], emojiMap = {}, emojiData = Config.Emoji, emojiIconSize = 18, emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1, emojiCode; for (emojiCode in emojiData) { emojiUtf.push(emojiData[emojiCode][0]); emojiMap[emojiData[emojiCode][0]] = emojiCode; } var regexAlphaChars = "a-z" + "\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u00ff" + // Latin-1 "\\u0100-\\u024f" + // Latin Extended A and B "\\u0253\\u0254\\u0256\\u0257\\u0259\\u025b\\u0263\\u0268\\u026f\\u0272\\u0289\\u028b" + // IPA Extensions "\\u02bb" + // Hawaiian "\\u0300-\\u036f" + // Combining diacritics "\\u1e00-\\u1eff" + // Latin Extended Additional (mostly for Vietnamese) "\\u0400-\\u04ff\\u0500-\\u0527" + // Cyrillic "\\u2de0-\\u2dff\\ua640-\\ua69f" + // Cyrillic Extended A/B "\\u0591-\\u05bf\\u05c1-\\u05c2\\u05c4-\\u05c5\\u05c7" + "\\u05d0-\\u05ea\\u05f0-\\u05f4" + // Hebrew "\\ufb1d-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40-\\ufb41" + "\\ufb43-\\ufb44\\ufb46-\\ufb4f" + // Hebrew Pres. Forms "\\u0610-\\u061a\\u0620-\\u065f\\u066e-\\u06d3\\u06d5-\\u06dc" + "\\u06de-\\u06e8\\u06ea-\\u06ef\\u06fa-\\u06fc\\u06ff" + // Arabic "\\u0750-\\u077f\\u08a0\\u08a2-\\u08ac\\u08e4-\\u08fe" + // Arabic Supplement and Extended A "\\ufb50-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb" + // Pres. Forms A "\\ufe70-\\ufe74\\ufe76-\\ufefc" + // Pres. Forms B "\\u200c" + // Zero-Width Non-Joiner "\\u0e01-\\u0e3a\\u0e40-\\u0e4e" + // Thai "\\u1100-\\u11ff\\u3130-\\u3185\\uA960-\\uA97F\\uAC00-\\uD7AF\\uD7B0-\\uD7FF" + // Hangul (Korean) "\\u3003\\u3005\\u303b" + // Kanji/Han iteration marks "\\uff21-\\uff3a\\uff41-\\uff5a" + // full width Alphabet "\\uff66-\\uff9f" + // half width Katakana "\\uffa1-\\uffdc"; // half width Hangul (Korean) var regexAlphaNumericChars = "0-9\.\_" + regexAlphaChars; var regExp = new RegExp('((?:(ftp|https?)://|(?:mailto:)?([A-Za-z0-9._%+-]+@))(\\S*\\.\\S*[^\\s.;,(){}<>"\']))|(\\n)|(' + emojiUtf.join('|') + ')|(^|\\s)(#[' + regexAlphaNumericChars + ']{3,20})', 'i'); var youtubeRegex = /(?:https?:\/\/)?(?:www\.)?youtu(?:|.be|be.com|.b)(?:\/v\/|\/watch\\?v=|e\/|\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/; return { wrapRichText: wrapRichText, wrapPlainText: wrapPlainText }; function encodeEntities(value) { return value. replace(/&/g, '&'). replace(/([^\#-~| |!])/g, function (value) { // non-alphanumeric return '&#' + value.charCodeAt(0) + ';'; }). replace(//g, '>'); } function getEmojiSpritesheetCoords(emojiCode) { var i, row, column, totalColumns; for (var cat = 0; cat < Config.EmojiCategories.length; cat++) { totalColumns = Config.EmojiCategorySpritesheetDimens[cat][1]; i = Config.EmojiCategories[cat].indexOf(emojiCode); if (i > -1) { row = Math.floor(i / totalColumns); column = (i % totalColumns); return { category: cat, row: row, column: column }; } } console.error('emoji not found in spritesheet', emojiCode); return null; } function wrapRichText(text, options) { if (!text || !text.length) { return ''; } options = options || {}; text = text.replace(/\ufe0f/g, '', text); var match, raw = text, html = [], url, emojiFound = false, emojiTitle, emojiCoords; while ((match = raw.match(regExp))) { // console.log(2, match); html.push(encodeEntities(raw.substr(0, match.index))); if (match[1]) { // URL if (!options.noLinks) { if (match[3]) { html.push( '', encodeEntities(match[3] + match[4]), '' ); } else { html.push( '', encodeEntities(match[2] + '://' + match[4]), '' ); } } else { html.push(encodeEntities(match[0])); } } else if (match[5]) { // New line if (!options.noLinebreaks) { html.push('
'); } else { html.push(' '); } } else if (match[6]) { if ((emojiCode = emojiMap[match[6]]) && (emojiCoords = getEmojiSpritesheetCoords(emojiCode))) { emojiTitle = encodeEntities(emojiData[emojiCode][1][0]); emojiFound = true; html.push( '', ':', emojiTitle, ':' ); } else { html.push(encodeEntities(match[6])); } } else if (match[8]) { if (!options.noLinks) { html.push( match[7], '', encodeEntities(match[8]), '' ); } else { html.push( match[7], encodeEntities(match[8]) ); } } raw = raw.substr(match.index + match[0].length); } html.push(encodeEntities(raw)); text = $sanitize(html.join('')); // console.log(3, text, html); if (emojiFound) { text = text.replace(//g, ''); } // console.log(4, text, html); if (!options.noLinks) { var youtubeMatches = text.match(youtubeRegex), videoID = youtubeMatches && youtubeMatches[1]; if (videoID) { var tag = Config.Modes.chrome_packed ? 'webview' : 'iframe'; text = text + '
<' + tag + ' type="text/html" frameborder="0" ' + 'src="http://www.youtube.com/embed/' + videoID + '?autoplay=0&controls=2">
' } } return $sce.trustAs('html', text); } function wrapPlainText (text, options) { if (emojiSupported) { return text; } if (!text || !text.length) { return ''; } options = options || {}; text = text.replace(/\ufe0f/g, '', text); var match, raw = text, text = [], emojiTitle; while ((match = raw.match(regExp))) { text.push(raw.substr(0, match.index)); if (match[6]) { if ((emojiCode = emojiMap[match[6]]) && (emojiTitle = emojiData[emojiCode][1][0])) { text.push(':' + emojiTitle + ':'); } else { text.push(match[0]); } } else { text.push(match[0]); } raw = raw.substr(match.index + match[0].length); } text.push(raw); return text.join(''); } }) .service('IdleManager', function ($rootScope, $window, $timeout) { $rootScope.idle = {isIDLE: false}; var toPromise, started = false; return { start: start }; function start () { if (!started) { started = true; $($window).on('blur focus keydown mousedown touchstart', onEvent); setTimeout(function () { onEvent({type: 'blur'}); }, 0); } } function onEvent (e) { // console.log('event', e.type); if (e.type == 'mousemove') { $($window).off('mousemove', onEvent); } var isIDLE = e.type == 'blur' || e.type == 'timeout' ? true : false; $timeout.cancel(toPromise); if (!isIDLE) { // console.log('update timeout'); toPromise = $timeout(function () { onEvent({type: 'timeout'}); }, 30000); } if ($rootScope.idle.isIDLE == isIDLE) { return; } // console.log('IDLE changed', isIDLE); $rootScope.$apply(function () { $rootScope.idle.isIDLE = isIDLE; }); if (isIDLE && e.type == 'timeout') { $($window).on('mousemove', onEvent); } } }) .service('StatusManager', function ($timeout, $rootScope, MtpApiManager, IdleManager) { var toPromise, lastOnlineUpdated = 0, started = false; return { start: start }; function start() { if (!started) { started = true; $rootScope.$watch('idle.isIDLE', checkIDLE); $rootScope.$watch('offline', checkIDLE); } } function sendUpdateStatusReq(offline) { var date = tsNow(); if (offline && !lastOnlineUpdated || !offline && (date - lastOnlineUpdated) < 50000 || $rootScope.offline) { return; } lastOnlineUpdated = offline ? 0 : date; return MtpApiManager.invokeApi('account.updateStatus', { offline: offline }, {noErrorBox: true}); } function checkIDLE() { toPromise && $timeout.cancel(toPromise); if ($rootScope.idle.isIDLE) { toPromise = $timeout(function () { sendUpdateStatusReq(true); }, 5000); } else { sendUpdateStatusReq(false); toPromise = $timeout(checkIDLE, 60000); } } }) .service('NotificationsManager', function ($rootScope, $window, $timeout, $interval, $q, MtpApiManager, AppPeersManager, IdleManager, Storage) { navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate; var notificationsUiSupport = ('Notification' in window) || ('mozNotification' in navigator); var notificationsShown = {}; var notificationIndex = 0; var notificationsCount = 0; var vibrateSupport = !!navigator.vibrate; var peerSettings = {}; var faviconBackupEl = $('link[rel="icon"]'), faviconNewEl = $(''); var titleBackup = document.title, titlePromise; $rootScope.$watch('idle.isIDLE', function (newVal) { // console.log('isIDLE watch', newVal); $interval.cancel(titlePromise); if (!newVal) { document.title = titleBackup; $('link[rel="icon"]').replaceWith(faviconBackupEl); notificationsClear(); } else { titleBackup = document.title; titlePromise = $interval(function () { var time = tsNow(); if (!notificationsCount || time % 2000 > 1000) { document.title = titleBackup; var curFav = $('link[rel="icon"]'); if (curFav.attr('href').indexOf('favicon_unread') != -1) { curFav.replaceWith(faviconBackupEl); } } else { document.title = notificationsCount > 1 ? (notificationsCount + ' notifications') : '1 notification'; $('link[rel="icon"]').replaceWith(faviconNewEl); } }, 1000); } }); $rootScope.$on('apiUpdate', function (e, update) { // console.log('on apiUpdate', update); switch (update._) { case 'updateNotifySettings': if (update.peer._ == 'notifyPeer') { var peerID = AppPeersManager.getPeerID(update.peer.peer); savePeerSettings(peerID, update.notify_settings); } break; } }); var registeredDevice = false; if (window.navigator.mozSetMessageHandler) { window.navigator.mozSetMessageHandler('push', function(e) { console.log(dT(), 'received push', e); $rootScope.$broadcast('push_received'); }); window.navigator.mozSetMessageHandler('push-register', function(e) { console.log(dT(), 'received push', e); registeredDevice = false; registerDevice(); }); } return { start: start, notify: notify, cancel: notificationCancel, clear: notificationsClear, getPeerSettings: getPeerSettings, getPeerMuted: getPeerMuted, savePeerSettings: savePeerSettings, updatePeerSettings: updatePeerSettings, getVibrateSupport: getVibrateSupport, testSound: playSound }; function getPeerSettings (peerID) { if (peerSettings[peerID] !== undefined) { return peerSettings[peerID]; } return peerSettings[peerID] = MtpApiManager.invokeApi('account.getNotifySettings', { peer: { _: 'inputNotifyPeer', peer: AppPeersManager.getInputPeerByID(peerID) } }); } function savePeerSettings (peerID, settings) { // console.trace(dT(), 'peer settings', peerID, settings); peerSettings[peerID] = $q.when(settings); } function updatePeerSettings (peerID, settings) { savePeerSettings(peerID, settings); var inputSettings = angular.copy(settings); inputSettings._ = 'inputPeerNotifySettings'; return MtpApiManager.invokeApi('account.updateNotifySettings', { peer: { _: 'inputNotifyPeer', peer: AppPeersManager.getInputPeerByID(peerID) }, settings: inputSettings }); } function getPeerMuted (peerID) { return getPeerSettings(peerID).then(function (peerNotifySettings) { return peerNotifySettings._ == 'peerNotifySettings' && peerNotifySettings.mute_until * 1000 > tsNow(); }); } function start () { registerDevice(); if (!notificationsUiSupport) { return false; } if ('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') { $($window).on('click', requestPermission); } try { $($window).on('beforeunload', notificationsClear); } catch (e) {} } function requestPermission() { Notification.requestPermission(); $($window).off('click', requestPermission); } function notify (data) { console.log('notify', $rootScope.idle.isIDLE, notificationsUiSupport); // FFOS Notification blob src bug workaround if (Config.Navigator.ffos) { data.image = 'https://raw.githubusercontent.com/zhukov/webogram/master/app/img/icons/icon60.png'; } notificationsCount++; if (!notificationsUiSupport || 'Notification' in window && Notification.permission !== 'granted') { return false; } Storage.get('notify_nosound', 'notify_volume').then(function (settings) { if (!settings[0] && settings[1] === false || settings[1] > 0) { playSound(settings[1] || 0.5); } }) Storage.get('notify_nodesktop', 'notify_novibrate').then(function (settings) { if (settings[0]) { if (vibrateSupport && !settings[1]) { navigator.vibrate([200, 100, 200]); return; } return; } var idx = ++notificationIndex, key = data.key || 'k' + idx, notification; if ('Notification' in window) { notification = new Notification(data.title, { icon: data.image || '', body: data.message || '', tag: data.tag || '' }); } else if ('mozNotification' in navigator) { notification = navigator.mozNotification.createNotification(data.title, data.message || '', data.image || ''); } else { return; } notification.onclick = function () { notification.close(); if (window.navigator.mozApps && document.hidden) { // Get app instance and launch it to bring app to foreground window.navigator.mozApps.getSelf().onsuccess = function() { this.result.launch(); }; } else { if (window.chrome && chrome.app && chrome.app.window) { chrome.app.window.current().focus(); } window.focus(); } notificationsClear(); if (data.onclick) { data.onclick(); } }; notification.onclose = function () { delete notificationsShown[key]; notificationsClear(); }; if (notification.show) { notification.show(); } notificationsShown[key] = notification; }); }; function playSound (volume) { var filename = 'img/sound_a.wav'; var obj = $('#notify_sound').html(''); obj.find('audio')[0].volume = volume; } function notificationCancel (key) { var notification = notificationsShown[key]; if (notification) { if (notificationsCount > 0) { notificationsCount--; } try { if (notification.close) { notification.close(); } } catch (e) {} delete notificationsCount[key]; } } function notificationsClear() { angular.forEach(notificationsShown, function (notification) { try { if (notification.close) { notification.close() } } catch (e) {} }); notificationsShown = {}; notificationsCount = 0; } var registerDevicePeriod = 1000, registerDeviceTO; function registerDevice () { if (registeredDevice) { return false; } if (navigator.push && Config.Navigator.ffos && Config.Modes.packed) { var req = navigator.push.register(); req.onsuccess = function(e) { clearTimeout(registerDeviceTO); console.log(dT(), 'Push registered', req.result); registeredDevice = req.result; MtpApiManager.invokeApi('account.registerDevice', { token_type: 4, token: registeredDevice, device_model: navigator.userAgent || 'Unknown UserAgent', system_version: navigator.platform || 'Unknown Platform', app_version: Config.App.version, app_sandbox: false, lang_code: navigator.language || 'en' }); } req.onerror = function(e) { console.error('Push register error', e, e.toString()); registerDeviceTO = setTimeout(registerDevice, registerDevicePeriod); registerDevicePeriod = Math.min(30000, registerDevicePeriod * 1.5); } } } function unregisterDevice () { if (!registeredDevice) { return false; } MtpApiManager.invokeApi('account.unregisterDevice', { token_type: 4, token: registeredDevice }).then(function () { registeredDevice = false; }) } function getVibrateSupport () { return vibrateSupport; } }) .service('ErrorService', function ($rootScope, $modal, $window) { var shownBoxes = 0; function show (params, options) { if (shownBoxes >= 2) { console.log('Skip error box, too many open', shownBoxes, params, options); return false; } options = options || {}; var scope = $rootScope.$new(); angular.extend(scope, params); shownBoxes++; var modal = $modal.open({ templateUrl: templateUrl('error_modal'), scope: scope, windowClass: options.windowClass || 'error_modal_window' }); modal.result['finally'](function () { shownBoxes--; }); return modal; } function alert (title, description) { return show ({ title: title, description: description }); }; function confirm (params, options) { options = options || {}; var scope = $rootScope.$new(); angular.extend(scope, params); var modal = $modal.open({ templateUrl: templateUrl('confirm_modal'), scope: scope, windowClass: options.windowClass || 'confirm_modal_window' }); return modal.result; }; $window.safeConfirm = function (params, callback) { if (typeof params === 'string') { params = {message: params}; } confirm(params).then(function (result) { callback(result || true) }, function () { callback(false) }); }; return { show: show, alert: alert, confirm: confirm } }) .service('PeersSelectService', function ($rootScope, $modal) { function selectPeer (options) { var scope = $rootScope.$new(); if (options) { angular.extend(scope, options); } var className = 'peer_select_window page_modal'; if (Config.Mobile) { className += ' mobile_modal'; } return $modal.open({ templateUrl: templateUrl('peer_select'), controller: 'PeerSelectController', scope: scope, windowClass: className }).result; } return { selectPeer: selectPeer } }) .service('ContactsSelectService', function ($rootScope, $modal) { function select (multiSelect, options) { options = options || {}; var scope = $rootScope.$new(); scope.multiSelect = multiSelect; angular.extend(scope, options); if (!scope.action && multiSelect) { scope.action = 'select'; } var className = 'contacts_modal_window page_modal'; if (Config.Mobile) { className += ' mobile_modal'; } return $modal.open({ templateUrl: templateUrl('contacts_modal'), controller: 'ContactsModalController', scope: scope, windowClass: className }).result; } return { selectContacts: function (options) { return select (true, options); }, selectContact: function (options) { return select (false, options); }, } }) .service('ChangelogNotifyService', function (Storage, $rootScope, $http, $modal) { function versionCompare (ver1, ver2) { if (typeof ver1 !== 'string') { ver1 = ''; } if (typeof ver2 !== 'string') { ver2 = ''; } ver1 = ver1.replace(/^\s+|\s+$/g, '').split('.'); ver2 = ver2.replace(/^\s+|\s+$/g, '').split('.'); var a = Math.max(ver1.length, ver2.length), i; for (i = 0; i < a; i++) { if (ver1[i] == ver2[i]) { continue; } if (ver1[i] > ver2[i]) { return 1; } else { return -1; } } return 0; } function checkUpdate () { Storage.get('last_version').then(function (lastVersion) { if (lastVersion != Config.App.version) { if (lastVersion) { showChangelog(lastVersion); } Storage.set({last_version: Config.App.version}); } }) } function showChangelog (lastVersion) { var $scope = $rootScope.$new(); $scope.lastVersion = lastVersion; $scope.canShowVersion = function (curVersion) { if ($scope.lastVersion === false || $scope.lastVersion === undefined) { return true; } return versionCompare(curVersion, lastVersion) >= 0; }; $modal.open({ templateUrl: templateUrl('changelog_modal'), scope: $scope, windowClass: 'changelog_modal_window page_modal' }); } return { checkUpdate: checkUpdate, showChangelog: showChangelog } })