/*! * Webogram v0.4.7 - 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', ['myApp.i18n', 'izhukov.utils']) .service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, qSync, MtpApiFileManager, MtpApiManager, RichTextProcessor, ErrorService, Storage, _) { var users = {}, usernames = {}, cachedPhotoLocations = {}, contactsFillPromise, contactsList, contactsIndex = SearchIndexManager.createIndex(), myID, serverTimeOffset = 0; Storage.get('server_time_offset').then(function (to) { if (to) { serverTimeOffset = to; } }); MtpApiManager.getUserID().then(function (id) { myID = id; }); 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 || '') + ' ' + (user.username || ''); } 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 userNameClean (username) { return username && username.toLowerCase() || ''; } function resolveUsername (username) { var searchUserName = userNameClean(username); var foundUserID = usernames[searchUserName]; if (foundUserID && userNameClean(users[foundUserID].username) == searchUserName) { return qSync.when(foundUserID); } return MtpApiManager.invokeApi('contacts.resolveUsername', {username: username}).then(function (resolveResult) { saveApiUser(resolveResult); return resolveResult.id; }); } function saveApiUsers (apiUsers) { angular.forEach(apiUsers, saveApiUser); }; function saveApiUser (apiUser, noReplace) { if (!angular.isObject(apiUser) || noReplace && angular.isObject(users[apiUser.id]) && users[apiUser.id].first_name) { return; } var userID = apiUser.id; if (apiUser.phone) { apiUser.rPhone = $filter('phoneNumber')(apiUser.phone); } apiUser.num = (Math.abs(userID) % 8) + 1; if (apiUser.first_name) { apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true}); apiUser.rFullName = apiUser.last_name ? RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}) : apiUser.rFirstName; } else { apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || _('user_first_name_deleted'); apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || _('user_name_deleted'); } if (apiUser.username) { usernames[userNameClean(apiUser.username)] = userID; } apiUser.sortName = SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || '')); apiUser.pFlags = { self: (apiUser.flags & (1 << 10)) > 0, contact: (apiUser.flags & (1 << 11)) > 0, mutual: (apiUser.flags & (1 << 12)) > 0, deleted: (apiUser.flags & (1 << 13)) > 0, bot: (apiUser.flags & (1 << 14)) > 0, botNoPrivacy: (apiUser.flags & (1 << 15)) > 0, botNoGroups: (apiUser.flags & (1 << 16)) > 0 }; var nameWords = apiUser.sortName.split(' '); var firstWord = nameWords.shift(); var lastWord = nameWords.pop(); apiUser.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1)); if (apiUser.pFlags.bot) { apiUser.sortStatus = -1; } else { apiUser.sortStatus = getUserStatusForSort(apiUser.status); } var result = users[userID]; if (result === undefined) { result = users[userID] = apiUser; } else { safeReplaceObject(result, apiUser); } $rootScope.$broadcast('user_update', userID); if (cachedPhotoLocations[userID] !== undefined) { safeReplaceObject(cachedPhotoLocations[userID], apiUser && apiUser.photo && apiUser.photo.photo_small || {empty: true}); } }; function getUserStatusForSort(status) { if (status) { var expires = status.expires || status.was_online; if (expires) { return expires; } var timeNow = tsNow(true) + serverTimeOffset; switch (status._) { case 'userStatusRecently': return tsNow(true) + serverTimeOffset - 86400 * 3; case 'userStatusLastWeek': return tsNow(true) + serverTimeOffset - 86400 * 7; case 'userStatusLastMonth': return tsNow(true) + serverTimeOffset - 86400 * 30; } } return 0; } function getUser (id) { if (angular.isObject(id)) { return id; } return users[id] || {id: id, deleted: true, num: 1}; } function getSelf() { return getUser(myID); } function isBot(id) { return users[id] && users[id].pFlags.bot; } 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.pFlags.self) { return {_: 'inputUserSelf'}; } return { _: 'inputUser', 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 = user.status.wasStatus || {_: 'userStatusOffline', was_online: user.status.expires}; delete user.status.wasStatus; $rootScope.$broadcast('user_update', user.id); } }); } function forceUserOnline (id) { if (isBot(id)) { return; } var user = getUser(id); if (user && user.status && user.status._ != 'userStatusOnline' && user.status._ != 'userStatusEmpty') { var wasStatus; if (user.status._ != 'userStatusOffline') { delete user.status.wasStatus; wasStatus != angular.copy(user.status); } user.status = { _: 'userStatusOnline', expires: tsNow(true) + serverTimeOffset + 60, wasStatus: wasStatus }; user.sortStatus = getUserStatusForSort(user.status); $rootScope.$broadcast('user_update', id); } } function wrapForFull (id) { var user = getUser(id); return user; } function openUser (userID, override) { var scope = $rootScope.$new(); scope.userID = userID; scope.override = override || {}; var modalInstance = $modal.open({ templateUrl: templateUrl('user_modal'), controller: 'UserModalController', scope: scope, windowClass: 'user_modal_window mobile_modal', backdrop: 'single' }); }; 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 || false; }); }; 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(getUserInput(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(parseInt(userID)), curIsContact = curPos != -1; if (isContact != curIsContact) { if (isContact) { contactsList.push(userID); SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex); } else { contactsList.splice(curPos, 1); } $rootScope.$broadcast('contacts_update', userID); } } } function openImportContact () { return $modal.open({ templateUrl: templateUrl('import_contact_modal'), controller: 'ImportContactModalController', windowClass: 'md_simple_modal_window mobile_modal' }).result.then(function (foundUserID) { if (!foundUserID) { return $q.reject(); } return foundUserID; }); }; function setUserStatus (userID, offline) { if (isBot(userID)) { return; } var user = users[userID]; if (user) { var status = offline ? { _: 'userStatusOffline', was_online: tsNow(true) + serverTimeOffset } : { _: 'userStatusOnline', expires: tsNow(true) + serverTimeOffset + 500 }; user.status = status; user.sortStatus = getUserStatusForSort(user.status); $rootScope.$broadcast('user_update', userID); } } $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 = getUserStatusForSort(user.status); $rootScope.$broadcast('user_update', userID); } break; case 'updateUserPhoto': var userID = update.user_id; if (users[userID]) { forceUserOnline(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._ == 'contactLinkContact'); break; } }); setInterval(updateUsersStatuses, 60000); return { getContacts: getContacts, saveApiUsers: saveApiUsers, saveApiUser: saveApiUser, getUser: getUser, getSelf: getSelf, getUserInput: getUserInput, setUserStatus: setUserStatus, forceUserOnline: forceUserOnline, getUserPhoto: getUserPhoto, getUserString: getUserString, getUserSearchText: getUserSearchText, hasUser: hasUser, isBot: isBot, importContact: importContact, importContacts: importContacts, deleteContacts: deleteContacts, wrapForFull: wrapForFull, openUser: openUser, resolveUsername: resolveUsername, 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 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 ($q, $rootScope, $modal, _, MtpApiFileManager, MtpApiManager, AppUsersManager, AppPhotosManager, RichTextProcessor) { var chats = {}, chatsFull = {}, chatFullPromises = {}, 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}) || _('chat_title_deleted'); var titleWords = SearchIndexManager.cleanSearchText(apiChat.title || '').split(' '); var firstWord = titleWords.shift(); var lastWord = titleWords.pop(); apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1)); apiChat.num = (Math.abs(apiChat.id >> 1) % (Config.Mobile ? 4 : 8)) + 1; if (chats[apiChat.id] === undefined) { chats[apiChat.id] = apiChat; } else { safeReplaceObject(chats[apiChat.id], apiChat); $rootScope.$broadcast('chat_update', apiChat.id); } 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 isChannel (id) { return (chats[id] || {})._ == 'channel'; } function getChatInput (id) { if (!id) { return {_: 'inputChatEmpty'}; } if (isChannel(id)) { return { _: 'inputChannel', channel_id: id, access_hash: getChat(id).access_hash || 0 } } return {_: 'inputChat', chat_id: id}; } function getChatFull(id) { if (chatsFull[id] !== undefined) { if (chats[id].version == chatsFull[id].participants.version || chats[id].left) { return $q.when(chatsFull[id]); } } if (chatFullPromises[id] !== undefined) { return chatFullPromises[id]; } return chatFullPromises[id] = MtpApiManager.invokeApi('messages.getFullChat', { chat_id: getChatInput(id) }).then(function (result) { saveApiChats(result.chats); AppUsersManager.saveApiUsers(result.users); if (result.full_chat && result.full_chat.chat_photo.id) { AppPhotosManager.savePhoto(result.full_chat.chat_photo); } delete chatFullPromises[id]; $rootScope.$broadcast('chat_full_update', id); return chatsFull[id] = result.full_chat; }); } function getChatInviteLink (id, force) { return getChatFull(id).then(function (chatFull) { if (!force && chatFull.exported_invite && chatFull.exported_invite._ == 'chatInviteExported') { return chatFull.exported_invite.link; } return MtpApiManager.invokeApi('messages.exportChatInvite', { chat_id: getChatInput(id) }).then(function (exportedInvite) { if (chatsFull[id] !== undefined) { chatsFull[id].exported_invite = exportedInvite; } return exportedInvite.link; }); }); } 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' + (Config.Mobile ? chat.num : Math.ceil(chat.num / 2)) + '@2x.png', location: cachedPhotoLocations[id] }; } function getChatString (id) { var chat = getChat(id); if (isChannel(id)) { return 'c' + id + '_' + chat.access_hash; } 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) { chatFull.isAdmin = (myID == chatFull.participants.admin_id); angular.forEach(chatFull.participants.participants, function(participant){ participant.user = AppUsersManager.getUser(participant.user_id); participant.canLeave = myID == participant.user_id; participant.canKick = !participant.canLeave && (chatFull.isAdmin || myID == participant.inviter_id); }); }); } chatFull.thumb = { placeholder: 'img/placeholders/GroupAvatar'+(Config.Mobile ? chat.num : Math.ceil(chat.num / 2))+'@2x.png', location: chat && chat.photo && chat.photo.photo_small, width: 72, height: 72, size: 0 }; chatFull.peerString = getChatString(id); chatFull.chat = chat; return chatFull; } function openChat (chatID, accessHash) { var scope = $rootScope.$new(); scope.chatID = chatID; var modalInstance = $modal.open({ templateUrl: templateUrl('chat_modal'), controller: 'ChatModalController', scope: scope, windowClass: 'chat_modal_window mobile_modal' }); } $rootScope.$on('apiUpdate', function (e, update) { // console.log('on apiUpdate', update); switch (update._) { case 'updateChatParticipants': var participants = update.participants; var chatFull = chatsFull[participants.id]; if (chatFull !== undefined) { chatFull.participants = update.participants; $rootScope.$broadcast('chat_full_update', chatID); } break; case 'updateChatParticipantAdd': var chatFull = chatsFull[update.chat_id]; if (chatFull !== undefined) { var participants = chatFull.participants.participants || []; for (var i = 0, length = participants.length; i < length; i++) { if (participants[i].user_id == update.user_id) { return; } } participants.push({ _: 'chatParticipant', user_id: update.user_id, inviter_id: update.inviter_id, date: tsNow(true) }); chatFull.participants.version = update.version; $rootScope.$broadcast('chat_full_update', update.chat_id); } break; case 'updateChatParticipantDelete': var chatFull = chatsFull[update.chat_id]; if (chatFull !== undefined) { var participants = chatFull.participants.participants || []; for (var i = 0, length = participants.length; i < length; i++) { if (participants[i].user_id == update.user_id) { participants.splice(i, 1); chatFull.participants.version = update.version; $rootScope.$broadcast('chat_full_update', update.chat_id); return; } } } break; } }); return { saveApiChats: saveApiChats, saveApiChat: saveApiChat, getChat: getChat, isChannel: isChannel, getChatInput: getChatInput, getChatFull: getChatFull, getChatPhoto: getChatPhoto, getChatString: getChatString, getChatInviteLink: getChatInviteLink, hasChat: hasChat, wrapForFull: wrapForFull, openChat: openChat } }) .service('AppPeersManager', function (AppUsersManager, AppChatsManager, MtpApiManager) { return { getInputPeer: function (peerString) { var firstChar = peerString.charAt(0), peerParams = peerString.substr(1).split('_'); if (firstChar == 'u') { return { _: 'inputPeerUser', user_id: peerParams[0], access_hash: peerParams[1] }; } else if (firstChar == 'c') { return { _: 'inputPeerChannel', channel_id: peerParams[0], access_hash: peerParams[1] || 0 }; } else { return { _: 'inputPeerChat', chat_id: peerParams[0] } } }, getInputPeerByID: function (peerID) { if (peerID > 0) { return { _: 'inputPeerUser', user_id: peerID, access_hash: AppUsersManager.getUser(peerID).access_hash || 0 }; } else if (peerID < 0) { var chatID = -peerID; if (!AppChatsManager.isChannel(chatID)) { return { _: 'inputPeerChat', chat_id: chatID }; } return { _: 'inputPeerChannel', channel_id: chatID, access_hash: AppChatsManager.getChat(chatID).access_hash || 0 } } }, getPeerSearchText: function (peerID) { var text; if (peerID > 0) { text = '%pu ' + AppUsersManager.getUserSearchText(peerID); } else if (peerID < 0) { var chat = AppChatsManager.getChat(-peerID); text = '%pg ' + (chat.title || ''); } return text; }, getPeerString: function (peerID) { if (peerID > 0) { return AppUsersManager.getUserString(peerID); } return AppChatsManager.getChatString(-peerID); }, getOutputPeer: function (peerID) { if (peerID > 0) { return {_: 'peerUser', user_id: peerID}; } var chatID = -peerID; if (AppChatsManager.isChannel(chatID)) { return {_: 'peerChannel', channel_id: chatID} } return {_: 'peerChat', chat_id: chatID} }, 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('AppProfileManager', function ($q, AppUsersManager, AppChatsManager, AppPhotosManager, NotificationsManager, MtpApiManager, RichTextProcessor) { var botInfos = {}; function saveBotInfo (botInfo) { var botID = botInfo && botInfo.user_id; if (!botID) { return false; } var commands = {}; angular.forEach(botInfo.commands, function (botCommand) { commands[botCommand.command] = botCommand.description; }) return botInfos[botID] = { id: botID, version: botInfo.version, shareText: botInfo.share_text, description: botInfo.description, rAbout: RichTextProcessor.wrapRichText(botInfo.share_text, {noLinebreaks: true}), commands: commands }; } function getProfile (id, override) { return MtpApiManager.invokeApi('users.getFullUser', { id: AppUsersManager.getUserInput(id) }).then(function (userFull) { if (override && override.phone_number) { userFull.user.phone = override.phone_number; if (override.first_name || override.last_name) { userFull.user.first_name = override.first_name; userFull.user.last_name = override.last_name; } AppUsersManager.saveApiUser(userFull.user); } else { AppUsersManager.saveApiUser(userFull.user, true); } AppPhotosManager.savePhoto(userFull.profile_photo, { user_id: id }); NotificationsManager.savePeerSettings(id, userFull.notify_settings); userFull.bot_info = saveBotInfo(userFull.bot_info); return userFull; }); } function getPeerBots (peerID) { var peerBots = []; if (peerID >= 0) { if (!AppUsersManager.isBot(peerID)) { return $q.when(peerBots); } return getProfile(peerID).then(function (userFull) { var botInfo = userFull.bot_info; if (botInfo && botInfo._ != 'botInfoEmpty') { peerBots.push(botInfo); } return peerBots; }); } return AppChatsManager.getChatFull(-peerID).then(function (chatFull) { angular.forEach(chatFull.bot_info, function (botInfo) { peerBots.push(saveBotInfo(botInfo)); }); return peerBots; }); } return { getProfile: getProfile, getPeerBots: getPeerBots } }) .service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, AppProfileManager, FileManager, TelegramMeWebService, ErrorService, StatusManager, _) { 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 needSingleMessages = [], fetchSingleMessagesTimeout = false; 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; } }); var maxSeenID = false; if (Config.Modes.packed) { Storage.get('max_seen_msg').then(function (maxID) { maxSeenID = maxID || 0; }); } var dateOrTimeFilter = $filter('dateOrTime'); var fwdMessagesPluralize = _.pluralize('conversation_forwarded_X_messages'); 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) { TelegramMeWebService.setAuthorized(true); AppUsersManager.saveApiUsers(dialogsResult.users); AppChatsManager.saveApiChats(dialogsResult.chats); // return { // count: 0, // dialogs: [] // }; 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; if (!maxID && curDialogStorage.dialogs.length) { incrementMaxSeenID(curDialogStorage.dialogs[0].top_message); } 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) { var historyStorage = {count: null, history: [dialog.top_message], pending: []}; historiesStorage[peerID] = historyStorage; var message = getMessage(dialog.top_message); if (mergeReplyKeyboard(historyStorage, message)) { $rootScope.$broadcast('history_reply_markup', {peerID: peerID}); } } NotificationsManager.savePeerSettings(peerID, dialog.notify_settings); if ( dialog.unread_count > 0 && maxSeenID && dialog.top_message > maxSeenID ) { var message = getMessage(dialog.top_message); var notifyPeer = message.flags & 16 ? message.from_id : peerID; if (message.unread && !message.out) { NotificationsManager.getPeerMuted(notifyPeer).then(function (muted) { if (!muted) { notifyAboutMessage(message); } }); } } }); 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 }, {noErrorBox: true}).then(function (historyResult) { AppUsersManager.saveApiUsers(historyResult.users); AppChatsManager.saveApiChats(historyResult.chats); saveMessages(historyResult.messages); var peerID = AppPeersManager.getPeerID(inputPeer); if ( peerID < 0 || !AppUsersManager.isBot(peerID) || (historyResult.messages.length == limit && limit < historyResult.count) ) { return historyResult; } return AppProfileManager.getProfile(peerID).then(function (userFull) { var description = userFull.bot_info && userFull.bot_info.description; if (description) { var messageID = tempID--; var message = { _: 'messageService', id: messageID, from_id: peerID, to_id: AppPeersManager.getOutputPeer(peerID), flags: 0, date: tsNow(true) + serverTimeOffset, action: { _: 'messageActionBotIntro', description: description } }; saveMessages([message]); historyResult.messages.push(message); if (historyResult.count) { historyResult.count++; } } 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 && historyResult.messages.length) { maxID = historyResult.messages[0].id + 1; } 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) { if (mergeReplyKeyboard(historyStorage, message)) { $rootScope.$broadcast('history_reply_markup', {peerID: AppPeersManager.getPeerID(inputPeer)}); } 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 wrapHistoryResult (peerID, result) { return $q.when(result); } function getHistory (inputPeer, maxID, limit, backLimit, prerendered) { var peerID = AppPeersManager.getPeerID(inputPeer), historyStorage = historiesStorage[peerID], offset = 0, offsetNotFound = false, unreadOffset = false, unreadSkip = false; prerendered = prerendered ? Math.min(50, prerendered) : 0; if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } 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 = 20; unreadOffset = 16; offset = unreadCount - unreadOffset; } else { limit = Math.max(10, prerendered, unreadCount + 2); unreadOffset = unreadCount; } } else if (Config.Mobile) { limit = 20; } } 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 : (prerendered || 5)); } var history = historyStorage.history.slice(offset, offset + limit); if (!maxID && historyStorage.pending.length) { history = historyStorage.pending.slice().concat(history); } return wrapHistoryResult(peerID, { count: historyStorage.count, history: history, unreadOffset: unreadOffset, unreadSkip: unreadSkip }); } if (!backLimit && !limit) { limit = prerendered || 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); }); if (!maxID && historyStorage.pending.length) { history = historyStorage.pending.slice().concat(history); } return wrapHistoryResult(peerID, { count: historyStorage.count, history: 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; } } } var history = historyStorage.history.slice(offset, offset + limit); if (!maxID && historyStorage.pending.length) { history = historyStorage.pending.slice().concat(history); } return wrapHistoryResult(peerID, { count: historyStorage.count, history: history, unreadOffset: unreadOffset, unreadSkip: unreadSkip }); }); } function getReplyKeyboard (peerID) { return (historiesStorage[peerID] || {}).reply_markup || false; } function mergeReplyKeyboard (historyStorage, message) { // console.log('merge', message.id, message.reply_markup, historyStorage.reply_markup); if (!message.reply_markup && !message.out && !message.action) { return false; } var messageReplyMarkup = message.reply_markup; var lastReplyMarkup = historyStorage.reply_markup; if (messageReplyMarkup) { if (lastReplyMarkup && lastReplyMarkup.id >= message.id) { return false; } if (messageReplyMarkup.pFlags.selective && !(message.flags & 16)) { return false; } if (historyStorage.maxOutID && message.id < historyStorage.maxOutID && messageReplyMarkup.pFlags.one_time) { messageReplyMarkup.pFlags.hidden = true; } messageReplyMarkup = angular.extend({ id: message.id }, messageReplyMarkup); if (messageReplyMarkup._ != 'replyKeyboardHide') { messageReplyMarkup.fromID = message.from_id; } historyStorage.reply_markup = messageReplyMarkup; // console.log('set', historyStorage.reply_markup); return true; } if (message.out) { if (lastReplyMarkup) { if (lastReplyMarkup.pFlags.one_time && !lastReplyMarkup.pFlags.hidden && (message.id > lastReplyMarkup.id || message.id < 0) && message.message) { lastReplyMarkup.pFlags.hidden = true; // console.log('set', historyStorage.reply_markup); return true; } } else if (!historyStorage.maxOutID || message.id > historyStorage.maxOutID) { historyStorage.maxOutID = message.id; } } if (message.action && message.action._ == 'messageActionChatDeleteUser' && (lastReplyMarkup ? message.action.user_id == lastReplyMarkup.fromID : AppUsersManager.isBot(message.action.user_id) ) ) { historyStorage.reply_markup = { _: 'replyKeyboardHide', id: message.id, flags: 0, pFlags: {} }; // console.log('set', historyStorage.reply_markup); return true; } return false; } 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 (error) { if (error.code == 400) { error.handled = true; } return $q.reject(error); }); } function getMessage (messageID) { return messagesStorage[messageID] || {deleted: true}; } function deleteMessages (messageIDs) { return MtpApiManager.invokeApi('messages.deleteMessages', { id: messageIDs }).then(function (affectedMessages) { ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateDeleteMessages', messages: messageIDs, pts: affectedMessages.pts, pts_count: affectedMessages.pts_count } }); return messageIDs; }); } function processAffectedHistory (inputPeer, affectedHistory, method) { ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updatePts', pts: affectedHistory.pts, pts_count: affectedHistory.pts_count } }); 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}); $rootScope.$broadcast('messages_read'); } })['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; } NotificationsManager.cancel('msg' + messageID); } } } NotificationsManager.soundReset(AppPeersManager.getPeerString(peerID)) return historyStorage.readPromise; } function readMessages (messageIDs) { MtpApiManager.invokeApi('messages.readMessageContents', { id: messageIDs }).then(function (affectedMessages) { ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateReadMessagesContents', messages: messageIDs, pts: affectedMessages.pts, pts_count: affectedMessages.pts_count } }); }); } 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) { apiMessage.unread = apiMessage.flags & 1 ? true : false; apiMessage.out = apiMessage.flags & 2 ? true : false; apiMessage.media_unread = apiMessage.flags & 32 ? true : false; messagesStorage[apiMessage.id] = apiMessage; apiMessage.date -= serverTimeOffset; var mediaContext = { user_id: apiMessage.from_id, date: apiMessage.date }; if (apiMessage.media) { switch (apiMessage.media._) { case 'messageMediaEmpty': delete apiMessage.media; break; case 'messageMediaPhoto': AppPhotosManager.savePhoto(apiMessage.media.photo, mediaContext); break; case 'messageMediaVideo': AppVideoManager.saveVideo(apiMessage.media.video, mediaContext); break; case 'messageMediaDocument': AppDocsManager.saveDoc(apiMessage.media.document, mediaContext); break; case 'messageMediaAudio': AppAudioManager.saveAudio(apiMessage.media.audio); break; case 'messageMediaWebPage': AppWebPagesManager.saveWebPage(apiMessage.media.webpage, apiMessage.id, mediaContext); break; } } if (apiMessage.action && apiMessage.action._ == 'messageActionChatEditPhoto') { AppPhotosManager.savePhoto(apiMessage.action.photo, mediaContext); } if (apiMessage.reply_markup) { apiMessage.reply_markup.pFlags = { resize: (apiMessage.reply_markup.flags & 1) > 0, one_time: (apiMessage.reply_markup.flags & 2) > 0, selective: (apiMessage.reply_markup.flags & 4) > 0 }; } if (apiMessage.message && apiMessage.message.length) { var myEntities = RichTextProcessor.parseEntities(apiMessage.message); var apiEntities = apiMessage.entities || []; apiMessage.totalEntities = RichTextProcessor.mergeEntities(myEntities, apiEntities, !apiMessage.pending); } }); } function sendText(peerID, text, options) { if (!angular.isString(text) || !text.length) { return; } 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), flags = 0, replyToMsgID = options.replyToMsgID, entities = [], message; text = RichTextProcessor.parseMarkdown(text, entities); if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } MtpApiManager.getUserID().then(function (fromID) { if (peerID != fromID) { flags |= 2; if (!AppUsersManager.isBot(peerID)) { flags |= 1; } } if (replyToMsgID) { flags |= 8; } message = { _: 'message', id: messageID, from_id: fromID, to_id: AppPeersManager.getOutputPeer(peerID), flags: flags, date: tsNow(true) + serverTimeOffset, message: text, random_id: randomIDS, reply_to_msg_id: replyToMsgID, entities: entities, 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; } } $rootScope.$broadcast('messages_pending'); } message.send = function () { toggleError(false); var sentRequestOptions = {}; if (pendingAfterMsgs[peerID]) { sentRequestOptions.afterMessageID = pendingAfterMsgs[peerID].messageID; } var flags = 0; if (replyToMsgID) { flags |= 1; } if (entities.length) { flags |= 8; } console.log(flags, entities); MtpApiManager.invokeApi('messages.sendMessage', { flags: flags, peer: inputPeer, message: text, random_id: randomID, reply_to_msg_id: replyToMsgID, entities: entities }, sentRequestOptions).then(function (updates) { if (updates._ == 'updateShortSentMessage') { message.flags = updates.flags; message.date = updates.date; message.id = updates.id; message.media = updates.media; message.entities = updates.entities; updates = { _: 'updates', users: [], chats: [], seq: 0, updates: [{ _: 'updateMessageID', random_id: randomIDS, id: updates.id }, { _: 'updateNewMessage', message: message, pts: updates.pts, pts_count: updates.pts_count }] }; } ApiUpdatesManager.processUpdateMessage(updates); }, 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), flags = 0, replyToMsgID = options.replyToMsgID, attachType, apiFileName, realFileName; if (!options.isMedia) { attachType = 'document'; apiFileName = 'document.' + file.type.split('/')[1]; } else if (['image/jpeg', 'image/png', 'image/bmp'].indexOf(file.type) >= 0) { attachType = 'photo'; apiFileName = 'photo.' + file.type.split('/')[1]; } else if (file.type.substr(0, 6) == 'audio/' || ['video/ogg'].indexOf(file.type) >= 0) { attachType = 'audio'; apiFileName = 'audio.' + (file.type.split('/')[1] == 'ogg' ? 'ogg' : 'mp3'); } else if (file.type.substr(0, 6) == 'video/') { attachType = 'video'; apiFileName = 'video.mp4'; } else { attachType = 'document'; apiFileName = 'document.' + file.type.split('/')[1]; } if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } MtpApiManager.getUserID().then(function (fromID) { if (peerID != fromID) { flags |= 2; if (!AppUsersManager.isBot(peerID)) { flags |= 1; } } if (replyToMsgID) { flags |= 8; } var media = { _: 'messageMediaPending', type: attachType, file_name: file.name || apiFileName, size: file.size, progress: {percent: 1, total: file.size} }; var message = { _: 'message', id: messageID, from_id: fromID, to_id: AppPeersManager.getOutputPeer(peerID), flags: flags, date: tsNow(true) + serverTimeOffset, message: '', media: media, random_id: randomIDS, reply_to_msg_id: replyToMsgID, 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; } } $rootScope.$broadcast('messages_pending'); } var uploaded = false, uploadPromise; message.send = function () { var sendFileDeferred = $q.defer(); sendFilePromise.then(function () { if (!uploaded || message.error) { uploaded = false; uploadPromise = MtpApiFileManager.uploadFile(file); } uploadPromise.then(function (inputFile) { inputFile.name = apiFileName; 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, mime_type: file.type, attributes: [ {_: 'documentAttributeFilename', file_name: file.name} ]}; } var flags = 0; if (replyToMsgID) { flags |= 1; } MtpApiManager.invokeApi('messages.sendMedia', { flags: flags, peer: inputPeer, media: inputMedia, random_id: randomID, reply_to_msg_id: replyToMsgID }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates); }, function (error) { if (attachType == 'photo' && error.code == 400 && error.type == 'PHOTO_INVALID_DIMENSIONS') { error.handled = true; attachType = 'document'; message.send(); return; } 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, 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), replyToMsgID = options.replyToMsgID; 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 = { _: 'messageMediaPhoto', photo: AppPhotosManager.getPhoto(inputMedia.id.id) }; break; case 'inputMediaDocument': media = { _: 'messageMediaDocument', 'document': AppDocsManager.getDoc(inputMedia.id.id) }; break; } var flags = 0; if (peerID != fromID) { flags |= 2; if (!AppUsersManager.isBot(peerID)) { flags |= 1; } } var message = { _: 'message', id: messageID, from_id: fromID, to_id: AppPeersManager.getOutputPeer(peerID), flags: flags, 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; } } $rootScope.$broadcast('messages_pending'); } message.send = function () { var flags = 0; if (replyToMsgID) { flags |= 1; } MtpApiManager.invokeApi('messages.sendMedia', { flags: flags, peer: inputPeer, media: inputMedia, random_id: randomID, reply_to_msg_id: replyToMsgID }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates); }, 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(); var randomIDs = []; var i; var len = msgIDs.length; for (var i = 0; i < msgIDs.length; i++) { randomIDs.push([nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]); } return MtpApiManager.invokeApi('messages.forwardMessages', { peer: AppPeersManager.getInputPeerByID(peerID), id: msgIDs, random_id: randomIDs }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates); }); }; function startBot (botID, chatID, startParam) { if (startParam) { var randomID = bigint(nextRandomInt(0xFFFFFFFF)).shiftLeft(32).add(bigint(nextRandomInt(0xFFFFFFFF))).toString(); return MtpApiManager.invokeApi('messages.startBot', { bot: AppUsersManager.getUserInput(botID), chat_id: AppChatsManager.getChatInput(chatID), random_id: randomID, start_param: startParam }); } var peerID = chatID ? -chatID : botID; var inputPeer = AppPeersManager.getInputPeerByID(peerID); if (chatID) { return MtpApiManager.invokeApi('messages.addChatUser', { chat_id: AppChatsManager.getChatInput(chatID), user_id: AppUsersManager.getUserInput(botID) }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates); }, function (error) { if (error && error.type == 'USER_ALREADY_PARTICIPANT') { var bot = AppUsersManager.getUser(botID); sendText(-chatID, '/start@' + bot.username); error.handled = true; } }); } return sendText(botID, '/start'); } function cancelPendingMessage (randomID) { var pendingData = pendingByRandomID[randomID]; console.log('pending', randomID, pendingData); if (pendingData) { var peerID = pendingData[0], tempID = pendingData[1], historyStorage = historiesStorage[peerID], pos = historyStorage.pending.indexOf(tempID); ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateDeleteMessages', messages: [tempID] } }); if (pos != -1) { historyStorage.pending.splice(pos, 1); } 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], message, historyMessage; // console.log('pending', randomID, historyStorage.pending); var pos = historyStorage.pending.indexOf(tempID); if (pos != -1) { historyStorage.pending.splice(pos, 1); } 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; $rootScope.$broadcast('messages_pending'); } delete messagesForHistory[tempID]; delete messagesStorage[tempID]; return message; } return false; } function openChatInviteLink (hash) { return MtpApiManager.invokeApi('messages.checkChatInvite', { hash: hash }).then(function (chatInvite) { var chatTitle; if (chatInvite._ == 'chatInviteAlready') { AppChatsManager.saveApiChat(chatInvite.chat); if (!chatInvite.chat.left) { return $rootScope.$broadcast('history_focus', { peerString: AppChatsManager.getChatString(chatInvite.chat.id) }); } chatTitle = chatInvite.chat.title; } else { chatTitle = chatInvite.title; } ErrorService.confirm({ type: 'JOIN_GROUP_BY_LINK', title: chatTitle }).then(function () { return MtpApiManager.invokeApi('messages.importChatInvite', { hash: hash }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates); if (updates.updates && updates.updates.length) { for (var i = 0, len = updates.updates.length, update; i < len; i++) { update = updates.updates[i]; if (update._ == 'updateNewMessage') { $rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(update.message.to_id.chat_id) }); break; } } } }); }); }); } function getMessagePeer (message) { var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0; if (toID < 0) { return toID; } else if (message.out || message.flags & 2) { return toID; } return message.from_id; } function wrapForDialog (msgID, unreadCount) { var useCache = unreadCount != -1; if (useCache && messagesForDialogs[msgID] !== undefined) { return messagesForDialogs[msgID]; } var message = angular.copy(messagesStorage[msgID]); if (!message || !message.to_id) { return message; } if (message.chatID = message.to_id.chat_id) { message.peerID = -message.chatID; message.peerData = AppChatsManager.getChat(message.chatID); } else { message.peerID = message.out ? message.to_id.user_id : message.from_id; message.peerData = AppUsersManager.getUser(message.peerID); } message.peerString = AppPeersManager.getPeerString(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 = dateOrTimeFilter(message.date); if (useCache) { messagesForDialogs[msgID] = message; } return 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; } var fromUser = AppUsersManager.getUser(message.from_id); var fromBot = fromUser.pFlags.bot && fromUser.username || false; var withBot = (fromBot || message.to_id && ( message.to_id.chat_id || message.to_id.user_id && AppUsersManager.isBot(message.to_id.user_id) ) ); if (message.media) { if (message.media.caption && message.media.caption.length) { message.media.rCaption = RichTextProcessor.wrapRichText(message.media.caption, { noCommands: !withBot, fromBot: fromBot }); } 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 'messageMediaGeo': var mapUrl = 'https://maps.google.com/?q=' + message.media.geo['lat'] + ',' + message.media.geo['long']; message.media.mapUrl = $sce.trustAsResourceUrl(mapUrl); break; case 'messageMediaVenue': var mapUrl; if (message.media.provider == 'foursquare' && message.media.venue_id) { mapUrl = 'https://foursquare.com/v/' + encodeURIComponent(message.media.venue_id); } else { mapUrl = 'https://maps.google.com/?q=' + message.media.geo['lat'] + ',' + message.media.geo['long']; } message.media.mapUrl = $sce.trustAsResourceUrl(mapUrl); break; case 'messageMediaContact': message.media.rFullName = RichTextProcessor.wrapRichText( message.media.first_name + ' ' + (message.media.last_name || ''), {noLinks: true, noLinebreaks: true} ); break; case 'messageMediaWebPage': if (!message.media.webpage || message.media.webpage._ == 'webPageEmpty' || Config.Mobile) { delete message.media; break; } message.media.webpage = AppWebPagesManager.wrapForHistory(message.media.webpage.id); 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}) || _('chat_title_deleted'); break; case 'messageActionBotIntro': message.action.rDescription = RichTextProcessor.wrapRichText(message.action.description, { noCommands: !withBot, fromBot: fromBot }); break; } } var replyToMsgID = message.reply_to_msg_id; if (replyToMsgID) { if (messagesStorage[replyToMsgID]) { message.reply_to_msg = wrapForDialog(replyToMsgID); } else { message.reply_to_msg = {id: replyToMsgID, loading: true}; if (needSingleMessages.indexOf(replyToMsgID) == -1) { needSingleMessages.push(replyToMsgID); if (fetchSingleMessagesTimeout === false) { fetchSingleMessagesTimeout = setTimeout(fetchSingleMessages, 100); } } } } return messagesForHistory[msgID] = message; } function wrapReplyMarkup (replyMarkup) { if (!replyMarkup || replyMarkup._ == 'replyKeyboardHide') { return false; } if (replyMarkup.wrapped) { return replyMarkup; } var count = replyMarkup.rows && replyMarkup.rows.length || 0; if (count > 0 && count <= 4 && !replyMarkup.pFlags.resize) { replyMarkup.splitCount = count; } replyMarkup.wrapped = true; angular.forEach(replyMarkup.rows, function (markupRow) { angular.forEach(markupRow.buttons, function (markupButton) { markupButton.rText = RichTextProcessor.wrapRichText(markupButton.text, {noLinks: true, noLinebreaks: true}); }) }) if (nextRandomInt(1)) { replyMarkup.rows = replyMarkup.rows.slice(0, 2); } return replyMarkup; } function fetchSingleMessages () { if (fetchSingleMessagesTimeout !== false) { clearTimeout(fetchSingleMessagesTimeout); fetchSingleMessagesTimeout = false; } if (!needSingleMessages.length) { return; } var msgIDs = needSingleMessages.slice(); needSingleMessages = []; MtpApiManager.invokeApi('messages.getMessages', { id: msgIDs }).then(function (getMessagesResult) { AppUsersManager.saveApiUsers(getMessagesResult.users); AppChatsManager.saveApiChats(getMessagesResult.chats); saveMessages(getMessagesResult.messages); $rootScope.$broadcast('messages_downloaded', msgIDs); }) } function regroupWrappedHistory (history, limit) { if (!history || !history.length) { return false; } var start = 0, len = history.length, end = len, i, curDay, prevDay, curMessage, prevMessage, curGrouped, prevGrouped, wasUpdated = false, groupFwd = !Config.Mobile; 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); prevGrouped = prevMessage && prevMessage.grouped; curGrouped = curMessage.grouped; if (curDay === prevDay) { if (curMessage.needDate) { delete curMessage.needDate; wasUpdated = true; } } else if (!i || prevMessage) { if (!curMessage.needDate) { curMessage.needDate = true; wasUpdated = true; } } if (curMessage.fwd_from_id && curMessage.media && curMessage.media.document && curMessage.media.document.sticker && (curMessage.from_id != (prevMessage || {}).from_id || !(prevMessage || {}).fwd_from_id)) { delete curMessage.fwd_from_id; curMessage._ = 'message'; } 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 && !curMessage.reply_to_msg_id; if (groupFwd && 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 (groupFwd && 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 (groupFwd && prevMessage && prevMessage.grouped && prevMessage.fwd_from_id) { prevMessage.grouped += ' im_grouped_fwd_end'; } } if (!wasUpdated && prevGrouped != (prevMessage && prevMessage.grouped)) { wasUpdated = true; } prevMessage = curMessage; prevDay = curDay; } if (!wasUpdated && curGrouped != (prevMessage && prevMessage.grouped)) { wasUpdated = true; } return wasUpdated; } 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 incrementMaxSeenID (maxID) { if (maxSeenID !== false && maxID && maxID > maxSeenID) { Storage.set({ max_seen_msg: maxID }); } } function notifyAboutMessage (message, options) { options = options || {}; 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; var notifySettings = NotificationsManager.getNotifySettings(); if (message.fwd_from_id && options.fwd_count) { notificationMessage = fwdMessagesPluralize(options.fwd_count); } else if (message.message) { if (notifySettings.nopreview) { notificationMessage = _('conversation_message_sent'); } else { notificationMessage = RichTextProcessor.wrapPlainText(message.message); } } else if (message.media) { switch (message.media._) { case 'messageMediaPhoto': notificationMessage = _('conversation_media_photo_raw'); break; case 'messageMediaVideo': notificationMessage = _('conversation_media_video_raw'); break; case 'messageMediaDocument': if (message.media.document.sticker) { notificationMessage = _('conversation_media_sticker'); var stickerEmoji = message.media.document.stickerEmojiRaw; if (stickerEmoji !== undefined) { notificationMessage = RichTextProcessor.wrapPlainText(stickerEmoji) + ' (' + notificationMessage + ')'; } } else { notificationMessage = message.media.document.file_name || _('conversation_media_document_raw'); } break; case 'messageMediaAudio': notificationMessage = _('conversation_media_audio_raw'); break; case 'messageMediaGeo': case 'messageMediaVenue': notificationMessage = _('conversation_media_location_raw'); break; case 'messageMediaContact': notificationMessage = _('conversation_media_contact_raw'); break; default: notificationMessage = _('conversation_media_attachment_raw'); break; } } else if (message._ == 'messageService') { switch (message.action._) { case 'messageActionChatCreate': notificationMessage = _('conversation_group_created_raw'); break; case 'messageActionChatEditTitle': notificationMessage = _('conversation_group_renamed_raw'); break; case 'messageActionChatEditPhoto': notificationMessage = _('conversation_group_photo_updated_raw'); break; case 'messageActionChatDeletePhoto': notificationMessage = _('conversation_group_photo_removed_raw'); break; case 'messageActionChatAddUser': notificationMessage = message.action.user_id == message.from_id ? _('conversation_returned_to_group') : _('conversation_invited_user_message_raw'); break; case 'messageActionChatDeleteUser': notificationMessage = message.action.user_id == message.from_id ? _('conversation_left_group') : _('conversation_kicked_user_message_raw'); break; case 'messageActionChatJoinedByLink': notificationMessage = _('conversation_joined_by_link'); break; } } if (peerID > 0) { notification.title = (fromUser.first_name || '') + (fromUser.first_name && fromUser.last_name ? ' ' : '') + (fromUser.last_name || ''); if (!notification.title) { notification.title = fromUser.phone || _('conversation_unknown_user_raw'); } notificationPhoto = fromPhoto; peerString = AppUsersManager.getUserString(peerID); } else { notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) + ' @ ' + (AppChatsManager.getChat(-peerID).title || _('conversation_unknown_chat_raw')); notificationPhoto = AppChatsManager.getChatPhoto(-peerID, 'Group'); peerString = AppChatsManager.getChatString(-peerID); } notification.title = RichTextProcessor.wrapPlainText(notification.title); notification.onclick = function () { $rootScope.$broadcast('history_focus', { peerString: peerString, messageID: message.flags & 16 ? message.id : 0, }); }; 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 (blob) { notification.image = FileManager.getUrl(blob, 'image/jpeg'); 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.selectPeers({confirm_type: 'EXT_SHARE_PEER'}).then(function (peerStrings) { angular.forEach(peerStrings, function (peerString) { var peerID = AppPeersManager.getPeerID(peerString); angular.forEach(source.data.blobs, function (blob) { sendFile(peerID, blob, {isMedia: true}); }); }) if (peerStrings.length == 1) { $rootScope.$broadcast('history_focus', {peerString: peerStrings[0]}); } }); } }); } var newMessagesHandlePromise = false; var newMessagesToHandle = {}; var newDialogsHandlePromise = false; var newDialogsToHandle = {}; var notificationsHandlePromise = false; var notificationsToHandle = {}; function handleNewMessages () { $timeout.cancel(newMessagesHandlePromise); newMessagesHandlePromise = false; $rootScope.$broadcast('history_multiappend', newMessagesToHandle); newMessagesToHandle = {}; } function handleNewDialogs () { $timeout.cancel(newDialogsHandlePromise); newDialogsHandlePromise = false; $rootScope.$broadcast('dialogs_multiupdate', newDialogsToHandle); newDialogsToHandle = {}; } function handleNotifications () { $timeout.cancel(notificationsHandlePromise); notificationsHandlePromise = false; var timeout = $rootScope.idle.isIDLE && StatusManager.isOtherDeviceActive() ? 30000 : 1000; angular.forEach(notificationsToHandle, function (notifyPeerToHandle) { notifyPeerToHandle.isMutedPromise.then(function (muted) { var topMessage = notifyPeerToHandle.top_message; if (muted || !topMessage.unread) { return; } setTimeout(function () { if (topMessage.unread) { notifyAboutMessage(topMessage, { fwd_count: notifyPeerToHandle.fwd_count }); } }, timeout); }); }); notificationsToHandle = {}; } $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 history = historyStorage.history; if (history.indexOf(message.id) != -1) { return false; } var topMsgID = history[0]; history.unshift(message.id); if (message.id > 0 && message.id < topMsgID) { history.sort(function (a, b) { return b - a; }); } if (historyStorage.count !== null) { historyStorage.count++; } } else { historyStorage = historiesStorage[peerID] = { count: null, history: [message.id], pending: [] }; } saveMessages([message]); if (mergeReplyKeyboard(historyStorage, message)) { $rootScope.$broadcast('history_reply_markup', {peerID: peerID}) } if (!message.out) { AppUsersManager.forceUserOnline(message.from_id); } 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) { if (newMessagesToHandle[peerID] === undefined) { newMessagesToHandle[peerID] = []; } newMessagesToHandle[peerID].push(message.id); if (!newMessagesHandlePromise) { newMessagesHandlePromise = $timeout(handleNewMessages, 0); } } var foundDialog = getDialogByPeerID(peerID); var dialog; var inboxUnread = !message.out && message.unread; if (foundDialog.length) { dialog = foundDialog[0]; if (foundDialog[1] > 0) { dialogsStorage.dialogs.splice(foundDialog[1], 1); dialogsStorage.dialogs.unshift(dialog); } dialog.top_message = message.id; if (inboxUnread) { dialog.unread_count++; } } else { SearchIndexManager.indexObject(peerID, AppPeersManager.getPeerSearchText(peerID), dialogsIndex); dialog = { peerID: peerID, unread_count: inboxUnread ? 1 : 0, top_message: message.id }; dialogsStorage.dialogs.unshift(dialog); } newDialogsToHandle[peerID] = dialog; if (!newDialogsHandlePromise) { newDialogsHandlePromise = $timeout(handleNewDialogs, 0); } if (inboxUnread && ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE)) { var notifyPeer = message.flags & 16 ? message.from_id : peerID; var notifyPeerToHandle = notificationsToHandle[notifyPeer]; if (notifyPeerToHandle === undefined) { notifyPeerToHandle = notificationsToHandle[notifyPeer] = { isMutedPromise: NotificationsManager.getPeerMuted(notifyPeer), fwd_count: 0, from_id: 0 }; } if (notifyPeerToHandle.from_id != message.from_id) { notifyPeerToHandle.from_id = message.from_id; notifyPeerToHandle.fwd_count = 0; } if (message.fwd_from_id) { notifyPeerToHandle.fwd_count++; } notifyPeerToHandle.top_message = message; if (!notificationsHandlePromise) { notificationsHandlePromise = $timeout(handleNotifications, 1000); } } incrementMaxSeenID(message.id); break; case 'updateReadHistoryInbox': case 'updateReadHistoryOutbox': var maxID = update.max_id; var isOut = update._ == 'updateReadHistoryOutbox'; var peerID = AppPeersManager.getPeerID(update.peer); var foundDialog = getDialogByPeerID(peerID); var history = (historiesStorage[peerID] || {}).history || []; var newUnreadCount = false; var length = history.length; var foundAffected = false; var messageID, message, i; if (peerID > 0 && isOut) { AppUsersManager.forceUserOnline(peerID); } for (i = 0; i < length; i++) { messageID = history[i]; if (messageID > maxID) { continue; } message = messagesStorage[messageID]; if (message.out != isOut) { continue; } if (!message.unread) { break; } // console.log('read', messageID, message.unread, message); if (message && message.unread) { message.unread = false; if (messagesForHistory[messageID]) { messagesForHistory[messageID].unread = false; if (!foundAffected) { foundAffected = true; } } if (messagesForDialogs[messageID]) { messagesForDialogs[messageID].unread = false; } if (!message.out) { if (foundDialog) { newUnreadCount = --foundDialog[0].unread_count; } NotificationsManager.cancel('msg' + messageID); } } } if (newUnreadCount !== false) { $rootScope.$broadcast('dialog_unread', {peerID: peerID, count: newUnreadCount}); } if (foundAffected) { $rootScope.$broadcast('messages_read'); } break; case 'updateReadMessagesContents': var messages = update.messages; var len = messages.length; var i, messageID, message, historyMessage; for (i = 0; i < len; i++) { messageID = messages[i]; if (message = messagesStorage[messageID]) { delete message.media_unread; } if (historyMessage = messagesForHistory[messageID]) { delete historyMessage.media_unread; } } break; case 'updateDeleteMessages': var dialogsUpdated = {}, historiesUpdated = {}, messageID, message, i, peerID, foundDialog, 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, flags: message.flags, 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; if (updatedData.count && historyStorage.count !== null && historyStorage.count > 0) { historyStorage.count -= updatedData.count; if (historyStorage.count < 0) { historyStorage.count = 0; } } 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; } }); $rootScope.$on('webpage_updated', function (e, eventData) { angular.forEach(eventData.msgs, function (msgID) { var historyMessage = messagesForHistory[msgID]; if (historyMessage) { historyMessage.media = { _: 'messageMediaWebPage', webpage: AppWebPagesManager.wrapForHistory(eventData.id) }; } }) }) return { getDialogs: getDialogs, getHistory: getHistory, getSearch: getSearch, getMessage: getMessage, getReplyKeyboard: getReplyKeyboard, readHistory: readHistory, readMessages: readMessages, flushHistory: flushHistory, deleteMessages: deleteMessages, saveMessages: saveMessages, sendText: sendText, sendFile: sendFile, sendOther: sendOther, forwardMessages: forwardMessages, startBot: startBot, openChatInviteLink: openChatInviteLink, getMessagePeer: getMessagePeer, wrapForDialog: wrapForDialog, wrapForHistory: wrapForHistory, wrapReplyMarkup: wrapReplyMarkup, regroupWrappedHistory: regroupWrappedHistory } }) .service('AppPhotosManager', function ($modal, $window, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager, FileManager) { var photos = {}, windowW = $(window).width(), windowH = $(window).height(); function savePhoto (apiPhoto, context) { if (context) { angular.extend(apiPhoto, context); } 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) { if (Config.Navigator.retina) { width *= 2; height *= 2; } 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 (userID, maxID, limit) { var inputUser = AppUsersManager.getUserInput(userID); 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 = []; var context = {user_id: userID}; for (var i = 0; i < photosResult.photos.length; i++) { savePhoto(photosResult.photos[i], context); 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]; var fullWidth = $(window).width() - (Config.Mobile ? 20 : 32); var fullHeight = $($window).height() - (Config.Mobile ? 150 : 116); if (fullWidth > 800) { fullWidth -= 208; } var 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, options) { options = options || {}; var photo = angular.copy(photos[photoID]) || {_: 'photoEmpty'}, width = options.website ? 100 : Math.min(windowW - 80, Config.Mobile ? 210 : 260), height = options.website ? 100 : Math.min(windowH - 100, Config.Mobile ? 210 : 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') { var dim = calcImageInBox(thumbPhotoSize.w, thumbPhotoSize.h, width, height); thumb.width = dim.w; thumb.height = dim.h; 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); var fullWidth = $(window).width() - (Config.Mobile ? 0 : 32); var fullHeight = $($window).height() - (Config.Mobile ? 0 : 116); if (!Config.Mobile && fullWidth > 800) { fullWidth -= 208; } var fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight); var full = { placeholder: 'img/placeholders/PhotoThumbModal.gif' }; full.width = fullWidth; full.height = fullHeight; if (fullPhotoSize && fullPhotoSize._ != 'photoSizeEmpty') { var wh = calcImageInBox(fullPhotoSize.w, fullPhotoSize.h, fullWidth, fullHeight, true); full.width = wh.w; full.height = wh.h; full.modalWidth = Math.max(full.width, Math.min(400, fullWidth)); full.location = fullPhotoSize.location; full.size = fullPhotoSize.size; } photo.full = full; return photo; } function openPhoto (photoID, list) { if (!photoID || photoID === '0') { return false; } var scope = $rootScope.$new(true); scope.photoID = photoID; var controller = 'PhotoModalController'; if (list && list.p > 0) { controller = 'UserpicModalController'; scope.userID = list.p; } else if (list && list.p < 0) { controller = 'ChatpicModalController'; scope.chatID = -list.p; } else if (list && list.m > 0) { scope.messageID = list.m; if (list.w) { scope.webpageID = list.w; } } var modalInstance = $modal.open({ templateUrl: templateUrl('photo_modal'), windowTemplateUrl: templateUrl('media_modal_layout'), controller: controller, scope: scope, windowClass: 'photo_modal_window' }); } function downloadPhoto (photoID) { var photo = photos[photoID], ext = 'jpg', mimeType = 'image/jpeg', fileName = 'photo' + photoID + '.' + ext, fullWidth = Math.max(screen.width || 0, $(window).width() - 36, 800), fullHeight = Math.max(screen.height || 0, $($window).height() - 150, 800), 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) { if (writableFileEntry) { MtpApiFileManager.downloadFile( fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, { mime: mimeType, toFileEntry: writableFileEntry }).then(function () { // console.log('file save done'); }, function (e) { console.log('photo download failed', e); }); } }, function () { var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation); if (cachedBlob) { return FileManager.download(cachedBlob, mimeType, fileName); } MtpApiFileManager.downloadFile( fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType} ).then(function (blob) { FileManager.download(blob, mimeType, fileName); }, function (e) { console.log('photo download failed', e); }); }); }; $rootScope.openPhoto = openPhoto; return { savePhoto: savePhoto, preloadPhoto: preloadPhoto, getUserPhotos: getUserPhotos, getPhoto: getPhoto, choosePhotoSize: choosePhotoSize, wrapForHistory: wrapForHistory, wrapForFull: wrapForFull, openPhoto: openPhoto, downloadPhoto: downloadPhoto } }) .service('AppWebPagesManager', function ($modal, $sce, $window, $rootScope, MtpApiManager, AppPhotosManager, RichTextProcessor) { var webpages = {}; var pendingWebPages = {}; function saveWebPage (apiWebPage, messageID, mediaContext) { if (apiWebPage.photo && apiWebPage.photo._ === 'photo') { AppPhotosManager.savePhoto(apiWebPage.photo, mediaContext); } else { delete apiWebPage.photo; } apiWebPage.rTitle = RichTextProcessor.wrapRichText( apiWebPage.title || apiWebPage.author, {noLinks: true, noLinebreaks: true} ); var contextHashtag = ''; if (apiWebPage.site_name == 'GitHub') { var matches = apiWebPage.url.match(/(https?:\/\/github\.com\/[^\/]+\/[^\/]+)/); if (matches) { contextHashtag = matches[0] + '/issues/{1}'; } } apiWebPage.rDescription = RichTextProcessor.wrapRichText( apiWebPage.description, { contextSite: apiWebPage.site_name || 'external', contextHashtag: contextHashtag } ); if (messageID) { if (pendingWebPages[apiWebPage.id] === undefined) { pendingWebPages[apiWebPage.id] = {}; } pendingWebPages[apiWebPage.id][messageID] = true; webpages[apiWebPage.id] = apiWebPage; } if (webpages[apiWebPage.id] === undefined) { webpages[apiWebPage.id] = apiWebPage; } else { safeReplaceObject(webpages[apiWebPage.id], apiWebPage); } if (!messageID && pendingWebPages[apiWebPage.id] !== undefined) { var msgs = []; angular.forEach(pendingWebPages[apiWebPage.id], function (t, msgID) { msgs.push(msgID); }); $rootScope.$broadcast('webpage_updated', { id: apiWebPage.id, msgs: msgs }); } }; function openEmbed (webpageID, messageID) { var scope = $rootScope.$new(true); scope.webpageID = webpageID; scope.messageID = messageID; $modal.open({ templateUrl: templateUrl('embed_modal'), windowTemplateUrl: templateUrl('media_modal_layout'), controller: 'EmbedModalController', scope: scope, windowClass: 'photo_modal_window' }); } function wrapForHistory (webPageID) { var webPage = angular.copy(webpages[webPageID]) || {_: 'webPageEmpty'}; if (webPage.photo && webPage.photo.id) { webPage.photo = AppPhotosManager.wrapForHistory(webPage.photo.id, {website: webPage.type != 'photo' && webPage.type != 'video'}); } return webPage; } function wrapForFull (webPageID) { var webPage = wrapForHistory(webPageID); if (!webPage.embed_url) { return webPage; } var fullWidth = $(window).width() - (Config.Mobile ? 0 : 10); var fullHeight = $($window).height() - (Config.Mobile ? 92 : 150); if (!Config.Mobile && fullWidth > 800) { fullWidth -= 208; } var full = { width: fullWidth, height: fullHeight, }; if (!webPage.embed_width || !webPage.embed_height) { full.height = full.width = Math.min(fullWidth, fullHeight); } else { var wh = calcImageInBox(webPage.embed_width, webPage.embed_height, fullWidth, fullHeight); full.width = wh.w; full.height = wh.h; } var embedTag = Config.Modes.chrome_packed ? 'webview' : 'iframe'; var embedType = webPage.embed_type != 'iframe' ? webPage.embed_type || 'text/html' : 'text/html'; var embedHtml = '<' + embedTag + ' src="' + encodeEntities(webPage.embed_url) + '" type="' + encodeEntities(embedType) + '" frameborder="0" border="0" webkitallowfullscreen mozallowfullscreen allowfullscreen width="' + full.width + '" height="' + full.height + '" style="width: ' + full.width + 'px; height: ' + full.height + 'px;">'; full.html = $sce.trustAs('html', embedHtml); webPage.full = full; return webPage; } $rootScope.$on('apiUpdate', function (e, update) { switch (update._) { case 'updateWebPage': saveWebPage(update.webpage); break; } }); return { saveWebPage: saveWebPage, openEmbed: openEmbed, wrapForFull: wrapForFull, wrapForHistory: wrapForHistory } }) .service('AppVideoManager', function ($sce, $rootScope, $modal, $window, MtpApiFileManager, AppUsersManager, FileManager, qSync) { var videos = {}, videosForHistory = {}, windowW = $(window).width(), windowH = $(window).height(); function saveVideo (apiVideo, context) { if (context) { angular.extend(apiVideo, context); } 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, Config.Mobile ? 210 : 150), height = Math.min(windowH - 100, Config.Mobile ? 210 : 150), 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() - (Config.Mobile ? 0 : 60), 542), fullHeight = $($window).height() - (Config.Mobile ? 92 : 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 { var wh = calcImageInBox(video.w, video.h, fullWidth, fullHeight); full.width = wh.w; full.height = wh.h; } video.full = full; video.fullThumb = angular.copy(video.thumb); video.fullThumb.width = full.width; video.fullThumb.height = full.height; return video; } function openVideo (videoID, messageID) { var scope = $rootScope.$new(true); scope.videoID = videoID; scope.messageID = messageID; return $modal.open({ templateUrl: templateUrl('video_modal'), windowTemplateUrl: templateUrl('media_modal_layout'), controller: 'VideoModalController', scope: scope, windowClass: 'video_modal_window' }); } function updateVideoDownloaded (videoID) { var video = videos[videoID], historyVideo = videosForHistory[videoID] || video || {}, inputFileLocation = { _: 'inputVideoFileLocation', id: videoID, access_hash: video.access_hash }; // historyVideo.progress = {enabled: true, percent: 10, total: video.size}; if (historyVideo.downloaded === undefined) { MtpApiFileManager.getDownloadedFile(inputFileLocation, video.size).then(function () { historyVideo.downloaded = true; }, function () { historyVideo.downloaded = false; }); } } function downloadVideo (videoID, toFileEntry) { var video = videos[videoID], historyVideo = videosForHistory[videoID] || video || {}, mimeType = video.mime_type || 'video/ogg', inputFileLocation = { _: 'inputVideoFileLocation', id: videoID, access_hash: video.access_hash }; if (historyVideo.downloaded && !toFileEntry) { var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation); if (cachedBlob) { return qSync.when(cachedBlob); } } historyVideo.progress = {enabled: !historyVideo.downloaded, percent: 1, total: video.size}; var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, { mime: mimeType, toFileEntry: toFileEntry }); downloadPromise.then(function (blob) { FileManager.getFileCorrectUrl(blob, mimeType).then(function (url) { historyVideo.url = $sce.trustAsResourceUrl(url); }); delete historyVideo.progress; historyVideo.downloaded = true; console.log('video save done'); }, function (e) { console.log('video download failed', e); historyVideo.progress.enabled = false; }, function (progress) { console.log('dl progress', progress); historyVideo.progress.enabled = true; historyVideo.progress.done = progress.done; historyVideo.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update'); }); historyVideo.progress.cancel = downloadPromise.cancel; return downloadPromise; } function saveVideoFile (videoID) { var video = videos[videoID], mimeType = video.mime_type || 'video/mp4', fileExt = mimeType.split('.')[1] || 'mp4', fileName = 't_video' + videoID + '.' + fileExt, historyVideo = videosForHistory[videoID] || video || {}; FileManager.chooseSave(fileName, fileExt, mimeType).then(function (writableFileEntry) { if (writableFileEntry) { downloadVideo(videoID, writableFileEntry); } }, function () { downloadVideo(videoID).then(function (blob) { FileManager.download(blob, mimeType, fileName); }); }); } return { saveVideo: saveVideo, wrapForHistory: wrapForHistory, wrapForFull: wrapForFull, openVideo: openVideo, updateVideoDownloaded: updateVideoDownloaded, downloadVideo: downloadVideo, saveVideoFile: saveVideoFile } }) .service('AppDocsManager', function ($sce, $rootScope, $modal, $window, $q, RichTextProcessor, MtpApiFileManager, FileManager, qSync) { var docs = {}, docsForHistory = {}, windowW = $(window).width(), windowH = $(window).height(); function saveDoc (apiDoc, context) { docs[apiDoc.id] = apiDoc; if (context) { angular.extend(apiDoc, context); } 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'; } angular.forEach(apiDoc.attributes, function (attribute) { switch (attribute._) { case 'documentAttributeFilename': apiDoc.file_name = attribute.file_name; break; case 'documentAttributeAudio': apiDoc.duration = attribute.duration; apiDoc.audioTitle = attribute.title; apiDoc.audioPerformer = attribute.performer; break; case 'documentAttributeVideo': apiDoc.duration = attribute.duration; break; case 'documentAttributeSticker': apiDoc.sticker = 1; if (attribute.alt !== undefined) { apiDoc.sticker = 2; apiDoc.stickerEmojiRaw = attribute.alt; apiDoc.stickerEmoji = RichTextProcessor.wrapRichText(apiDoc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true}); } if (attribute.stickerset) { if (attribute.stickerset._ == 'inputStickerSetEmpty') { delete attribute.stickerset; } else if (attribute.stickerset._ == 'inputStickerSetID') { apiDoc.stickerSetInput = attribute.stickerset; } } break; case 'documentAttributeImageSize': apiDoc.w = attribute.w; apiDoc.h = attribute.h; break; } }); apiDoc.file_name = apiDoc.file_name || ''; }; function getDoc (docID) { return docs[docID] || {_: 'documentEmpty'}; } function wrapForHistory (docID) { if (docsForHistory[docID] !== undefined) { return docsForHistory[docID]; } var doc = angular.copy(docs[docID]), isGif = doc.mime_type == 'image/gif', isSticker = doc.mime_type.substr(0, 6) == 'image/' && doc.sticker, thumbPhotoSize = doc.thumb, width, height; if (isGif) { width = Math.min(windowW - 80, 260); height = Math.min(windowH - 100, 260); } else if (isSticker) { width = Math.min(windowW - 80, Config.Mobile ? 128 : 192); height = Math.min(windowH - 100, Config.Mobile ? 128 : 192); } else { width = height = 100; } var thumb = { width: width, height: height }; if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { var dim = calcImageInBox(thumbPhotoSize.w, thumbPhotoSize.h, width, height); thumb.width = dim.w; thumb.height = dim.h; thumb.location = thumbPhotoSize.location; thumb.size = thumbPhotoSize.size; } else if (isSticker) { var dim = calcImageInBox(doc.w, doc.h, width, height); thumb.width = dim.w; thumb.height = dim.h; } else { thumb = false; } doc.thumb = thumb; doc.withPreview = !Config.Mobile && doc.mime_type.match(/^(image\/)/) ? 1 : 0; if (isGif && doc.thumb) { doc.isSpecial = 'gif'; } else if (isSticker) { doc.isSpecial = 'sticker'; } else if (doc.mime_type.substr(0, 6) == 'audio/') { doc.isSpecial = 'audio'; } return docsForHistory[docID] = doc; } function updateDocDownloaded (docID) { var doc = docs[docID], historyDoc = docsForHistory[docID] || doc || {}, inputFileLocation = { _: 'inputDocumentFileLocation', id: docID, access_hash: doc.access_hash, file_name: doc.file_name }; if (historyDoc.downloaded === undefined) { MtpApiFileManager.getDownloadedFile(inputFileLocation, doc.size).then(function () { historyDoc.downloaded = true; }, function () { historyDoc.downloaded = false; }); } } function downloadDoc (docID, toFileEntry) { var doc = docs[docID], historyDoc = docsForHistory[docID] || doc || {}, inputFileLocation = { _: 'inputDocumentFileLocation', id: docID, access_hash: doc.access_hash, file_name: doc.file_name }; if (historyDoc.downloaded && !toFileEntry) { var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation); if (cachedBlob) { return qSync.when(cachedBlob); } } historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size}; var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, { mime: doc.mime_type || 'application/octet-stream', toFileEntry: toFileEntry }); downloadPromise.then(function (blob) { delete historyDoc.progress; if (blob) { FileManager.getFileCorrectUrl(blob, doc.mime_type).then(function (url) { historyDoc.url = $sce.trustAsResourceUrl(url); }) historyDoc.downloaded = true; } console.log('file save done'); }, function (e) { console.log('document download failed', e); historyDoc.progress.enabled = false; }, function (progress) { console.log('dl progress', progress); historyDoc.progress.enabled = true; historyDoc.progress.done = progress.done; historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update'); }); historyDoc.progress.cancel = downloadPromise.cancel; return downloadPromise; } function openDoc (docID, messageID) { var scope = $rootScope.$new(true); scope.docID = docID; scope.messageID = messageID; var modalInstance = $modal.open({ templateUrl: templateUrl('document_modal'), windowTemplateUrl: templateUrl('media_modal_layout'), controller: 'DocumentModalController', scope: scope, windowClass: 'document_modal_window' }); } function saveDocFile (docID) { var doc = docs[docID], historyDoc = docsForHistory[docID] || doc || {}; var ext = (doc.file_name.split('.', 2) || [])[1] || ''; FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) { if (writableFileEntry) { downloadDoc(docID, writableFileEntry); } }, function () { downloadDoc(docID).then(function (blob) { FileManager.download(blob, doc.mime_type, doc.file_name); }); }); } return { saveDoc: saveDoc, getDoc: getDoc, wrapForHistory: wrapForHistory, updateDocDownloaded: updateDocDownloaded, downloadDoc: downloadDoc, openDoc: openDoc, saveDocFile: saveDocFile } }) .service('AppAudioManager', function ($sce, $rootScope, $modal, $window, MtpApiFileManager, FileManager, qSync) { 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 updateAudioDownloaded (audioID) { var audio = audios[audioID], historyAudio = audiosForHistory[audioID] || audio || {}, inputFileLocation = { _: 'inputAudioFileLocation', id: audioID, access_hash: audio.access_hash }; // historyAudio.progress = {enabled: !historyAudio.downloaded, percent: 10, total: audio.size}; if (historyAudio.downloaded === undefined) { MtpApiFileManager.getDownloadedFile(inputFileLocation, audio.size).then(function () { historyAudio.downloaded = true; }, function () { historyAudio.downloaded = false; }); } } function downloadAudio (audioID, toFileEntry) { var audio = audios[audioID], historyAudio = audiosForHistory[audioID] || audio || {}, mimeType = audio.mime_type || 'audio/ogg', inputFileLocation = { _: 'inputAudioFileLocation', id: audioID, access_hash: audio.access_hash }; if (historyAudio.downloaded && !toFileEntry) { var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation); if (cachedBlob) { return qSync.when(cachedBlob); } } historyAudio.progress = {enabled: !historyAudio.downloaded, percent: 1, total: audio.size}; var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, { mime: mimeType, toFileEntry: toFileEntry }); downloadPromise.then(function (blob) { FileManager.getFileCorrectUrl(blob, mimeType).then(function (url) { historyAudio.url = $sce.trustAsResourceUrl(url); }); delete historyAudio.progress; historyAudio.downloaded = true; console.log('audio save done'); }, function (e) { console.log('audio download failed', e); historyAudio.progress.enabled = false; }, function (progress) { console.log('dl progress', progress); historyAudio.progress.enabled = true; historyAudio.progress.done = progress.done; historyAudio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); $rootScope.$broadcast('history_update'); }); historyAudio.progress.cancel = downloadPromise.cancel; return downloadPromise; } function saveAudioFile (audioID) { var audio = audios[audioID], mimeType = audio.mime_type || 'audio/ogg', fileExt = mimeType.split('.')[1] || 'ogg', fileName = 't_audio' + audioID + '.' + fileExt, historyAudio = audiosForHistory[audioID] || audio || {}; FileManager.chooseSave(fileName, fileExt, mimeType).then(function (writableFileEntry) { if (writableFileEntry) { downloadAudio(audioID, writableFileEntry); } }, function () { downloadAudio(audioID).then(function (blob) { FileManager.download(blob, mimeType, fileName); }); }); } return { saveAudio: saveAudio, wrapForHistory: wrapForHistory, updateAudioDownloaded: updateAudioDownloaded, downloadAudio: downloadAudio, saveAudioFile: saveAudioFile } }) .service('AppStickersManager', function ($q, $rootScope, $modal, _, FileManager, MtpApiManager, MtpApiFileManager, AppDocsManager, Storage) { var currentStickers = []; var currentStickersets = []; var installedStickersets = {}; var stickersetItems = {}; var applied = false; var started = false; return { start: start, openStickersetLink: openStickersetLink, openStickerset: openStickerset, installStickerset: installStickerset, getStickers: getStickers, getStickerset: getStickerset, getStickersImages: getStickersImages }; function start () { if (!started) { started = true; setTimeout(getStickers, 1000); } } function processRawStickers(stickers) { if (applied !== stickers.hash) { applied = stickers.hash; var i, j, len1, len2, doc, set, setItems, fullSet; currentStickersets = []; currentStickers = []; len1 = stickers.sets.length; for (i = 0; i < len1; i++) { set = stickers.sets[i]; fullSet = stickers.fullSets[set.id]; len2 = fullSet.documents.length; setItems = []; for (j = 0; j < len2; j++) { doc = fullSet.documents[j]; AppDocsManager.saveDoc(doc); currentStickers.push(doc.id); setItems.push(doc.id); } currentStickersets.push({ id: set.id, title: set.title, short_name: set.short_name, installed: (set.flags & (1 << 0)) > 0, disabled: (set.flags & (1 << 1)) > 0, official: (set.flags & (1 << 2)) > 0, docIDs: setItems }); installedStickersets[set.id] = true; } } return currentStickersets; } function getStickers (force) { return Storage.get('all_stickers').then(function (stickers) { var layer = Config.Schema.API.layer; if (stickers.layer != layer) { stickers = false; } if (stickers && stickers.date > tsNow(true) && !force) { return processRawStickers(stickers); } return MtpApiManager.invokeApi('messages.getAllStickers', { hash: stickers && stickers.hash || '' }).then(function (newStickers) { var notModified = newStickers._ == 'messages.allStickersNotModified'; if (notModified) { newStickers = stickers; } newStickers.date = tsNow(true) + 3600; newStickers.layer = layer; delete newStickers._; if (notModified) { Storage.set({all_stickers: newStickers}); return processRawStickers(newStickers); } return getStickerSets(newStickers).then(function () { Storage.set({all_stickers: newStickers}); return processRawStickers(newStickers); }); }); }) } function getStickerSets (allStickers) { var promises = []; var cachedSets = allStickers.fullSets || {}; allStickers.fullSets = {}; angular.forEach(allStickers.sets, function (shortSet) { var fullSet = cachedSets[shortSet.id]; if (fullSet && fullSet.set.hash == shortSet.hash) { allStickers.fullSets[shortSet.id] = fullSet; } else { var promise = MtpApiManager.invokeApi('messages.getStickerSet', { stickerset: { _: 'inputStickerSetID', id: shortSet.id, access_hash: shortSet.access_hash } }).then(function (fullSet) { allStickers.fullSets[shortSet.id] = fullSet; }); promises.push(promise); } }); return $q.all(promises); } function downloadStickerThumb (docID) { var doc = AppDocsManager.getDoc(docID); var thumbLocation = angular.copy(doc.thumb.location); thumbLocation.sticker = true; return MtpApiFileManager.downloadSmallFile(thumbLocation).then(function (blob) { return { id: doc.id, src: FileManager.getUrl(blob, 'image/webp') }; }); } function getStickersImages () { var promises = []; angular.forEach(currentStickers, function (docID) { promises.push(downloadStickerThumb (docID)); }); return $q.all(promises); } function getStickerset (inputStickerset) { return MtpApiManager.invokeApi('messages.getStickerSet', { stickerset: inputStickerset }).then(function (result) { for (var i = 0; i < result.documents.length; i++) { AppDocsManager.saveDoc(result.documents[i]); } result.installed = installedStickersets[result.set.id] !== undefined; return result; }); } function installStickerset (set, uninstall) { var method = uninstall ? 'messages.uninstallStickerSet' : 'messages.installStickerSet'; var inputStickerset = { _: 'inputStickerSetID', id: set.id, access_hash: set.access_hash }; return MtpApiManager.invokeApi(method, { stickerset: inputStickerset, disabled: false }).then(function (result) { if (uninstall) { delete installedStickersets[set.id]; } else { installedStickersets[set.id] = true; } getStickers(true); }); } function openStickersetLink (shortName) { return openStickerset({ _: 'inputStickerSetShortName', short_name: shortName }); } function openStickerset (inputStickerset) { var scope = $rootScope.$new(true); scope.inputStickerset = inputStickerset; var modal = $modal.open({ templateUrl: templateUrl('stickerset_modal'), controller: 'StickersetModalController', scope: scope, windowClass: 'stickerset_modal_window mobile_modal' }); } }) .service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) { var curState = {}; var myID = 0; MtpApiManager.getUserID().then(function (id) { myID = id; }); var syncPending = false; var syncLoading = true; var pendingSeqUpdates = {}; var pendingPtsUpdates = []; function popPendingSeqUpdate () { var nextSeq = curState.seq + 1, pendingUpdatesData = pendingSeqUpdates[nextSeq]; if (!pendingUpdatesData) { return false; } var updates = pendingUpdatesData.updates; var i, length; for (var i = 0, length = updates.length; i < length; i++) { saveUpdate(updates[i]); } curState.seq = pendingUpdatesData.seq; if (pendingUpdatesData.date && curState.date < pendingUpdatesData.date) { curState.date = pendingUpdatesData.date; } delete pendingSeqUpdates[nextSeq]; if (!popPendingSeqUpdate() && syncPending && syncPending.seqAwaiting && curState.seq >= syncPending.seqAwaiting) { if (!syncPending.ptsAwaiting) { clearTimeout(syncPending.timeout); syncPending = false; } else { delete syncPending.seqAwaiting; } } return true; } function popPendingPtsUpdate () { if (!pendingPtsUpdates.length) { return false; } pendingPtsUpdates.sort(function (a, b) { return a.pts - b.pts; }); var curPts = curState.pts; var goodPts = false; var goodIndex = false; var update; for (var i = 0, length = pendingPtsUpdates.length; i < length; i++) { update = pendingPtsUpdates[i]; curPts += update.pts_count; if (curPts >= update.pts) { goodPts = update.pts; goodIndex = i; } } if (!goodPts) { return false; } curState.pts = goodPts; for (i = 0; i <= goodIndex; i++) { update = pendingPtsUpdates[i]; saveUpdate(update); } pendingPtsUpdates.splice(goodIndex, length - goodIndex); if (!pendingPtsUpdates.length && syncPending) { if (!syncPending.seqAwaiting) { clearTimeout(syncPending.timeout); syncPending = false; } else { delete syncPending.ptsAwaiting; } } return true; } function forceGetDifference () { if (!syncLoading) { getDifference(); } } function processUpdateMessage (updateMessage) { var processOpts = { date: updateMessage.date, seq: updateMessage.seq, seqStart: updateMessage.seq_start }; switch (updateMessage._) { case 'updatesTooLong': case 'new_session_created': forceGetDifference(); break; case 'updateShort': processUpdate(updateMessage.update, processOpts); break; case 'updateShortMessage': case 'updateShortChatMessage': var isOut = updateMessage.flags & 2; var fromID = updateMessage.from_id || (isOut ? myID : updateMessage.user_id); var toID = updateMessage.chat_id ? -updateMessage.chat_id : (isOut ? updateMessage.user_id : myID); processUpdate({ _: 'updateNewMessage', message: { _: 'message', flags: updateMessage.flags, id: updateMessage.id, from_id: fromID, to_id: AppPeersManager.getOutputPeer(toID), date: updateMessage.date, message: updateMessage.message, fwd_from_id: updateMessage.fwd_from_id, fwd_date: updateMessage.fwd_date, reply_to_msg_id: updateMessage.reply_to_msg_id, }, pts: updateMessage.pts, pts_count: updateMessage.pts_count }, processOpts); break; case 'updatesCombined': case 'updates': AppUsersManager.saveApiUsers(updateMessage.users); AppChatsManager.saveApiChats(updateMessage.chats); angular.forEach(updateMessage.updates, function (update) { processUpdate(update, processOpts); }); break; default: console.warn(dT(), 'Unknown update message', updateMessage); } } function getDifference () { if (!syncLoading) { syncLoading = true; pendingSeqUpdates = {}; pendingPtsUpdates = []; } if (syncPending) { clearTimeout(syncPending.timeout); syncPending = false; } MtpApiManager.invokeApi('updates.getDifference', {pts: curState.pts, date: curState.date, qts: -1}).then(function (differenceResult) { if (differenceResult._ == 'updates.differenceEmpty') { console.log(dT(), 'apply empty diff', differenceResult.seq); curState.date = differenceResult.date; curState.seq = differenceResult.seq; syncLoading = false; $rootScope.$broadcast('stateSynchronized'); return false; } AppUsersManager.saveApiUsers(differenceResult.users); AppChatsManager.saveApiChats(differenceResult.chats); // Should be first because of updateMessageID // console.log(dT(), 'applying', differenceResult.other_updates.length, 'other updates'); angular.forEach(differenceResult.other_updates, function(update){ saveUpdate(update); }); // console.log(dT(), 'applying', differenceResult.new_messages.length, 'new messages'); angular.forEach(differenceResult.new_messages, function (apiMessage) { saveUpdate({ _: 'updateNewMessage', message: apiMessage, pts: curState.pts, pts_count: 0 }); }); 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(); } else { // console.log(dT(), 'finished get diff'); $rootScope.$broadcast('stateSynchronized'); syncLoading = false; } }); } function processUpdate (update, options) { if (syncLoading) { return false; } if (update._ == 'updateNewMessage') { var message = update.message; if (message.from_id && !AppUsersManager.hasUser(message.from_id) || message.fwd_from_id && !AppUsersManager.hasUser(message.fwd_from_id) || message.to_id.user_id && !AppUsersManager.hasUser(message.to_id.user_id) || message.to_id.chat_id && !AppChatsManager.hasChat(message.to_id.chat_id)) { console.warn(dT(), 'Short update not enough data', message); forceGetDifference(); return false; } } var popPts, popSeq; if (update.pts) { var newPts = curState.pts + (update.pts_count || 0); if (newPts < update.pts) { console.log(dT(), 'Pts hole', curState, update); pendingPtsUpdates.push(update); if (!syncPending) { syncPending = { timeout: setTimeout(function () { getDifference(); }, 5000) }; } syncPending.ptsAwaiting = true; return false; } if (update.pts > curState.pts) { curState.pts = update.pts; popPts = true; } } else if (options.seq > 0) { var seq = options.seq; var seqStart = options.seqStart || seq; if (seqStart != curState.seq + 1) { if (seqStart > curState.seq) { console.warn(dT(), 'Seq hole', curState, syncPending && syncPending.seqAwaiting); if (pendingSeqUpdates[seqStart] === undefined) { pendingSeqUpdates[seqStart] = {seq: seq, date: options.date, updates: []}; } pendingSeqUpdates[seqStart].updates.push(update); if (!syncPending) { syncPending = { timeout: setTimeout(function () { getDifference(); }, 5000) }; } if (!syncPending.seqAwaiting || syncPending.seqAwaiting < seqStart) { syncPending.seqAwaiting = seqStart; } return false; } } if (curState.seq != seq) { curState.seq = seq; if (options.date && curState.date < options.date) { curState.date = options.date; } popSeq = true; } } saveUpdate (update); if (popPts) { popPendingPtsUpdate(); } else if (popSeq) { popPendingSeqUpdate(); } } function saveUpdate (update) { $rootScope.$broadcast('apiUpdate', update); } function attach () { MtpNetworkerFactory.setUpdatesProcessor(processUpdateMessage); MtpApiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then(function (stateResult) { curState.seq = stateResult.seq; curState.pts = stateResult.pts; curState.date = stateResult.date; setTimeout(function () { syncLoading = false; }, 1000); // curState.seq = 1; // curState.pts = stateResult.pts - 5000; // curState.date = 1; // getDifference(); }) } return { processUpdateMessage: processUpdateMessage, attach: attach } }) .service('RichTextProcessor', function ($sce, $sanitize) { var emojiMap = {}, emojiData = Config.Emoji, emojiIconSize = 18, emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1, emojiCode; var emojiRegExp = "\\u0023\\u20E3|\\u00a9|\\u00ae|\\u203c|\\u2049|\\u2139|[\\u2194-\\u2199]|\\u21a9|\\u21aa|\\u231a|\\u231b|\\u23e9|[\\u23ea-\\u23ec]|\\u23f0|\\u24c2|\\u25aa|\\u25ab|\\u25b6|\\u2611|\\u2614|\\u26fd|\\u2705|\\u2709|[\\u2795-\\u2797]|\\u27a1|\\u27b0|\\u27bf|\\u2934|\\u2935|[\\u2b05-\\u2b07]|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u3030|\\u303d|\\u3297|\\u3299|[\\uE000-\\uF8FF\\u270A-\\u2764\\u2122\\u25C0\\u25FB-\\u25FE\\u2615\\u263a\\u2648-\\u2653\\u2660-\\u2668\\u267B\\u267F\\u2693\\u261d\\u26A0-\\u26FA\\u2708\\u2702\\u2601\\u260E]|[\\u2600\\u26C4\\u26BE\\u23F3\\u2764]|\\uD83D[\\uDC00-\\uDFFF]|\\uD83C[\\uDDE8-\\uDDFA\uDDEC]\\uD83C[\\uDDEA-\\uDDFA\uDDE7]|[0-9]\\u20e3|\\uD83C[\\uDC00-\\uDFFF]"; for (emojiCode in emojiData) { emojiMap[emojiData[emojiCode][0]] = emojiCode; } var alphaCharsRegExp = "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 alphaNumericRegExp = "0-9\_" + alphaCharsRegExp; // Based on Regular Expression for URL validation by Diego Perini var urlRegExp = "((?:https?|ftp)://|mailto:)?" + // user:pass authentication "(?:\\S{1,64}(?::\\S{0,64})?@)?" + "(?:" + // sindresorhus/ip-regexp "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}" + "|" + // host name "[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "0-9\-]{0,64}" + // domain name "(?:\\.[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "0-9\-]{0,64}){0,10}" + // TLD identifier "(?:\\.(xn--[0-9a-z]{2,16}|[" + alphaCharsRegExp + "]{2,24}))" + ")" + // port number "(?::\\d{2,5})?" + // resource path "(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>\"'])?)?"; var usernameRegExp = "[a-zA-Z\\d_]{5,32}"; var botCommandRegExp = "\\/([a-zA-Z\\d_]{1,32})(?:@(" + usernameRegExp + "))?(\\b|$)" var fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|\\s)(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i'); var emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; var youtubeRegExp = /^(?:https?:\/\/)?(?:www\.)?youtu(?:|\.be|be\.com|\.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/; var vimeoRegExp = /^(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/; var instagramRegExp = /^https?:\/\/(?:instagr\.am\/p\/|instagram\.com\/p\/)([a-zA-Z0-9\-\_]+)/i; var vineRegExp = /^https?:\/\/vine\.co\/v\/([a-zA-Z0-9\-\_]+)/i; var twitterRegExp = /^https?:\/\/twitter\.com\/.+?\/status\/\d+/i; var facebookRegExp = /^https?:\/\/(?:www\.|m\.)?facebook\.com\/(?:.+?\/posts\/\d+|(?:story\.php|permalink\.php)\?story_fbid=(\d+)(?:&substory_index=\d+)?&id=(\d+))/i; var gplusRegExp = /^https?:\/\/plus\.google\.com\/\d+\/posts\/[a-zA-Z0-9\-\_]+/i; var soundcloudRegExp = /^https?:\/\/(?:soundcloud\.com|snd\.sc)\/([a-zA-Z0-9%\-\_]+)\/([a-zA-Z0-9%\-\_]+)/i; var spotifyRegExp = /(https?:\/\/(open\.spotify\.com|play\.spotify\.com|spoti\.fi)\/(.+)|spotify:(.+))/i; var markdownRegExp = /(^|\n)```(.{0,16})\n([\s\S]+?)\n```(\n|$)|(^|\s)`([^\n]+?)`/; var siteHashtags = { Telegram: '#/im?q=%23{1}', Twitter: 'https://twitter.com/hashtag/{1}', Instagram: 'https://instagram.com/explore/tags/{1}/', 'Google Plus': 'https://plus.google.com/explore/{1}' }; var siteMentions = { Telegram: '#/im?p=%40{1}', Twitter: 'https://twitter.com/{1}', Instagram: 'https://instagram.com/{1}/', GitHub: 'https://github.com/{1}' }; return { wrapRichText: wrapRichText, wrapPlainText: wrapPlainText, parseEntities: parseEntities, parseMarkdown: parseMarkdown, mergeEntities: mergeEntities }; 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 parseEntities (text, options) { options = options || {}; var match, raw = text, url, entities = [], emojiCode, emojiCoords, matchIndex, rawOffset = 0; // var start = tsNow(); while ((match = raw.match(fullRegExp))) { matchIndex = rawOffset + match.index; if (match[3]) { // mentions entities.push({ _: 'messageEntityMention', offset: matchIndex + match[1].length, length: match[2].length + match[3].length }); } else if (match[4]) { if (emailRegExp.test(match[4])) { // email entities.push({ _: 'messageEntityEmail', offset: matchIndex, length: match[4].length }); } else { var url = false, protocol = match[5], tld = match[6], excluded = ''; if (tld) { // URL if (!protocol && (tld.substr(0, 4) === 'xn--' || Config.TLD.indexOf(tld.toLowerCase()) !== -1)) { protocol = 'http://'; } if (protocol) { var balanced = checkBrackets(match[4]); if (balanced.length !== match[4].length) { excluded = match[4].substring(balanced.length); match[4] = balanced; } url = (match[5] ? '' : protocol) + match[4]; } var tgMeMatch; if (tld == 'me' && (tgMeMatch = url.match(/^https?:\/\/telegram\.me\/(.+)/))) { var path = tgMeMatch[1].split('/'); switch (path[0]) { case 'joinchat': url = 'tg://join?invite=' + path[1]; break; case 'addstickers': url = 'tg://addstickers?set=' + path[1]; break; default: var domainQuery = path[0].split('?'); url = 'tg://resolve?domain=' + domainQuery[0] + (domainQuery[1] ? '&' + domainQuery[1] : ''); } } } else { // IP address url = (match[5] ? '' : 'http://') + match[4]; } if (url) { entities.push({ _: 'messageEntityUrl', offset: matchIndex, length: match[4].length }); } } } else if (match[7]) { // New line entities.push({ _: 'messageEntityLinebreak', offset: matchIndex, length: 1 }); } else if (match[8]) { // Emoji if ((emojiCode = emojiMap[match[8]]) && (emojiCoords = getEmojiSpritesheetCoords(emojiCode))) { entities.push({ _: 'messageEntityEmoji', offset: matchIndex, length: match[0].length, coords: emojiCoords, title: emojiData[emojiCode][1][0] }); } } else if (match[10]) { // Hashtag entities.push({ _: 'messageEntityHashtag', offset: matchIndex + match[9].length, length: match[10].length }); } else if (match[12]) { // Bot command entities.push({ _: 'messageEntityBotCommand', offset: matchIndex + match[11].length, length: 1 + match[12].length + (match[13] ? 1 + match[13].length : 0) }); } raw = raw.substr(match.index + match[0].length); rawOffset += match.index + match[0].length; } if (entities.length) { console.log('parse entities', text, entities.slice()); } return entities; } function parseMarkdown (text, entities) { if (text.indexOf('`') == -1) { return text; } var raw = text; var match; var newText = []; while (match = raw.match(markdownRegExp)) { newText.push(raw.substr(0, match.index)); if (match[3]) { // pre newText.push(match[1] + match[3] + match[4]); entities.push({ _: 'messageEntityPre', language: match[2] || '', offset: match.index + match[1].length, length: match[3].length }) } else { // code newText.push(match[5] + match[6]); entities.push({ _: 'messageEntityCode', offset: match.index + match[5].length, length: match[6].length }) } raw = raw.substr(match.index + match[0].length); } newText.push(raw); return newText.join(''); } function mergeEntities (currentEntities, newEntities, fromApi) { var totalEntities = newEntities.slice(); var i, len = currentEntities.length; var j, len2 = newEntities.length; var startJ = 0; var curEntity, newEntity; var start, end, cStart, cEnd, bad; for (i = 0; i < len; i++) { curEntity = currentEntities[i]; if (fromApi && curEntity._ != 'messageEntityLinebreak' && curEntity._ != 'messageEntityEmoji') { continue; } // console.log('s', curEntity, newEntities); start = curEntity.offset; end = start + curEntity.length; bad = false; for (j = startJ; j < len2; j++) { newEntity = newEntities[j]; cStart = newEntity.offset; cEnd = cStart + newEntity.length; if (cStart <= start) { startJ = j; } if (start >= cStart && start < cEnd || end > cStart && end <= cEnd) { // console.log('bad', curEntity, newEntity); bad = true; break; } if (cStart >= end) { break; } } if (bad) { continue; } totalEntities.push(curEntity); } totalEntities.sort(function (a, b) { return a.offset - b.offset; }); // console.log('merge', currentEntities, newEntities, totalEntities); return totalEntities; } function wrapRichText (text, options) { if (!text || !text.length) { return ''; } options = options || {}; var entities = options.entities, contextSite = options.contextSite || 'Telegram', contextExternal = contextSite != 'Telegram', emojiFound = false; if (entities === undefined) { entities = parseEntities(text, options); } var i = 0; var len = entities.length; var entity; var entityText; var skipEntity; var url; var html = []; var lastOffset = 0; for (i = 0; i < len; i++) { entity = entities[i]; if (entity.offset > lastOffset) { html.push( encodeEntities(text.substr(lastOffset, entity.offset - lastOffset)) ); } else if (entity.offset < lastOffset) { continue; } skipEntity = false; entityText = text.substr(entity.offset, entity.length); switch (entity._) { case 'messageEntityMention': var contextUrl = !options.noLinks && siteMentions[contextSite]; if (!contextUrl) { skipEntity = true; break; } var username = entityText.substr(1); var attr = ''; if (options.highlightUsername && options.highlightUsername.toLowerCase() == username.toLowerCase()) { attr = 'class="im_message_mymention"'; } html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityHashtag': var contextUrl = !options.noLinks && siteHashtags[contextSite]; if (!contextUrl) { skipEntity = true; break; } var hashtag = entityText.substr(1); html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityEmail': if (options.noLinks) { skipEntity = true; break; } html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityUrl': case 'messageEntityTextUrl': if (options.noLinks) { skipEntity = true; break; } var url = entity.url || entityText; if (!url.match(/^https?:\/\//i)) { url = 'http://' + url; } var tgMeMatch; if ((tgMeMatch = url.match(/^https?:\/\/telegram\.me\/(.+)/))) { var path = tgMeMatch[1].split('/'); switch (path[0]) { case 'joinchat': url = 'tg://join?invite=' + path[1]; break; case 'addstickers': url = 'tg://addstickers?set=' + path[1]; break; default: var domainQuery = path[0].split('?'); url = 'tg://resolve?domain=' + domainQuery[0] + (domainQuery[1] ? '&' + domainQuery[1] : ''); } } html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityLinebreak': html.push(options.noLinebreaks ? ' ' : '
'); break; case 'messageEntityEmoji': html.push( '', ':', entity.title, ':' ); emojiFound = true; break; case 'messageEntityBotCommand': if (options.noLinks || options.noCommands || contextExternal) { skipEntity = true; break; } var command = entityText; var bot, atPos; if ((atPos = command.indexOf('@')) != -1) { bot = command.substr(atPos); command = command.substr(0, atPos); } else { bot = options.fromBot; } html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityBold': html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityItalic': html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityCode': html.push( '', encodeEntities(entityText), '' ); break; case 'messageEntityPre': html.push( '
',
            encodeEntities(entityText),
            '
' ); break; default: skipEntity = true; } if (!skipEntity) { lastOffset = entity.offset + entity.length; } } html.push(encodeEntities(text.substr(lastOffset))); text = $sanitize(html.join('')); if (emojiFound) { text = text.replace(/\ufe0f|️|�|‍/g, '', text); text = text.replace(//g, ''); } return $sce.trustAs('html', text); } function checkBrackets(url) { var urlLength = url.length, urlOpenBrackets = url.split('(').length - 1, urlCloseBrackets = url.split(')').length - 1; while (urlCloseBrackets > urlOpenBrackets && url.charAt(urlLength - 1) === ')') { url = url.substr(0, urlLength - 1); urlCloseBrackets--; urlLength--; } if (urlOpenBrackets > urlCloseBrackets) { url = url.replace(/\)+$/, ''); } return url; } 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(fullRegExp))) { text.push(raw.substr(0, match.index)); if (match[8]) { if ((emojiCode = emojiMap[match[8]]) && (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('StatusManager', function ($timeout, $rootScope, MtpApiManager, AppUsersManager, IdleManager) { var toPromise; var lastOnlineUpdated = 0; var started = false; var myID = 0; var myOtherDeviceActive = false; MtpApiManager.getUserID().then(function (id) { myID = id; }); $rootScope.$on('apiUpdate', function (e, update) { if (update._ == 'updateUserStatus' && update.user_id == myID) { myOtherDeviceActive = tsNow() + (update.status._ == 'userStatusOnline' ? 300000 : 0); } }); return { start: start, isOtherDeviceActive: isOtherDeviceActive }; 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; AppUsersManager.setUserStatus(myID, offline); 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); } } function isOtherDeviceActive() { if (!myOtherDeviceActive) { return false; } if (tsNow() > myOtherDeviceActive) { myOtherDeviceActive = false; return false; } return true; } }) .service('NotificationsManager', function ($rootScope, $window, $interval, $q, _, MtpApiManager, AppPeersManager, IdleManager, Storage, AppRuntimeManager) { navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate; var notificationsMsSiteMode = false; try { if (window.external && window.external.msIsSiteMode()) { notificationsMsSiteMode = true; } } catch (e) {}; var notificationsUiSupport = notificationsMsSiteMode || ('Notification' in window) || ('mozNotification' in navigator); var notificationsShown = {}; var notificationIndex = 0; var notificationsCount = 0; var soundsPlayed = {}; var vibrateSupport = !!navigator.vibrate; var nextSoundAt = false; var prevSoundVolume = false; var peerSettings = {}; var faviconEl = $('link[rel="icon"]:first')[0]; var langNotificationsPluralize = _.pluralize('page_title_pluralize_notifications'); var titleBackup = document.title, titleChanged = false, titlePromise; var prevFavicon; var settings = {}; $rootScope.$watch('idle.isIDLE', function (newVal) { if (!newVal) { notificationsClear(); } if (!Config.Navigator.mobile) { $interval.cancel(titlePromise); if (!newVal) { titleChanged = false; document.title = titleBackup; setFavicon(); } else { titleBackup = document.title; titlePromise = $interval(function () { var time = tsNow(); if (!notificationsCount || time % 2000 > 1000) { if (titleChanged) { titleChanged = false; document.title = titleBackup; setFavicon(); } } else { titleChanged = true; document.title = langNotificationsPluralize(notificationsCount); setFavicon('favicon_unread.ico'); } }, 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, soundReset: notificationSoundReset, getPeerSettings: getPeerSettings, getPeerMuted: getPeerMuted, savePeerSettings: savePeerSettings, updatePeerSettings: updatePeerSettings, updateNotifySettings: updateNotifySettings, getNotifySettings: getNotifySettings, getVibrateSupport: getVibrateSupport, testSound: playSound }; function updateNotifySettings () { Storage.get('notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview').then(function (updSettings) { settings.nodesktop = updSettings[0]; settings.volume = updSettings[1] === false ? 0.5 : updSettings[1]; settings.novibrate = updSettings[2]; settings.nopreview = updSettings[3]; }); } function getNotifySettings () { return settings; } function getPeerSettings (peerID) { if (peerSettings[peerID] !== undefined) { return peerSettings[peerID]; } return peerSettings[peerID] = MtpApiManager.invokeApi('account.getNotifySettings', { peer: { _: 'inputNotifyPeer', peer: AppPeersManager.getInputPeerByID(peerID) } }); } function setFavicon (href) { href = href || 'favicon.ico'; if (prevFavicon === href) { return } var link = document.createElement('link'); link.rel = 'shortcut icon'; link.type = 'image/x-icon'; link.href = href; faviconEl.parentNode.replaceChild(link, faviconEl); faviconEl = link; prevFavicon = href } 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 () { updateNotifySettings(); $rootScope.$on('settings_changed', updateNotifySettings); registerDevice(); if (!notificationsUiSupport) { return false; } if ('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') { $($window).on('click', requestPermission); } try { if ('onbeforeunload' in window) { $($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'; } else if (!data.image) { data.image = 'img/icons/icon60.png'; } notificationsCount++; var now = tsNow(); if (settings.volume > 0 && ( !data.tag || !soundsPlayed[data.tag] || now > soundsPlayed[data.tag] + 60000 ) ) { playSound(settings.volume); soundsPlayed[data.tag] = now; } if (!notificationsUiSupport || 'Notification' in window && Notification.permission !== 'granted') { return false; } if (settings.nodesktop) { if (vibrateSupport && !settings.novibrate) { 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 if (notificationsMsSiteMode) { window.external.msSiteModeClearIconOverlay(); window.external.msSiteModeSetIconOverlay('img/icons/icon16.png', data.title); window.external.msSiteModeActivate(); notification = { index: idx }; } else { return; } notification.onclick = function () { notification.close(); AppRuntimeManager.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 now = tsNow(); if (nextSoundAt && now < nextSoundAt && prevSoundVolume == volume) { return; } nextSoundAt = now + 1000; prevSoundVolume = volume; var filename = 'img/sound_a.mp3'; 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(); } else if (notificationsMsSiteMode && notification.index == notificationIndex) { window.external.msSiteModeClearIconOverlay(); } } catch (e) {} delete notificationsCount[key]; } } function notificationSoundReset (tag) { delete soundsPlayed[tag]; } function notificationsClear() { if (notificationsMsSiteMode) { window.external.msSiteModeClearIconOverlay(); } else { 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('PasswordManager', function ($timeout, $q, $rootScope, MtpApiManager, CryptoWorker, MtpSecureRandom) { return { check: check, getState: getState, requestRecovery: requestRecovery, recover: recover, updateSettings: updateSettings }; function getState (options) { return MtpApiManager.invokeApi('account.getPassword', {}, options).then(function (result) { return result; }); } function updateSettings (state, settings) { var currentHashPromise; var newHashPromise; var params = { new_settings: { _: 'account.passwordInputSettings', flags: 0, hint: settings.hint || '' } }; if (typeof settings.cur_password === 'string' && settings.cur_password.length > 0) { currentHashPromise = makePasswordHash(state.current_salt, settings.cur_password); } else { currentHashPromise = $q.when([]); } if (typeof settings.new_password === 'string' && settings.new_password.length > 0) { var saltRandom = new Array(8); var newSalt = bufferConcat(state.new_salt, saltRandom); MtpSecureRandom.nextBytes(saltRandom); newHashPromise = makePasswordHash(newSalt, settings.new_password); params.new_settings.new_salt = newSalt; params.new_settings.flags |= 1; } else { if (typeof settings.new_password === 'string') { params.new_settings.flags |= 1; params.new_settings.new_salt = []; } newHashPromise = $q.when([]); } if (typeof settings.email === 'string') { params.new_settings.flags |= 2; params.new_settings.email = settings.email || ''; } return $q.all([currentHashPromise, newHashPromise]).then(function (hashes) { params.current_password_hash = hashes[0]; params.new_settings.new_password_hash = hashes[1]; return MtpApiManager.invokeApi('account.updatePasswordSettings', params); }); } function check (state, password, options) { return makePasswordHash(state.current_salt, password).then(function (passwordHash) { return MtpApiManager.invokeApi('auth.checkPassword', { password_hash: passwordHash }, options); }); } function requestRecovery (state, options) { return MtpApiManager.invokeApi('auth.requestPasswordRecovery', {}, options); } function recover (code, options) { return MtpApiManager.invokeApi('auth.recoverPassword', { code: code }, options); } function makePasswordHash (salt, password) { var passwordUTF8 = unescape(encodeURIComponent(password)); var buffer = new ArrayBuffer(passwordUTF8.length); var byteView = new Uint8Array(buffer); for (var i = 0, len = passwordUTF8.length; i < len; i++) { byteView[i] = passwordUTF8.charCodeAt(i); } buffer = bufferConcat(bufferConcat(salt, byteView), salt); return CryptoWorker.sha256Hash(buffer); } }) .service('ErrorService', function ($rootScope, $modal, $window) { var shownBoxes = 0; function show (params, options) { if (shownBoxes >= 1) { 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(); scope.multiSelect = false; scope.noMessages = true; if (options) { angular.extend(scope, options); } return $modal.open({ templateUrl: templateUrl('peer_select'), controller: 'PeerSelectController', scope: scope, windowClass: 'peer_select_window mobile_modal', backdrop: 'single' }).result; } function selectPeers (options) { if (Config.Mobile) { return selectPeer(options).then(function (peerString) { return [peerString]; }); } var scope = $rootScope.$new(); scope.multiSelect = true; scope.noMessages = true; if (options) { angular.extend(scope, options); } return $modal.open({ templateUrl: templateUrl('peer_select'), controller: 'PeerSelectController', scope: scope, windowClass: 'peer_select_window mobile_modal', backdrop: 'single' }).result; } return { selectPeer: selectPeer, selectPeers: selectPeers } }) .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'; } return $modal.open({ templateUrl: templateUrl('contacts_modal'), controller: 'ContactsModalController', scope: scope, windowClass: 'contacts_modal_window mobile_modal', backdrop: 'single' }).result; } return { selectContacts: function (options) { return select (true, options); }, selectContact: function (options) { return select (false, options); } } }) .service('ChangelogNotifyService', function (Storage, $rootScope, $modal) { 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; $modal.open({ controller: 'ChangelogModalController', templateUrl: templateUrl('changelog_modal'), scope: $scope, windowClass: 'changelog_modal_window mobile_modal' }); } return { checkUpdate: checkUpdate, showChangelog: showChangelog } }) .service('HttpsMigrateService', function (ErrorService, Storage) { var started = false; function check () { Storage.get('https_dismiss').then(function (ts) { if (!ts || tsNow() > ts + 43200000) { ErrorService.confirm({ type: 'MIGRATE_TO_HTTPS' }).then(function () { var popup; try { popup = window.open('https://web.telegram.org', '_blank'); } catch (e) {} if (!popup) { location = 'https://web.telegram.org'; } }, function () { Storage.set({https_dismiss: tsNow()}); }); } }); } function start () { if (started || location.protocol != 'http:' || Config.Modes.http || Config.App.domains.indexOf(location.hostname) == -1) { return; } started = true; setTimeout(check, 120000); } return { start: start, check: check } }) .service('LayoutSwitchService', function (ErrorService, Storage, AppRuntimeManager, $window) { var started = false; var confirmShown = false; function switchLayout(mobile) { ConfigStorage.noPrefix(); Storage.set({ layout_selected: mobile ? 'mobile' : 'desktop', layout_width: $(window).width() }).then(function () { AppRuntimeManager.reload(); }); } function layoutCheck (e) { if (confirmShown) { return; } var width = $(window).width(); var newMobile = width < 600; if (!width || !e && (Config.Navigator.mobile ? width <= 800 : newMobile)) { return; } if (newMobile != Config.Mobile) { ConfigStorage.noPrefix(); Storage.get('layout_width').then(function (confirmedWidth) { if (width == confirmedWidth) { return false; } confirmShown = true; ErrorService.confirm({ type: newMobile ? 'SWITCH_MOBILE_VERSION' : 'SWITCH_DESKTOP_VERSION' }).then(function () { switchLayout(newMobile); }, function () { ConfigStorage.noPrefix(); Storage.set({layout_width: width}); confirmShown = false; }); }); } } function start () { if (started || Config.Navigator.mobile) { return; } started = true; layoutCheck(); $($window).on('resize', layoutCheck); } return { start: start, switchLayout: switchLayout } }) .service('TelegramMeWebService', function (Storage) { var disabled = Config.Modes.test || Config.App.domains.indexOf(location.hostname) == -1 || location.protocol != 'http:' && location.protocol != 'https:' || location.protocol == 'https:' && location.hostname != 'web.telegram.org'; function sendAsyncRequest (canRedirect) { if (disabled) { return false; } Storage.get('tgme_sync').then(function (curValue) { var ts = tsNow(true); if (canRedirect && curValue && curValue.canRedirect == canRedirect && curValue.ts + 86400 > ts) { return false; } Storage.set({tgme_sync: {canRedirect: canRedirect, ts: ts}}); var script = $('