/*! * Webogram v0.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' /* global Config, location, templateUrl, onContentLoaded, tsNow, cancelEvent, safeReplaceObject, dT, SearchIndexManager, setZeroTimeout, versionCompare, calcImageInBox, getSelectedText, SVGElement, hasOnclick */ /* Controllers */ angular.module('myApp.controllers', ['myApp.i18n']) .controller('AppWelcomeController', function ($scope, $location, MtpApiManager, ChangelogNotifyService, LayoutSwitchService) { MtpApiManager.getUserID().then(function (id) { if (id) { $location.url('/im') return } if (location.protocol == 'http:' && !Config.Modes.http && Config.App.domains.indexOf(location.hostname) != -1) { location.href = location.href.replace(/^http:/, 'https:') return } $location.url('/login') }) ChangelogNotifyService.checkUpdate() LayoutSwitchService.start() }) .controller('AppLoginController', function ($scope, $rootScope, $location, $timeout, $modal, $modalStack, MtpApiManager, ErrorService, NotificationsManager, PasswordManager, ChangelogNotifyService, IdleManager, LayoutSwitchService, WebPushApiManager, TelegramMeWebService, _) { $modalStack.dismissAll() IdleManager.start() MtpApiManager.getUserID().then(function (id) { if (id) { $location.url('/im') return } if (location.protocol == 'http:' && !Config.Modes.http && Config.App.domains.indexOf(location.hostname) != -1) { location.href = location.href.replace(/^http:/, 'https:') return } TelegramMeWebService.setAuthorized(false) WebPushApiManager.forceUnsubscribe() }) var options = {dcID: 2, createNetworker: true} var countryChanged = false var selectedCountry = false $scope.credentials = {phone_country: '', phone_country_name: '', phone_number: '', phone_full: ''} $scope.progress = {} $scope.nextPending = {} $scope.about = {} $scope.chooseCountry = function () { var modal = $modal.open({ templateUrl: templateUrl('country_select_modal'), controller: 'CountrySelectModalController', windowClass: 'countries_modal_window mobile_modal', backdrop: 'single' }) modal.result.then(selectCountry) } function initPhoneCountry () { var langCode = (navigator.language || '').toLowerCase() var countryIso2 = Config.LangCountries[langCode] var shouldPregenerate = !Config.Navigator.mobile if (['en', 'en-us', 'en-uk'].indexOf(langCode) == -1) { if (countryIso2 !== undefined) { selectPhoneCountryByIso2(countryIso2) } else if (langCode.indexOf('-') > 0) { selectPhoneCountryByIso2(langCode.split('-')[1].toUpperCase()) } else { selectPhoneCountryByIso2('US') } } else { selectPhoneCountryByIso2('US') } if (!shouldPregenerate) { return } var wasCountry = $scope.credentials.phone_country MtpApiManager.invokeApi('help.getNearestDc', {}, {dcID: 2, createNetworker: true}).then(function (nearestDcResult) { if (wasCountry == $scope.credentials.phone_country) { selectPhoneCountryByIso2(nearestDcResult.country) } if (nearestDcResult.nearest_dc != nearestDcResult.this_dc) { MtpApiManager.getNetworker(nearestDcResult.nearest_dc, {createNetworker: true}) } }) } function selectPhoneCountryByIso2 (countryIso2) { if (countryIso2) { var i, country for (i = 0; i < Config.CountryCodes.length; i++) { country = Config.CountryCodes[i] if (country[0] == countryIso2) { return selectCountry({name: _(country[1] + '_raw'), code: country[2]}) } } } return selectCountry({name: _('country_select_modal_country_us_raw'), code: '+1'}) } function selectCountry (country) { selectedCountry = country if ($scope.credentials.phone_country != country.code) { $scope.credentials.phone_country = country.code } else { updateCountry() } $scope.$broadcast('country_selected') $scope.$broadcast('value_updated') } function updateCountry () { var phoneNumber = ( ($scope.credentials.phone_country || '') + ($scope.credentials.phone_number || '') ).replace(/\D+/g, '') var i, j, code var maxLength = 0 var maxName = false if (phoneNumber.length) { if (selectedCountry && !phoneNumber.indexOf(selectedCountry.code.replace(/\D+/g, ''))) { maxName = selectedCountry.name } else { for (i = 0; i < Config.CountryCodes.length; i++) { for (j = 2; j < Config.CountryCodes[i].length; j++) { code = Config.CountryCodes[i][j].replace(/\D+/g, '') if (code.length > maxLength && !phoneNumber.indexOf(code)) { maxLength = code.length maxName = _(Config.CountryCodes[i][1] + '_raw') } } } } } $scope.credentials.phone_full = phoneNumber $scope.credentials.phone_country_name = maxName || _('login_controller_unknown_country_raw') } $scope.$watch('credentials.phone_country', updateCountry) $scope.$watch('credentials.phone_number', updateCountry) initPhoneCountry() var nextTimeout var updatePasswordTimeout = false function saveAuth (result) { MtpApiManager.setUserAuth(options.dcID, { id: result.user.id }) $timeout.cancel(nextTimeout) $location.url('/im') } $scope.sendCode = function () { $timeout.cancel(nextTimeout) var fullPhone = ($scope.credentials.phone_country || '') + ($scope.credentials.phone_number || '') var badPhone = !fullPhone.match(/^[\d\-+\s]+$/) if (!badPhone) { fullPhone = fullPhone.replace(/\D/g, '') if (fullPhone.length < 7) { badPhone = true } } if (badPhone) { $scope.progress.enabled = false $scope.error = {field: 'phone'} return } ErrorService.confirm({ type: 'LOGIN_PHONE_CORRECT', country_code: $scope.credentials.phone_country, phone_number: $scope.credentials.phone_number }).then(function () { $scope.progress.enabled = true onContentLoaded(function () { $scope.$broadcast('ui_height') }) var authKeyStarted = tsNow() MtpApiManager.invokeApi('auth.sendCode', { flags: 0, phone_number: $scope.credentials.phone_full, api_id: Config.App.id, api_hash: Config.App.hash, lang_code: navigator.language || 'en' }, options).then(function (sentCode) { $scope.progress.enabled = false $scope.error = {} $scope.about = {} $scope.credentials.phone_code_hash = sentCode.phone_code_hash applySentCode(sentCode) }, function (error) { $scope.progress.enabled = false console.log('sendCode error', error) switch (error.type) { case 'PHONE_NUMBER_INVALID': $scope.error = {field: 'phone'} error.handled = true break case 'PHONE_NUMBER_APP_SIGNUP_FORBIDDEN': $scope.error = {field: 'phone'} break } })['finally'](function () { if ($rootScope.idle.isIDLE || tsNow() - authKeyStarted > 60000) { NotificationsManager.notify({ title: 'Telegram', message: 'Your authorization key was successfully generated! Open the app to log in.', tag: 'auth_key' }) } }) }) } function applySentCode (sentCode) { $scope.credentials.type = sentCode.type $scope.nextPending.type = sentCode.next_type || false $scope.nextPending.remaining = sentCode.timeout || false delete $scope.nextPending.progress nextTimeoutCheck() onContentLoaded(function () { $scope.$broadcast('ui_height') }) } $scope.sendNext = function () { if (!$scope.nextPending.type || $scope.nextPending.remaining > 0) { return } $scope.nextPending.progress = true MtpApiManager.invokeApi('auth.resendCode', { phone_number: $scope.credentials.phone_full, phone_code_hash: $scope.credentials.phone_code_hash }, options).then(applySentCode) } function nextTimeoutCheck () { $timeout.cancel(nextTimeout) if (!$scope.nextPending.type || $scope.nextPending.remaining === false) { return } if ((--$scope.nextPending.remaining) > 0) { nextTimeout = $timeout(nextTimeoutCheck, 1000) } } $scope.editPhone = function () { $timeout.cancel(nextTimeout) if ($scope.credentials.phone_full && $scope.credentials.phone_code_hash) { MtpApiManager.invokeApi('auth.cancelCode', { phone_number: $scope.credentials.phone_full, phone_code_hash: $scope.credentials.phone_code_hash }, options) } delete $scope.credentials.phone_code_hash delete $scope.credentials.phone_unoccupied delete $scope.credentials.phone_code_valid delete $scope.nextPending.remaining } $scope.$watch('credentials.phone_code', function (newVal) { if (newVal && newVal.match(/^\d+$/) && $scope.credentials.type && $scope.credentials.type.length && newVal.length == $scope.credentials.type.length) { $scope.logIn() } }) $scope.logIn = function (forceSignUp) { if ($scope.progress.enabled && $scope.progress.forceSignUp == forceSignUp) { return } var method = 'auth.signIn' var params = { phone_number: $scope.credentials.phone_full, phone_code_hash: $scope.credentials.phone_code_hash, phone_code: $scope.credentials.phone_code } if (forceSignUp) { method = 'auth.signUp' angular.extend(params, { first_name: $scope.credentials.first_name || '', last_name: $scope.credentials.last_name || '' }) } $scope.progress.forceSignUp = forceSignUp $scope.progress.enabled = true MtpApiManager.invokeApi(method, params, options).then(saveAuth, function (error) { $scope.progress.enabled = false if (error.code == 400 && error.type == 'PHONE_NUMBER_UNOCCUPIED') { error.handled = true $scope.credentials.phone_code_valid = true $scope.credentials.phone_unoccupied = true $scope.about = {} return } else if (error.code == 400 && error.type == 'PHONE_NUMBER_OCCUPIED') { error.handled = true return $scope.logIn(false) } else if (error.code == 401 && error.type == 'SESSION_PASSWORD_NEEDED') { $scope.progress.enabled = true updatePasswordState().then(function () { $scope.progress.enabled = false $scope.credentials.phone_code_valid = true $scope.credentials.password_needed = true $scope.about = {} }) error.handled = true return } switch (error.type) { case 'FIRSTNAME_INVALID': $scope.error = {field: 'first_name'} error.handled = true break case 'LASTNAME_INVALID': $scope.error = {field: 'last_name'} error.handled = true break case 'PHONE_CODE_INVALID': $scope.error = {field: 'phone_code'} delete $scope.credentials.phone_code_valid error.handled = true break case 'PHONE_CODE_EXPIRED': $scope.editPhone() error.handled = true break } }) } $scope.checkPassword = function () { $scope.progress.enabled = true return PasswordManager.check($scope.password, $scope.credentials.password, options).then(saveAuth, function (error) { $scope.progress.enabled = false switch (error.type) { case 'PASSWORD_HASH_INVALID': $scope.error = {field: 'password'} error.handled = true break } }) } $scope.forgotPassword = function (event) { PasswordManager.requestRecovery($scope.password, options).then(function (emailRecovery) { var scope = $rootScope.$new() scope.recovery = emailRecovery scope.options = options var modal = $modal.open({ scope: scope, templateUrl: templateUrl('password_recovery_modal'), controller: 'PasswordRecoveryModalController', windowClass: 'md_simple_modal_window mobile_modal' }) modal.result.then(function (result) { if (result && result.user) { saveAuth(result) } else { $scope.canReset = true } }) }, function (error) { switch (error.type) { case 'PASSWORD_EMPTY': $scope.logIn() error.handled = true break case 'PASSWORD_RECOVERY_NA': $timeout(function () { $scope.canReset = true }, 1000) error.handled = true break } }) return cancelEvent(event) } $scope.resetAccount = function () { ErrorService.confirm({ type: 'RESET_ACCOUNT' }).then(function () { $scope.progress.enabled = true MtpApiManager.invokeApi('account.deleteAccount', { reason: 'Forgot password' }, options).then(function () { delete $scope.progress.enabled delete $scope.credentials.password_needed $scope.credentials.phone_unoccupied = true }, function (error) { if (error.type && error.type.substr(0, 17) == '2FA_CONFIRM_WAIT_') { error.waitTime = error.type.substr(17) error.type = '2FA_CONFIRM_WAIT_TIME' } delete $scope.progress.enabled }) }) } function updatePasswordState () { // $timeout.cancel(updatePasswordTimeout) // updatePasswordTimeout = false return PasswordManager.getState(options).then(function (result) { return $scope.password = result // if (result._ == 'account.noPassword' && result.email_unconfirmed_pattern) { // updatePasswordTimeout = $timeout(updatePasswordState, 5000) // } }) } ChangelogNotifyService.checkUpdate() LayoutSwitchService.start() }) .controller('AppIMController', function ($q, qSync, $scope, $location, $routeParams, $modal, $rootScope, $modalStack, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ContactsSelectService, ChangelogNotifyService, ErrorService, AppRuntimeManager, HttpsMigrateService, LayoutSwitchService, LocationParamsService, AppStickersManager) { $scope.$on('$routeUpdate', updateCurDialog) var pendingParams = false var pendingAttachment = false $scope.$on('history_focus', function (e, peerData) { if (peerData.peerString == $scope.curDialog.peer && (peerData.messageID ? peerData.messageID == $scope.curDialog.messageID : !$scope.curDialog.messageID) && !peerData.startParam && !peerData.attachment) { if (peerData.messageID) { $scope.$broadcast('ui_history_change_scroll', true) } else { $scope.$broadcast('ui_history_focus') } $modalStack.dismissAll() } else { var peerID = AppPeersManager.getPeerID(peerData.peerString) var username = AppPeersManager.getPeer(peerID).username var peer = username ? '@' + username : peerData.peerString if (peerData.messageID || peerData.startParam) { pendingParams = { messageID: peerData.messageID, startParam: peerData.startParam } } else { pendingParams = false } if (peerData.attachment) { pendingAttachment = peerData.attachment } if ($routeParams.p != peer) { $location.url('/im?p=' + peer) } else { updateCurDialog() } } }) $scope.$on('esc_no_more', function () { $rootScope.$apply(function () { $location.url('/im') }) }) $scope.isLoggedIn = true $scope.isEmpty = {} $scope.search = {} $scope.historyFilter = {mediaType: false} $scope.historyPeer = {} $scope.historyState = { selectActions: false, botActions: false, channelActions: false, canReply: false, canDelete: false, canEdit: false, actions: function () { return $scope.historyState.selectActions ? 'selected' : ($scope.historyState.botActions ? 'bot' : ($scope.historyState.channelActions ? 'channel' : false)) }, typing: [], missedCount: 0, skipped: false } $scope.openSettings = function () { $modal.open({ templateUrl: templateUrl('settings_modal'), controller: 'SettingsModalController', windowClass: 'settings_modal_window mobile_modal', backdrop: 'single' }) } // setTimeout($scope.openSettings, 1000) $scope.openFaq = function () { var url = 'https://telegram.org/faq' switch (Config.I18n.locale) { case 'es-es': url += '/es' break case 'it-it': url += '/it' break case 'de-de': url += '/de' break case 'ko-ko': url += '/ko' break case 'pt-br': url += '/br' break } var popup = window.open(url, '_blank') try { popup.opener = null; } catch (e) {} } $scope.openContacts = function () { ContactsSelectService.selectContact().then(function (userID) { $scope.dialogSelect(AppUsersManager.getUserString(userID)) }) } $scope.openGroup = function () { ContactsSelectService.selectContacts({action: 'new_group'}).then(function (userIDs) { if (userIDs.length == 1) { $scope.dialogSelect(AppUsersManager.getUserString(userIDs[0])) } else if (userIDs.length > 1) { var scope = $rootScope.$new() scope.userIDs = userIDs $modal.open({ templateUrl: templateUrl('chat_create_modal'), controller: 'ChatCreateModalController', scope: scope, windowClass: 'md_simple_modal_window mobile_modal', backdrop: 'single' }) } }) } $scope.importContact = function () { AppUsersManager.openImportContact().then(function (foundContact) { if (foundContact) { $rootScope.$broadcast('history_focus', { peerString: AppUsersManager.getUserString(foundContact) }) } }) } $scope.searchClear = function () { $scope.search.query = '' $scope.$broadcast('search_clear') } $scope.dialogSelect = function (peerString, messageID) { var params = {peerString: peerString} if (messageID) { params.messageID = messageID } else if ($scope.search.query) { $scope.searchClear() } var peerID = AppPeersManager.getPeerID(peerString) var converted = AppMessagesManager.convertMigratedPeer(peerID) if (converted) { params.peerString = AppPeersManager.getPeerString(converted) } $rootScope.$broadcast('history_focus', params) } $scope.logOut = function () { ErrorService.confirm({type: 'LOGOUT'}).then(function () { MtpApiManager.logOut().then(function () { location.hash = '/login' AppRuntimeManager.reload() }) }) } $scope.openChangelog = function () { ChangelogNotifyService.showChangelog(false) } $scope.showPeerInfo = function () { if ($scope.curDialog.peerID > 0) { AppUsersManager.openUser($scope.curDialog.peerID) } else if ($scope.curDialog.peerID < 0) { AppChatsManager.openChat(-$scope.curDialog.peerID) } } $scope.toggleEdit = function () { $scope.$broadcast('history_edit_toggle') } $scope.selectedFlush = function () { $scope.$broadcast('history_edit_flush') } $scope.toggleMedia = function (mediaType) { $scope.$broadcast('history_media_toggle', mediaType) } $scope.returnToRecent = function () { $scope.$broadcast('history_return_recent') } $scope.toggleSearch = function () { $scope.$broadcast('dialogs_search_toggle') } updateCurDialog() function updateCurDialog () { $modalStack.dismissAll() var addParams = pendingParams || {} pendingParams = false addParams.messageID = parseInt(addParams.messageID) || false addParams.startParam = addParams.startParam var peerStringPromise if ($routeParams.p && $routeParams.p.charAt(0) == '@') { if ($scope.curDialog === undefined) { $scope.curDialog = { peer: '', peerID: 0 } } peerStringPromise = AppPeersManager.resolveUsername($routeParams.p.substr(1)).then(function (peerID) { return qSync.when(AppPeersManager.getPeerString(peerID)) }) } else { peerStringPromise = qSync.when($routeParams.p) } peerStringPromise.then(function (peerString) { $scope.curDialog = angular.extend({ peer: peerString, peerID: AppPeersManager.getPeerID(peerString || '') }, addParams) if (pendingAttachment) { $scope.$broadcast('peer_draft_attachment', pendingAttachment) pendingAttachment = false } }) } ChangelogNotifyService.checkUpdate() HttpsMigrateService.start() LayoutSwitchService.start() LocationParamsService.start() AppStickersManager.start() }) .controller('AppImDialogsController', function ($scope, $location, $q, $timeout, $routeParams, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppProfileManager, AppPeersManager, PhonebookContactsService, ErrorService, AppRuntimeManager) { $scope.dialogs = [] $scope.myResults = [] $scope.foundPeers = [] $scope.foundMessages = [] if ($scope.search === undefined) { $scope.search = {} } if ($scope.isEmpty === undefined) { $scope.isEmpty = {} } $scope.phonebookAvailable = PhonebookContactsService.isAvailable() var searchMessages = false var offsetIndex = 0 var maxID = 0 var hasMore = false var jump = 0 var contactsJump = 0 var peersInDialogs = {} var typingTimeouts = {} var contactsShown $scope.$on('dialogs_need_more', function () { // console.log('on need more') showMoreDialogs() }) $scope.$on('dialog_unread', function (e, dialog) { angular.forEach($scope.dialogs, function (curDialog) { if (curDialog.peerID == dialog.peerID) { curDialog.unreadCount = dialog.count } }) }) $scope.$on('history_search', function (e, peerID) { $scope.setSearchPeer(peerID) }) $scope.$on('esc_no_more', function () { $scope.setSearchPeer(false) }) $scope.$on('dialogs_multiupdate', function (e, dialogsUpdated) { if (searchMessages) { return false } if ($scope.search.query !== undefined && $scope.search.query.length) { return false } var i var dialog var newPeer = false var len = $scope.dialogs.length for (i = 0; i < len; i++) { dialog = $scope.dialogs[i] if (dialogsUpdated[dialog.peerID]) { $scope.dialogs.splice(i, 1) i-- len-- AppMessagesManager.clearDialogCache(dialog.mid) } } angular.forEach(dialogsUpdated, function (dialog, peerID) { if ($scope.noUsers && peerID > 0) { return } if (!peersInDialogs[peerID]) { peersInDialogs[peerID] = true newPeer = true } $scope.dialogs.unshift( AppMessagesManager.wrapForDialog(dialog.top_message, dialog) ) }) sortDialogs() if (newPeer) { delete $scope.isEmpty.dialogs if (contactsShown) { showMoreConversations() } } }) function deleteDialog (peerID) { for (var i = 0; i < $scope.dialogs.length; i++) { if ($scope.dialogs[i].peerID == peerID) { $scope.dialogs.splice(i, 1) break } } } function sortDialogs () { var myID = false if ($scope.forPeerSelect) { myID = AppUsersManager.getSelf().id } $scope.dialogs.sort(function (d1, d2) { if (d1.peerID == myID) { return -1 } else if (d2.peerID == myID) { return 1 } return d2.index - d1.index }) } $scope.$on('dialog_top', function (e, dialog) { var curDialog, i, wrappedDialog var len = $scope.dialogs.length for (i = 0; i < len; i++) { curDialog = $scope.dialogs[i] if (curDialog.peerID == dialog.peerID) { wrappedDialog = AppMessagesManager.wrapForDialog(dialog.top_message, dialog) $scope.dialogs.splice(i, 1, wrappedDialog) break } } sortDialogs() if (wrappedDialog == $scope.dialogs[len - 1]) { $scope.dialogs.splice(len - 1, 1) } }) $scope.$on('dialog_flush', function (e, update) { var curDialog, i for (i = 0; i < $scope.dialogs.length; i++) { curDialog = $scope.dialogs[i] if (curDialog.peerID == update.peerID) { curDialog.deleted = true break } } }) $scope.$on('dialog_drop', function (e, dialog) { deleteDialog(dialog.peerID) }) $scope.$on('dialog_draft', function (e, draftUpdate) { var curDialog, i for (i = 0; i < $scope.dialogs.length; i++) { curDialog = $scope.dialogs[i] if (curDialog.peerID == draftUpdate.peerID) { curDialog.draft = draftUpdate.draft if (draftUpdate.index) { curDialog.index = draftUpdate.index } sortDialogs() break } } }) $scope.$on('history_delete', function (e, historyUpdate) { for (var i = 0; i < $scope.dialogs.length; i++) { if ($scope.dialogs[i].peerID == historyUpdate.peerID) { if (historyUpdate.msgs[$scope.dialogs[i].mid]) { $scope.dialogs[i].deleted = true } break } } }) $scope.$on('apiUpdate', function (e, update) { switch (update._) { case 'updateUserTyping': case 'updateChatUserTyping': if (!AppUsersManager.hasUser(update.user_id)) { if (update.chat_id && AppChatsManager.hasChat(update.chat_id) && !AppChatsManager.isChannel(update.chat_id)) { AppProfileManager.getChatFull(update.chat_id) } return } var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id AppUsersManager.forceUserOnline(update.user_id) for (var i = 0; i < $scope.dialogs.length; i++) { if ($scope.dialogs[i].peerID == peerID) { $scope.dialogs[i].typing = update.user_id $timeout.cancel(typingTimeouts[peerID]) typingTimeouts[peerID] = $timeout(function () { for (var i = 0; i < $scope.dialogs.length; i++) { if ($scope.dialogs[i].peerID == peerID) { if ($scope.dialogs[i].typing == update.user_id) { delete $scope.dialogs[i].typing } } } }, 6000) break } } break } }) $scope.$watchCollection('search', function () { $scope.dialogs = [] $scope.foundMessages = [] searchMessages = !!$scope.searchPeer contactsJump++ loadDialogs() }) if (Config.Mobile) { $scope.$watch('curDialog.peer', function () { $scope.$broadcast('ui_dialogs_update') }) } $scope.importPhonebook = function () { PhonebookContactsService.openPhonebookImport() } $scope.setSearchPeer = function (peerID) { $scope.searchPeer = peerID || false $scope.searchClear() if (peerID) { $scope.dialogs = [] $scope.foundPeers = [] searchMessages = true $scope.toggleSearch() } else { searchMessages = false } loadDialogs(true) } $scope.$on('contacts_update', function () { if (contactsShown) { showMoreConversations() } }) $scope.$on('ui_dialogs_search_clear', $scope.searchClear) if (!$scope.noMessages) { $scope.$on('dialogs_search', function (e, data) { $scope.search.query = data.query || '' $scope.toggleSearch() }) } var searchTimeoutPromise function getDialogs (force) { var curJump = ++jump $timeout.cancel(searchTimeoutPromise) if (searchMessages) { searchTimeoutPromise = (force || maxID) ? $q.when() : $timeout(angular.noop, 500) return searchTimeoutPromise.then(function () { var searchPeerID = $scope.searchPeer || false return AppMessagesManager.getSearch(searchPeerID, $scope.search.query, {_: 'inputMessagesFilterEmpty'}, maxID).then(function (result) { if (curJump != jump) { return $q.reject() } var dialogs = [] angular.forEach(result.history, function (messageID) { var message = AppMessagesManager.getMessage(messageID) var peerID = AppMessagesManager.getMessagePeer(message) dialogs.push({ peerID: peerID, top_message: messageID, unread_count: -1 }) }) return { dialogs: dialogs } }) }) } var query = $scope.search.query || '' if ($scope.noUsers) { query = '%pg ' + query } return AppMessagesManager.getConversations(query, offsetIndex).then(function (result) { if (curJump != jump) { return $q.reject() } if (!query && !offsetIndex && $scope.forPeerSelect) { var myID = AppUsersManager.getSelf().id return AppMessagesManager.getConversation(myID).then(function (dialog) { result.dialogs.unshift(dialog) return result }) } return result }) } function loadDialogs (force) { offsetIndex = 0 maxID = 0 hasMore = false if (!searchMessages) { peersInDialogs = {} contactsShown = false } getDialogs(force).then(function (dialogsResult) { if (!searchMessages) { $scope.dialogs = [] $scope.myResults = [] $scope.foundPeers = [] } $scope.foundMessages = [] var dialogsList = searchMessages ? $scope.foundMessages : $scope.dialogs if (dialogsResult.dialogs.length) { angular.forEach(dialogsResult.dialogs, function (dialog) { if ($scope.canSend && AppPeersManager.isChannel(dialog.peerID) && !AppChatsManager.hasRights(-dialog.peerID, 'send')) { return } var wrapDialog = searchMessages ? undefined : dialog var wrappedDialog = AppMessagesManager.wrapForDialog(dialog.top_message, wrapDialog) if (searchMessages && $scope.searchPeer) { var message = AppMessagesManager.getMessage(dialog.top_message) if (message.fromID > 0) { wrappedDialog.peerID = message.fromID } } if (searchMessages) { wrappedDialog.unreadCount = -1 } else { if (peersInDialogs[dialog.peerID]) { return } else { peersInDialogs[dialog.peerID] = true } } dialogsList.push(wrappedDialog) }) if (searchMessages) { maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message } else { offsetIndex = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].index delete $scope.isEmpty.dialogs } hasMore = true } else { hasMore = false } $scope.$broadcast('ui_dialogs_change') if (!$scope.search.query) { AppMessagesManager.getConversations('', offsetIndex, 100) if (!dialogsResult.dialogs.length) { $scope.isEmpty.dialogs = true showMoreDialogs() } } else { showMoreDialogs() } }) } function showMoreDialogs () { if (contactsShown && (!hasMore || (!offsetIndex && !maxID))) { return } if (!hasMore && !searchMessages && !$scope.noUsers && ($scope.search.query || !$scope.dialogs.length)) { showMoreConversations() return } getDialogs().then(function (dialogsResult) { if (dialogsResult.dialogs.length) { var dialogsList = searchMessages ? $scope.foundMessages : $scope.dialogs angular.forEach(dialogsResult.dialogs, function (dialog) { if ($scope.canSend && AppPeersManager.isChannel(dialog.peerID) && !AppChatsManager.hasRights(-dialog.peerID, 'send')) { return } var wrapDialog = searchMessages ? undefined : dialog var wrappedDialog = AppMessagesManager.wrapForDialog(dialog.top_message, wrapDialog) if (searchMessages) { wrappedDialog.unreadCount = -1 } else { if (peersInDialogs[dialog.peerID]) { return } else { peersInDialogs[dialog.peerID] = true } } if (searchMessages && $scope.searchPeer) { var message = AppMessagesManager.getMessage(dialog.top_message) if (message.fromID > 0) { wrappedDialog.peerID = message.fromID } } dialogsList.push(wrappedDialog) }) if (searchMessages) { maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message } else { offsetIndex = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].index } $scope.$broadcast('ui_dialogs_append') hasMore = true } else { hasMore = false } }) } function showMoreConversations () { contactsShown = true var curJump = ++contactsJump AppUsersManager.getContacts($scope.search.query).then(function (contactsList) { if (curJump != contactsJump) return $scope.myResults = [] angular.forEach(contactsList, function (userID) { if (peersInDialogs[userID] === undefined) { $scope.myResults.push({ id: userID, peerString: AppUsersManager.getUserString(userID) }) } }) if (contactsList.length) { delete $scope.isEmpty.contacts } else if (!$scope.search.query) { $scope.isEmpty.contacts = true } $scope.$broadcast('ui_dialogs_append') }) if ($scope.search.query && $scope.search.query.length >= 2) { $timeout(function () { if (curJump != contactsJump) return MtpApiManager.invokeApi('contacts.search', {q: $scope.search.query, limit: 10}).then(function (result) { AppUsersManager.saveApiUsers(result.users) AppChatsManager.saveApiChats(result.chats) if (curJump != contactsJump) return var alreadyPeers = [] angular.forEach($scope.myResults, function (peerFound) { alreadyPeers.push(peerFound.id) }) angular.forEach(result.my_results, function (peerFound) { var peerID = AppPeersManager.getPeerID(peerFound) if (peersInDialogs[peerID] === undefined && alreadyPeers.indexOf(peerID) == -1) { alreadyPeers.push(peerID) if ($scope.canSend && AppPeersManager.isChannel(peerID) && !AppChatsManager.hasRights(-peerID, 'send')) { return } $scope.myResults.push({ id: peerID, peerString: AppPeersManager.getPeerString(peerID) }) } }) $scope.foundPeers = [] angular.forEach(result.results, function (peerFound) { var peerID = AppPeersManager.getPeerID(peerFound) if (peersInDialogs[peerID] === undefined && alreadyPeers.indexOf(peerID) == -1) { if ($scope.canSend && AppPeersManager.isChannel(peerID) && !AppChatsManager.hasRights(-peerID, 'send')) { return } alreadyPeers.push(peerID) $scope.foundPeers.push({ id: peerID, username: AppPeersManager.getPeer(peerID).username, peerString: AppUsersManager.getUserString(peerID) }) } }) }, function (error) { if (error.code == 400) { error.handled = true } }) }, 500) } if ($scope.search.query && !$scope.noMessages) { searchMessages = true loadDialogs() } } }) .controller('AppImHistoryController', function ($scope, $location, $timeout, $modal, $rootScope, toaster, _, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, PeersSelectService, IdleManager, StatusManager, NotificationsManager, ErrorService, GeoLocationManager) { $scope.$watchCollection('curDialog', applyDialogSelect) ApiUpdatesManager.attach() IdleManager.start() StatusManager.start() $scope.peerHistories = [] $scope.selectedMsgs = {} $scope.selectedCount = 0 $scope.historyState.selectActions = false $scope.historyState.botActions = false $scope.historyState.channelActions = false $scope.historyState.canDelete = false $scope.historyState.canReply = false $scope.historyState.missedCount = 0 $scope.historyState.skipped = false $scope.state = {} $scope.toggleMessage = toggleMessage $scope.selectedDelete = selectedDelete $scope.selectedForward = selectedForward $scope.selectedReply = selectedReply $scope.selectedEdit = selectedEdit $scope.selectedCancel = selectedCancel $scope.selectedFlush = selectedFlush $scope.selectInlineBot = selectInlineBot $scope.startBot = startBot $scope.cancelBot = cancelBot $scope.joinChannel = joinChannel $scope.togglePeerMuted = togglePeerMuted $scope.toggleEdit = toggleEdit $scope.toggleMedia = toggleMedia $scope.returnToRecent = returnToRecent $scope.$on('history_edit_toggle', toggleEdit) $scope.$on('history_edit_flush', selectedFlush) $scope.$on('history_media_toggle', function (e, mediaType) { toggleMedia(mediaType) }) $scope.$on('history_return_recent', returnToRecent) var peerID var peerHistory = false var unreadAfterIdle = false var hasMore = false var hasLess = false var maxID = 0 var minID = 0 var lastSelectID = false var inputMediaFilters = { photos: 'inputMessagesFilterPhotos', video: 'inputMessagesFilterVideo', documents: 'inputMessagesFilterDocument', audio: 'inputMessagesFilterVoice', round: 'inputMessagesFilterRoundVideo', music: 'inputMessagesFilterMusic', urls: 'inputMessagesFilterUrl', mentions: 'inputMessagesFilterMyMentions' } var jump = 0 var moreJump = 0 var moreActive = false var morePending = false var lessJump = 0 var lessActive = false var lessPending = false function applyDialogSelect (newDialog, oldDialog) { peerID = $rootScope.selectedPeerID = newDialog.peerID $scope.historyFilter.mediaType = false AppPeersManager.getInputPeer(newDialog.peer || $scope.curDialog.peer || '') updateBotActions() selectedCancel(true) if (oldDialog.peer && oldDialog.peer == newDialog.peer && newDialog.messageID) { messageFocusHistory() } else if (peerID) { updateHistoryPeer(true) loadHistory() } else { showEmptyHistory() } } function historiesQueuePush (peerID) { var pos = -1 var maxLen = 10 var i, history, diff for (i = 0; i < $scope.peerHistories.length; i++) { if ($scope.peerHistories[i].peerID == peerID) { pos = i break } } if (pos > -1) { history = $scope.peerHistories[pos] return history } history = {peerID: peerID, messages: [], ids: []} $scope.peerHistories.unshift(history) diff = $scope.peerHistories.length - maxLen if (diff > 0) { $scope.peerHistories.splice(maxLen - 1, diff) } return history } function historiesQueueFind (peerID) { var i for (i = 0; i < $scope.peerHistories.length; i++) { if ($scope.peerHistories[i].peerID == peerID) { return $scope.peerHistories[i] } } return false } function historiesQueuePop (peerID) { var i for (i = 0; i < $scope.peerHistories.length; i++) { if ($scope.peerHistories[i].peerID == peerID) { $scope.peerHistories.splice(i, 1) return true } } return false } function updateHistoryPeer (preload) { var peerData = AppPeersManager.getPeer(peerID) // console.log('update', preload, peerData) if (!peerData || peerData.deleted) { safeReplaceObject($scope.state, {loaded: false}) return false } peerHistory = historiesQueuePush(peerID) safeReplaceObject($scope.historyPeer, { id: peerID, data: peerData }) MtpApiManager.getUserID().then(function (myID) { $scope.ownID = myID }) if (preload) { $scope.historyState.typing.splice(0, $scope.historyState.typing.length) $scope.$broadcast('ui_peer_change') $scope.$broadcast('ui_history_change') safeReplaceObject($scope.state, {loaded: true, empty: !peerHistory.messages.length, mayBeHasMore: true}) updateBotActions() updateChannelActions() } } function updateBotActions () { var wasBotActions = $scope.historyState.botActions if (!peerID || peerID < 0 || !AppUsersManager.isBot(peerID) || $scope.historyFilter.mediaType || $scope.curDialog.messageID) { $scope.historyState.botActions = false } else if ( $scope.state.empty || ( peerHistory && peerHistory.messages.length == 1 && peerHistory.messages[0].action && peerHistory.messages[0].action._ == 'messageActionBotIntro' ) ) { $scope.historyState.botActions = 'start' } else if ($scope.curDialog.startParam) { $scope.historyState.botActions = 'param' } else { $scope.historyState.botActions = false } if (wasBotActions != $scope.historyState.botActions) { $scope.$broadcast('ui_panel_update') } } function updateChannelActions () { var wasChannelActions = $scope.historyState.channelActions var channel if (peerID && AppPeersManager.isChannel(peerID) && (channel = AppChatsManager.getChat(-peerID))) { var canSend = AppChatsManager.hasRights(-peerID, 'send') if (!canSend) { if (channel.pFlags.left) { $scope.historyState.channelActions = 'join' } else { if (!$scope.historyState.channelActions) { $scope.historyState.channelActions = 'mute' } NotificationsManager.getPeerMuted(peerID).then(function (muted) { $scope.historyState.channelActions = muted ? 'unmute' : 'mute' }) } } else { $scope.historyState.channelActions = false } $scope.historyState.canReply = canSend $scope.historyState.canDelete = canSend || channel.pFlags.moderator } else { $scope.historyState.channelActions = false $scope.historyState.canReply = true $scope.historyState.canDelete = true } if (wasChannelActions != $scope.historyState.channelActions) { $scope.$broadcast('ui_panel_update') } } function messageFocusHistory () { var history = historiesQueueFind(peerID) if (history && history.ids.indexOf($scope.curDialog.messageID) != -1) { $scope.historyUnread = {} var focusedMsgID = $scope.curDialog.messageID || 0 $scope.$broadcast('messages_focus', focusedMsgID) $scope.$broadcast('ui_history_change_scroll', true) } else { loadHistory() } } function showLessHistory () { if (!hasLess) { return } if (moreActive) { lessPending = true return } lessPending = false $scope.state.lessActive = lessActive = true var curJump = jump var curLessJump = ++lessJump var limit = 0 var backLimit = 20 AppMessagesManager.getHistory($scope.curDialog.peerID, minID, limit, backLimit).then(function (historyResult) { $scope.state.lessActive = lessActive = false if (curJump != jump || curLessJump != lessJump) return var i, id for (i = historyResult.history.length - 1; i >= 0; i--) { id = historyResult.history[i] if (id > minID) { peerHistory.messages.push(AppMessagesManager.wrapForHistory(id)) peerHistory.ids.push(id) } } if (historyResult.history.length) { minID = historyResult.history.length >= backLimit ? historyResult.history[0] : 0 if (AppMessagesManager.regroupWrappedHistory(peerHistory.messages, -backLimit)) { $scope.$broadcast('messages_regroup') } delete $scope.state.empty $scope.$broadcast('ui_history_append') } else { minID = 0 } $scope.historyState.skipped = hasLess = minID > 0 if (morePending) { showMoreHistory() } }) } function showMoreHistory () { if (!hasMore) { return } if (lessActive) { morePending = true return } morePending = false $scope.state.moreActive = moreActive = true var curJump = jump var curMoreJump = ++moreJump var inputMediaFilter = $scope.historyFilter.mediaType && {_: inputMediaFilters[$scope.historyFilter.mediaType]} var limit = Config.Mobile ? 20 : 0 var getMessagesPromise = inputMediaFilter ? AppMessagesManager.getSearch($scope.curDialog.peerID, '', inputMediaFilter, maxID, limit) : AppMessagesManager.getHistory($scope.curDialog.peerID, maxID, limit) getMessagesPromise.then(function (historyResult) { $scope.state.moreActive = moreActive = false if (curJump != jump || curMoreJump != moreJump) return angular.forEach(historyResult.history, function (id) { peerHistory.messages.unshift(AppMessagesManager.wrapForHistory(id)) peerHistory.ids.unshift(id) }) hasMore = historyResult.count === null || (historyResult.history.length && peerHistory.messages.length < historyResult.count) if (historyResult.history.length) { delete $scope.state.empty maxID = historyResult.history[historyResult.history.length - 1] $scope.$broadcast('ui_history_prepend') if (AppMessagesManager.regroupWrappedHistory(peerHistory.messages, historyResult.history.length + 1)) { $scope.$broadcast('messages_regroup') } } if (lessPending) { showLessHistory() } }) } function loadHistory (forceRecent) { $scope.historyState.missedCount = 0 hasMore = false $scope.historyState.skipped = hasLess = false maxID = 0 minID = 0 peerHistory = historiesQueuePush(peerID) var limit = 0 var backLimit = 0 if ($scope.curDialog.messageID) { maxID = parseInt($scope.curDialog.messageID) limit = 20 backLimit = 20 } else if (forceRecent) { limit = 10 } $scope.state.moreActive = moreActive = false morePending = false $scope.state.lessActive = lessActive = false lessPending = false var prerenderedLen = peerHistory.messages.length if (prerenderedLen && (maxID || backLimit)) { prerenderedLen = 0 peerHistory.messages = [] peerHistory.ids = [] $scope.state.empty = true } var curJump = ++jump var inputMediaFilter = $scope.historyFilter.mediaType && {_: inputMediaFilters[$scope.historyFilter.mediaType]} var getMessagesPromise = inputMediaFilter ? AppMessagesManager.getSearch($scope.curDialog.peerID, '', inputMediaFilter, maxID) : AppMessagesManager.getHistory($scope.curDialog.peerID, maxID, limit, backLimit, prerenderedLen) $scope.state.mayBeHasMore = true // console.log(dT(), 'start load history', $scope.curDialog) getMessagesPromise.then(function (historyResult) { if (curJump != jump) return // console.log(dT(), 'history loaded', angular.copy(historyResult)) var fetchedLength = historyResult.history.length minID = (historyResult.unreadSkip || (maxID && historyResult.history.indexOf(maxID) >= backLimit - 1)) ? historyResult.history[0] : 0 maxID = historyResult.history[historyResult.history.length - 1] $scope.historyState.skipped = hasLess = minID > 0 hasMore = historyResult.count === null || (fetchedLength && fetchedLength < historyResult.count) updateHistoryPeer() safeReplaceObject($scope.state, {loaded: true, empty: !fetchedLength}) peerHistory.messages = [] peerHistory.ids = [] angular.forEach(historyResult.history, function (id) { var message = AppMessagesManager.wrapForHistory(id) if ($scope.historyState.skipped) { delete message.pFlags.unread } if (historyResult.unreadOffset) { message.unreadAfter = true } peerHistory.messages.push(message) peerHistory.ids.push(id) }) peerHistory.messages.reverse() peerHistory.ids.reverse() if (AppMessagesManager.regroupWrappedHistory(peerHistory.messages)) { $scope.$broadcast('messages_regroup') } if (historyResult.unreadOffset) { $scope.historyUnreadAfter = historyResult.history[historyResult.unreadOffset - 1] } else if ($scope.historyUnreadAfter) { delete $scope.historyUnreadAfter } $scope.$broadcast('messages_unread_after') var focusedMsgID = $scope.curDialog.messageID || 0 onContentLoaded(function () { $scope.$broadcast('messages_focus', focusedMsgID) }) $scope.$broadcast('ui_history_change') if (!$rootScope.idle.isIDLE) { AppMessagesManager.readHistory($scope.curDialog.peerID) } updateBotActions() updateChannelActions() }, function () { safeReplaceObject($scope.state, {error: true, loaded: true}) }) } function showEmptyHistory () { jump++ safeReplaceObject($scope.historyPeer, {}) safeReplaceObject($scope.state, {notSelected: true}) peerHistory = false hasMore = false $scope.$broadcast('ui_history_change') } function startBot () { AppMessagesManager.startBot(peerID, 0, $scope.curDialog.startParam) $scope.curDialog.startParam = false } function cancelBot () { delete $scope.curDialog.startParam } function joinChannel () { MtpApiManager.invokeApi('channels.joinChannel', { channel: AppChatsManager.getChannelInput(-peerID) }).then(function (result) { ApiUpdatesManager.processUpdateMessage(result) }) } function togglePeerMuted (muted) { NotificationsManager.getPeerSettings(peerID).then(function (settings) { settings.mute_until = !muted ? 0 : 2000000000 NotificationsManager.updatePeerSettings(peerID, settings) }) } function toggleMessage (messageID, $event) { if ($scope.historyState.botActions || $rootScope.idle.afterFocus) { return false } var message = AppMessagesManager.getMessage(messageID) if (message._ == 'messageService') { return false } if (!$scope.historyState.selectActions) { if (getSelectedText()) { return false } var target = $event.target while (target) { if (target instanceof SVGElement) { target = target.parentNode continue } if (target.className && target.className.indexOf('im_message_outer_wrap') != -1) { if (Config.Mobile) { return false } break } if (target.className && target.className.indexOf('im_message_date') != -1) { if ($scope.historyFilter.mediaType) { $rootScope.$broadcast('history_focus', { peerString: $scope.curDialog.peer, messageID: messageID }) return } if (AppPeersManager.isBroadcast(peerID)) { quickForward(messageID) } else { selectedReply(messageID) } return false } if (Config.Mobile && target.className && target.className.indexOf('im_message_body') != -1) { break } if (target.tagName == 'A' || hasOnclick(target)) { return false } target = target.parentNode } if (Config.Mobile) { $scope.historyState.canEdit = AppMessagesManager.canEditMessage(messageID) $modal.open({ templateUrl: templateUrl('message_actions_modal'), windowClass: 'message_actions_modal_window', scope: $scope.$new() }).result.then(function (action) { switch (action) { case 'reply': selectedReply(messageID) break case 'edit': selectedEdit(messageID) break case 'delete': selectedDelete(messageID) break case 'forward': selectedForward(messageID) break case 'select': $scope.historyState.selectActions = 'selected' $scope.$broadcast('ui_panel_update') toggleMessage(messageID) break } }) return false } } var shiftClick = $event && $event.shiftKey if (shiftClick) { $scope.$broadcast('ui_selection_clear') } if ($scope.selectedMsgs[messageID]) { lastSelectID = false delete $scope.selectedMsgs[messageID] $scope.selectedCount-- if (!$scope.selectedCount) { $scope.historyState.selectActions = false $scope.$broadcast('ui_panel_update') } } else { if (!shiftClick) { lastSelectID = messageID } else if (lastSelectID != messageID) { var dir = lastSelectID > messageID var i, startPos, curMessageID for (i = 0; i < peerHistory.messages.length; i++) { if (peerHistory.messages[i].mid == lastSelectID) { startPos = i break } } i = startPos while (peerHistory.messages[i] && (curMessageID = peerHistory.messages[i].mid) != messageID) { if (!$scope.selectedMsgs[curMessageID]) { $scope.selectedMsgs[curMessageID] = true $scope.selectedCount++ } i += dir ? -1 : +1 } } $scope.selectedMsgs[messageID] = true $scope.selectedCount++ if (!$scope.historyState.selectActions) { $scope.historyState.selectActions = 'selected' $scope.$broadcast('ui_panel_update') } } if ($scope.selectedCount == 1) { angular.forEach($scope.selectedMsgs, function (t, messageID) { $scope.historyState.canEdit = AppMessagesManager.canEditMessage(messageID) }) } $scope.$broadcast('messages_select') } function selectInlineBot (botID, $event) { if ($scope.historyState.canReply) { $scope.$broadcast('inline_bot_select', botID) } return cancelEvent($event) } function selectedCancel (noBroadcast) { $scope.selectedMsgs = {} $scope.selectedCount = 0 $scope.historyState.selectActions = false lastSelectID = false if (!noBroadcast) { $scope.$broadcast('ui_panel_update') } $scope.$broadcast('messages_select') } function selectedFlush () { ErrorService.confirm({type: 'HISTORY_FLUSH'}).then(function () { AppMessagesManager.flushHistory($scope.curDialog.peerID, true).then(function () { selectedCancel() }) }) } function selectedDelete (selectedMessageID) { var selectedMessageIDs = [] if (selectedMessageID) { selectedMessageIDs.push(selectedMessageID) } else if ($scope.selectedCount > 0) { angular.forEach($scope.selectedMsgs, function (t, messageID) { selectedMessageIDs.push(messageID) }) } if (selectedMessageIDs.length) { var peerID = $scope.curDialog.peerID var isUser = peerID > 0 var isChannel = AppPeersManager.isChannel(peerID) var isBroadcast = AppPeersManager.isBroadcast(peerID) var isMegagroup = AppPeersManager.isMegagroup(peerID) var isUsualGroup = !isChannel && !isUser var revocable = !isChannel for (var i = 0; revocable && i < selectedMessageIDs.length; i++) { var messageID = selectedMessageIDs[i] if (!AppMessagesManager.canRevokeMessage(messageID)) { revocable = false } } ErrorService.confirm({ type: 'MESSAGES_DELETE', count: selectedMessageIDs.length, revocable: revocable, isUser: isUser, peerID: peerID, isChannel: isBroadcast, isSupergroup: isMegagroup, isUsualGroup: isUsualGroup }, {}, { revoke: false }).then(function (data) { AppMessagesManager.deleteMessages(selectedMessageIDs, data.revoke).then(function () { selectedCancel() }) }) } } function quickForward (msgID) { PeersSelectService.selectPeers({ canSend: true, confirm_type: 'FORWARD_PEER', shareLinkPromise: AppMessagesManager.getMessageShareLink(msgID) }).then(function (peerStrings) { angular.forEach(peerStrings, function (peerString) { var peerID = AppPeersManager.getPeerID(peerString) AppMessagesManager.forwardMessages(peerID, [msgID]) }) var toastData = toaster.pop({ type: 'info', body: _('confirm_modal_forward_to_peer_success'), bodyOutputType: 'trustedHtml', clickHandler: function () { $rootScope.$broadcast('history_focus', { peerString: peerStrings[0] }) toaster.clear(toastData) }, showCloseButton: false }) }) } function selectedForward (selectedMessageID) { var selectedMessageIDs = [] if (selectedMessageID) { selectedMessageIDs.push(selectedMessageID) } else if ($scope.selectedCount > 0) { angular.forEach($scope.selectedMsgs, function (t, messageID) { selectedMessageIDs.push(messageID) }) } if (selectedMessageIDs.length) { PeersSelectService.selectPeer({canSend: true}).then(function (peerString) { selectedCancel() $rootScope.$broadcast('history_focus', { peerString: peerString, attachment: { _: 'fwd_messages', id: selectedMessageIDs } }) }) } } function selectedReply (selectedMessageID) { if (!selectedMessageID && $scope.selectedCount == 1) { angular.forEach($scope.selectedMsgs, function (t, messageID) { selectedMessageID = messageID }) } if (selectedMessageID) { selectedCancel() $scope.$broadcast('reply_selected', selectedMessageID) } } function selectedEdit (selectedMessageID) { if (!selectedMessageID && $scope.selectedCount == 1) { angular.forEach($scope.selectedMsgs, function (t, messageID) { selectedMessageID = messageID }) } if (selectedMessageID) { selectedCancel() $scope.$broadcast('edit_selected', selectedMessageID) } } function toggleEdit () { if ($scope.historyState.selectActions) { selectedCancel() } else { $scope.historyState.selectActions = 'selected' $scope.$broadcast('ui_panel_update') } } function toggleMedia (mediaType) { if (mediaType == 'search') { $rootScope.$broadcast('history_search', $scope.curDialog.peerID) return } $scope.historyFilter.mediaType = mediaType || false if (mediaType) { $scope.curDialog.messageID = false } peerHistory.messages = [] peerHistory.ids = [] $scope.state.empty = true loadHistory() } function returnToRecent () { if ($scope.historyFilter.mediaType) { toggleMedia() } else { if ($scope.curDialog.messageID) { $rootScope.$broadcast('history_focus', {peerString: $scope.curDialog.peer}) } else { loadHistory(true) } } } $scope.$on('history_update', angular.noop) var loadAfterSync = false $scope.$on('stateSynchronized', function () { if (!loadAfterSync) { return } if (loadAfterSync == $scope.curDialog.peerID) { loadHistory() } loadAfterSync = false }) $scope.$on('reply_button_press', function (e, button) { var replyKeyboard = $scope.historyState.replyKeyboard if (!replyKeyboard) { return } var sendOptions = { replyToMsgID: peerID < 0 && replyKeyboard.mid } switch (button._) { case 'keyboardButtonRequestPhone': ErrorService.confirm({type: 'BOT_ACCESS_PHONE'}).then(function () { var user = AppUsersManager.getSelf() AppMessagesManager.sendOther(peerID, { _: 'inputMediaContact', phone_number: user.phone, first_name: user.first_name, last_name: user.last_name }, sendOptions) }) break case 'keyboardButtonRequestGeoLocation': ErrorService.confirm({type: 'BOT_ACCESS_GEO'}).then(function () { return GeoLocationManager.getPosition().then(function (coords) { AppMessagesManager.sendOther(peerID, { _: 'inputMediaGeoPoint', geo_point: { _: 'inputGeoPoint', 'lat': coords['lat'], 'long': coords['long'] } }, sendOptions) }, function (error) { ErrorService.alert( _('error_modal_bad_request_title_raw'), _('error_modal_gelocation_na_raw') ) }) }) break default: AppMessagesManager.sendText(peerID, button.text, sendOptions) } }) $scope.$on('history_reload', function (e, updPeerID) { if (updPeerID == $scope.curDialog.peerID) { loadHistory() } }) $scope.$on('history_forbidden', function (e, updPeerID) { if (updPeerID == $scope.curDialog.peerID) { $location.url('/im') } historiesQueuePop(updPeerID) }) $scope.$on('dialog_migrate', function (e, data) { if (data.migrateFrom == $scope.curDialog.peerID) { var peerString = AppPeersManager.getPeerString(data.migrateTo) $rootScope.$broadcast('history_focus', {peerString: peerString}) } historiesQueuePop(data.migrateFrom) }) $scope.$on('notify_settings', function (e, data) { if (data.peerID == $scope.curDialog.peerID) { updateChannelActions() } }) $scope.$on('channel_settings', function (e, data) { if (data.channelID == -$scope.curDialog.peerID) { updateChannelActions() } }) var typingTimeouts = {} $scope.$on('history_append', function (e, addedMessage) { var history = historiesQueueFind(addedMessage.peerID) if (!history) { return } var curPeer = addedMessage.peerID == $scope.curDialog.peerID if (curPeer) { if ($scope.historyFilter.mediaType || $scope.historyState.skipped) { if (addedMessage.my) { returnToRecent() } else { $scope.historyState.missedCount++ } return } if ($scope.curDialog.messageID && addedMessage.my) { returnToRecent() } delete $scope.state.empty } // console.log('append', addedMessage) // console.trace() var historyMessage = AppMessagesManager.wrapForHistory(addedMessage.messageID) history.messages.push(historyMessage) history.ids.push(addedMessage.messageID) if (AppMessagesManager.regroupWrappedHistory(history.messages, -3)) { $scope.$broadcast('messages_regroup') } if (curPeer) { $scope.historyState.typing.splice(0, $scope.historyState.typing.length) $scope.$broadcast('ui_history_append_new', { my: addedMessage.my, idleScroll: unreadAfterIdle && !historyMessage.pFlags.out && $rootScope.idle.isIDLE }) if (addedMessage.my && $scope.historyUnreadAfter) { delete $scope.historyUnreadAfter $scope.$broadcast('messages_unread_after') } // console.log('append check', $rootScope.idle.isIDLE, addedMessage.peerID, $scope.curDialog.peerID, historyMessage, history.messages[history.messages.length - 2]) if ($rootScope.idle.isIDLE) { if (historyMessage.pFlags.unread && !historyMessage.pFlags.out && !(history.messages[history.messages.length - 2] || {}).pFlags.unread) { $scope.historyUnreadAfter = historyMessage.mid unreadAfterIdle = true $scope.$broadcast('messages_unread_after') } } else { $timeout(function () { AppMessagesManager.readHistory($scope.curDialog.peerID) }) } updateBotActions() updateChannelActions() } }) $scope.$on('history_multiappend', function (e, historyMultiAdded) { // console.log(dT(), 'multiappend', angular.copy(historyMultiAdded)) var regroupped = false var unreadAfterChanged = false var isIDLE = $rootScope.idle.isIDLE angular.forEach(historyMultiAdded, function (msgs, peerID) { var history = historiesQueueFind(peerID) // var history = historiesQueuePush(peerID) if (!history) { return } var curPeer = peerID == $scope.curDialog.peerID var exlen = history.messages.length var len = msgs.length if (curPeer) { if ($scope.historyFilter.mediaType || $scope.historyState.skipped) { $scope.historyState.missedCount += len return } delete $scope.state.empty } if ((!curPeer || isIDLE) && exlen > (len > 10 ? 10 : 100)) { console.warn(dT(), 'Drop too many messages', len, exlen, isIDLE, curPeer, peerID) if (curPeer) { minID = history.messages[exlen - 1].mid $scope.historyState.skipped = hasLess = minID > 0 if (hasLess) { loadAfterSync = peerID $scope.$broadcast('ui_history_append') } } else { historiesQueuePop(peerID) } return } var messageID, i var hasOut = false var unreadAfterNew = false var historyMessage = history.messages[history.messages.length - 1] var lastIsRead = !historyMessage || !historyMessage.pFlags.unread for (i = 0; i < len; i++) { messageID = msgs[i] if (messageID > 0 && messageID < maxID || history.ids.indexOf(messageID) !== -1) { continue } historyMessage = AppMessagesManager.wrapForHistory(messageID) history.messages.push(historyMessage) history.ids.push(messageID) if (!unreadAfterNew && isIDLE) { if (historyMessage.pFlags.unread && !historyMessage.pFlags.out && lastIsRead) { unreadAfterNew = messageID } else { lastIsRead = !historyMessage.pFlags.unread } } if (!hasOut && historyMessage.pFlags.out) { hasOut = true } } // console.log('after append', angular.copy(history.messages), angular.copy(history.ids)) if (AppMessagesManager.regroupWrappedHistory(history.messages, -len - 2)) { regroupped = true } if (curPeer) { if ($scope.historyState.typing.length) { $scope.historyState.typing.splice(0, $scope.historyState.typing.length) } $scope.$broadcast('ui_history_append_new', { idleScroll: unreadAfterIdle && !hasOut && isIDLE }) if (isIDLE) { if (unreadAfterNew) { $scope.historyUnreadAfter = unreadAfterNew unreadAfterIdle = true unreadAfterChanged = true } } else { $timeout(function () { AppMessagesManager.readHistory($scope.curDialog.peerID) }) } updateBotActions() updateChannelActions() } }) if (regroupped) { $scope.$broadcast('messages_regroup') } if (unreadAfterChanged) { $scope.$broadcast('messages_unread_after') } }) $scope.$on('history_delete', function (e, historyUpdate) { var history = historiesQueueFind(historyUpdate.peerID) if (!history) { return } var newMessages = [] var i for (i = 0; i < history.messages.length; i++) { if (!historyUpdate.msgs[history.messages[i].mid]) { newMessages.push(history.messages[i]) } } history.messages = newMessages AppMessagesManager.regroupWrappedHistory(history.messages) $scope.$broadcast('messages_regroup') if (historyUpdate.peerID == $scope.curDialog.peerID) { $scope.state.empty = !newMessages.length updateBotActions() } }) $scope.$on('dialog_flush', function (e, dialog) { var history = historiesQueueFind(dialog.peerID) if (history) { history.messages = [] history.ids = [] if (dialog.peerID == $scope.curDialog.peerID) { $scope.state.empty = true updateBotActions() } } }) $scope.$on('history_focus', function (e, peerData) { if ($scope.historyFilter.mediaType) { toggleMedia() } }) $scope.$on('apiUpdate', function (e, update) { switch (update._) { case 'updateUserTyping': case 'updateChatUserTyping': AppUsersManager.forceUserOnline(update.user_id) if (AppUsersManager.hasUser(update.user_id) && $scope.curDialog.peerID == (update._ == 'updateUserTyping' ? update.user_id : -update.chat_id )) { if ($scope.historyState.typing.indexOf(update.user_id) == -1) { $scope.historyState.typing.push(update.user_id) } $timeout.cancel(typingTimeouts[update.user_id]) typingTimeouts[update.user_id] = $timeout(function () { var pos = $scope.historyState.typing.indexOf(update.user_id) if (pos !== -1) { $scope.historyState.typing.splice(pos, 1) } }, 6000) } break } }) $scope.$on('history_need_less', showLessHistory) $scope.$on('history_need_more', showMoreHistory) $rootScope.$watch('idle.isIDLE', function (newVal) { if (!newVal && $scope.curDialog && $scope.curDialog.peerID && !$scope.historyFilter.mediaType && !$scope.historyState.skipped) { AppMessagesManager.readHistory($scope.curDialog.peerID) } if (!newVal) { unreadAfterIdle = false if (loadAfterSync && loadAfterSync == $scope.curDialog.peerID) { loadHistory() loadAfterSync = false } } }) }) .controller('AppImPanelController', function ($scope) { $scope.$on('user_update', angular.noop) }) .controller('AppImSendController', function ($rootScope, $q, $scope, $timeout, MtpApiManager, Storage, AppProfileManager, AppChatsManager, AppUsersManager, AppPeersManager, AppDocsManager, AppStickersManager, AppMessagesManager, AppInlineBotsManager, MtpApiFileManager, DraftsManager, RichTextProcessor) { $scope.$watch('curDialog.peer', resetDraft) $scope.$on('user_update', angular.noop) $scope.$on('peer_draft_attachment', applyDraftAttachment) $scope.$on('reply_selected', function (e, messageID) { replySelect(messageID, true) }) $scope.$on('edit_selected', function (e, messageID) { setEditDraft(messageID, true) }) $scope.$on('ui_typing', onTyping) $scope.draftMessage = { text: '', send: submitMessage, replyClear: replyClear, fwdsClear: fwdsClear, toggleSlash: toggleSlash, replyKeyboardToggle: replyKeyboardToggle, type: 'new' } $scope.mentions = {} $scope.commands = {} $scope.$watch('draftMessage.text', onMessageChange) $scope.$watch('draftMessage.files', onFilesSelected) $scope.$watch('draftMessage.sticker', onStickerSelected) $scope.$watch('draftMessage.command', onCommandSelected) $scope.$watch('draftMessage.inlineResultID', onInlineResultSelected) $scope.$on('history_reply_markup', function (e, peerData) { if (peerData.peerID == $scope.curDialog.peerID) { updateReplyKeyboard() } }) $scope.$on('inline_bot_select', function (e, botID) { var bot = AppUsersManager.getUser(botID) $scope.draftMessage.text = '@' + bot.username + ' ' $scope.$broadcast('ui_peer_draft', {focus: true}) }) $scope.$on('inline_bots_popular', updateMentions) $scope.$on('last_message_edit', setEditLastMessage) $rootScope.$watch('idle.isIDLE', function (newVal) { if ($rootScope.idle.initial) { return } if (newVal && $scope.curDialog.peerID) { $scope.$broadcast('ui_message_before_send') $timeout(function () { DraftsManager.syncDraft($scope.curDialog.peerID) }) } }) $scope.$on('draft_updated', function (e, draftUpdate) { if (draftUpdate.peerID == $scope.curDialog.peerID && !draftUpdate.local && (!$scope.draftMessage.text || $rootScope.idle.isIDLE)) { getDraft() } }) var replyToMarkup = false var forceDraft = false var editMessageID = false function submitMessage (e) { $scope.$broadcast('ui_message_before_send') $timeout(function () { if (editMessageID) { editMessage() } else { sendMessage() } }) return cancelEvent(e) } function sendMessage () { var text = $scope.draftMessage.text if (angular.isString(text) && text.length > 0) { text = RichTextProcessor.parseEmojis(text) var options = { replyToMsgID: $scope.draftMessage.replyToMsgID, clearDraft: true } do { AppMessagesManager.sendText($scope.curDialog.peerID, text.substr(0, 4096), options) text = text.substr(4096) options = angular.copy(options) delete options.clearDraft } while (text.length) } fwdsSend() if (forceDraft == $scope.curDialog.peer) { forceDraft = false } resetDraft() $scope.$broadcast('ui_message_send') } function editMessage () { var text = $scope.draftMessage.text text = RichTextProcessor.parseEmojis(text) AppMessagesManager.editMessage(editMessageID, text).then(function () { editMessageID = false resetDraft() $scope.$broadcast('ui_message_send') $timeout(function () { $scope.$broadcast('ui_peer_reply') }) }) } function updateMentions () { var peerID = $scope.curDialog.peerID if (!peerID) { safeReplaceObject($scope.mentions, {}) $scope.$broadcast('mentions_update') return } var mentionUsers = [] var mentionIndex = SearchIndexManager.createIndex() var inlineBotsPromise = AppInlineBotsManager.getPopularBots().then(function (inlineBots) { var ids = [] angular.forEach(inlineBots, function (bot) { ids.push(bot.id) }) return ids }) var chatParticipantsPromise if (peerID < 0) { chatParticipantsPromise = AppProfileManager.getChatFull(-peerID).then(function (chatFull) { var participantsVector = (chatFull.participants || {}).participants || [] var ids = [] angular.forEach(participantsVector, function (participant) { ids.push(participant.user_id) }) return ids }) } else { chatParticipantsPromise = $q.when([]) } $q.all({pop: inlineBotsPromise, chat: chatParticipantsPromise}).then(function (result) { var done = {} var ids = result.pop.concat(result.chat) angular.forEach(ids, function (userID) { if (done[userID]) { return } done[userID] = true mentionUsers.push(AppUsersManager.getUser(userID)) SearchIndexManager.indexObject(userID, AppUsersManager.getUserSearchText(userID), mentionIndex) }) safeReplaceObject($scope.mentions, { users: mentionUsers, index: mentionIndex }) $scope.$broadcast('mentions_update') }) } function updateCommands () { var peerID = $scope.curDialog.peerID if (!peerID) { safeReplaceObject($scope.commands, {}) $scope.$broadcast('mentions_update') return } AppProfileManager.getPeerBots(peerID).then(function (peerBots) { if (!peerBots.length) { safeReplaceObject($scope.commands, {}) $scope.$broadcast('mentions_update') return } var needMentions = peerID < 0 var commandsList = [] var commandsIndex = SearchIndexManager.createIndex() angular.forEach(peerBots, function (peerBot) { var mention = '' if (needMentions) { var bot = AppUsersManager.getUser(peerBot.id) if (bot && bot.username) { mention += '@' + bot.username } } var botSearchText = AppUsersManager.getUserSearchText(peerBot.id) angular.forEach(peerBot.commands, function (description, command) { var value = '/' + command + mention commandsList.push({ botID: peerBot.id, value: value, rDescription: RichTextProcessor.wrapRichText(description, {noLinks: true, noLineBreaks: true}) }) SearchIndexManager.indexObject(value, botSearchText + ' ' + command + ' ' + description, commandsIndex) }) }) safeReplaceObject($scope.commands, { list: commandsList, index: commandsIndex }) $scope.$broadcast('mentions_update') }) } function resetDraft (newPeer, prevPeer) { var prevPeerID = prevPeer ? AppPeersManager.getPeerID(prevPeer) : 0 if (newPeer != prevPeer && prevPeerID) { $scope.$broadcast('ui_message_before_send') $timeout(function () { DraftsManager.syncDraft(prevPeerID) resetDraft() }) return } editMessageID = false updateMentions() updateCommands() replyClear() updateReplyKeyboard() delete $scope.draftMessage.inlineProgress $scope.$broadcast('inline_results', false) // console.log(dT(), 'reset draft', $scope.curDialog.peer, forceDraft) if (forceDraft) { if (forceDraft == $scope.curDialog.peer) { $scope.draftMessage.isBroadcast = AppPeersManager.isBroadcast($scope.curDialog.peerID) $scope.$broadcast('ui_peer_draft') return } else { forceDraft = false } } fwdsClear() getDraft() } function getDraft () { if ($scope.curDialog.peerID) { var draftDataPromise if (editMessageID) { draftDataPromise = AppMessagesManager.getMessageEditData(editMessageID).then(function (draftData) { draftData.replyToMsgID = editMessageID return draftData }, function (error) { console.warn(error) editMessageID = false getDraft() return $q.reject() }) } else { draftDataPromise = DraftsManager.getDraft($scope.curDialog.peerID) } draftDataPromise.then(function (draftData) { $scope.draftMessage.type = editMessageID ? 'edit' : 'new' $scope.draftMessage.text = draftData ? draftData.text : '' $scope.draftMessage.isBroadcast = AppPeersManager.isBroadcast($scope.curDialog.peerID) if (draftData.replyToMsgID) { var replyToMsgID = draftData.replyToMsgID replySelect(replyToMsgID) } else { replyClear() } $scope.$broadcast('ui_peer_draft') }) } else { // console.log('Reset peer') $scope.draftMessage.text = '' $scope.$broadcast('ui_peer_draft') } } function applyDraftAttachment (e, attachment) { console.log(dT(), 'apply draft attach', attachment) if (!attachment || !attachment._) { return } if (attachment._ == 'share_url') { var url = attachment.url var text = attachment.text || ' ' forceDraft = $scope.curDialog.peer $timeout(function () { $scope.draftMessage.text = url + '\n' + text $scope.$broadcast('ui_peer_draft', { customSelection: [ url + '\n', text, '' ] }) }, 1000) } else if (attachment._ == 'fwd_messages') { forceDraft = $scope.curDialog.peer $timeout(function () { $scope.draftMessage.fwdMessages = attachment.id $scope.$broadcast('ui_peer_reply') }, 100) } else if (attachment._ == 'inline_query') { var mention = attachment.mention var query = attachment.query forceDraft = $scope.curDialog.peer $timeout(function () { $scope.draftMessage.text = mention + ' ' + query $scope.$broadcast('ui_peer_draft', { customSelection: [ mention + ' ' + query, '', '' ] }) }, 1000) } } function replySelect (messageID, byUser) { if (editMessageID && byUser) { replyClear() return } $scope.draftMessage.replyToMsgID = messageID $scope.$broadcast('ui_peer_reply') replyToMarkup = false if (byUser && !editMessageID) { DraftsManager.changeDraft($scope.curDialog.peerID, { text: $scope.draftMessage.text, replyToMsgID: messageID }) } } function setEditDraft (messageID) { editMessageID = messageID getDraft() } function setEditLastMessage () { if (editMessageID || !$scope.curDialog.peerID) { return false } AppMessagesManager.getHistory($scope.curDialog.peerID).then(function (historyResult) { for (var i = 0, messageID; i < historyResult.history.length; i++) { messageID = historyResult.history[i] if (AppMessagesManager.canEditMessage(messageID)) { setEditDraft(messageID) break } } }) } function replyClear (byUser) { if (editMessageID) { editMessageID = false getDraft() return } var mid = $scope.draftMessage.replyToMsgID if (mid && $scope.historyState.replyKeyboard && $scope.historyState.replyKeyboard.mid == mid && !$scope.historyState.replyKeyboard.pFlags.hidden) { $scope.historyState.replyKeyboard.pFlags.hidden = true $scope.$broadcast('ui_keyboard_update') } delete $scope.draftMessage.replyToMsgID $scope.$broadcast('ui_peer_reply') if (byUser) { DraftsManager.changeDraft($scope.curDialog.peerID, { text: $scope.draftMessage.text }) } } function fwdsClear () { if ($scope.draftMessage.fwdMessages && $scope.draftMessage.fwdMessages.length) { delete $scope.draftMessage.fwdMessages $scope.$broadcast('ui_peer_reply') if (forceDraft == $scope.curDialog.peer) { forceDraft = false } } } function fwdsSend () { if ($scope.draftMessage.fwdMessages && $scope.draftMessage.fwdMessages.length) { var ids = $scope.draftMessage.fwdMessages.slice() fwdsClear() setZeroTimeout(function () { AppMessagesManager.forwardMessages($scope.curDialog.peerID, ids) }) } } function toggleSlash ($event) { if ($scope.draftMessage.text && $scope.draftMessage.text.charAt(0) == '/') { $scope.draftMessage.text = '' } else { $scope.draftMessage.text = '/' } $scope.$broadcast('ui_peer_draft', {focus: true}) return cancelEvent($event) } function updateReplyKeyboard () { var peerID = $scope.curDialog.peerID var replyKeyboard = AppMessagesManager.getReplyKeyboard(peerID) if (replyKeyboard) { replyKeyboard = AppMessagesManager.wrapReplyMarkup(replyKeyboard) } // console.log('update reply markup', peerID, replyKeyboard) $scope.historyState.replyKeyboard = replyKeyboard var addReplyMessage = replyKeyboard && !replyKeyboard.pFlags.hidden && (replyKeyboard._ == 'replyKeyboardForceReply' || (replyKeyboard._ == 'replyKeyboardMarkup' && peerID < 0)) if (addReplyMessage) { replySelect(replyKeyboard.mid) replyToMarkup = true } else if (replyToMarkup) { replyClear() } var enabled = replyKeyboard && !replyKeyboard.pFlags.hidden && replyKeyboard._ == 'replyKeyboardMarkup' $scope.$broadcast('ui_keyboard_update', {enabled: enabled}) $scope.$emit('ui_panel_update', {blur: enabled}) } function replyKeyboardToggle ($event) { var replyKeyboard = $scope.historyState.replyKeyboard if (replyKeyboard) { replyKeyboard.pFlags.hidden = !replyKeyboard.pFlags.hidden updateReplyKeyboard() } return cancelEvent($event) } function onMessageChange (newVal, prevVal, a) { // console.log('ctrl text changed', newVal, prevVal); if (newVal === '' && prevVal === '') { return } if (newVal && newVal.length) { if (!$scope.historyFilter.mediaType && !$scope.historyState.skipped) { AppMessagesManager.readHistory($scope.curDialog.peerID) } } if ($scope.curDialog.peerID) { if (!editMessageID) { var replyToMsgID = $scope.draftMessage.replyToMsgID if (replyToMsgID && $scope.historyState.replyKeyboard && $scope.historyState.replyKeyboard.mid == replyToMsgID) { replyToMsgID = 0 } DraftsManager.changeDraft($scope.curDialog.peerID, { text: newVal, replyToMsgID: replyToMsgID }) } checkInlinePattern(newVal) } } var inlineUsernameRegex = /^@([a-zA-Z\d_]{1,32})( | )([\s\S]*)$/ var inlineStickersEmojiRegex = /^\s*:(\S+):\s*$/ var getInlineResultsTO = false var lastInlineBot = false var jump = 0 function checkInlinePattern (message) { if (getInlineResultsTO) { $timeout.cancel(getInlineResultsTO) } var curJump = ++jump if (!message || !message.length) { delete $scope.draftMessage.inlineProgress $scope.$broadcast('inline_results', false) return } var matches = message.match(inlineUsernameRegex) if (!matches) { matches = message.match(inlineStickersEmojiRegex) if (matches) { var emojiCode = EmojiHelper.shortcuts[matches[1]] if (emojiCode) { $scope.draftMessage.inlineProgress = true AppStickersManager.searchStickers(emojiCode).then(function (docs) { var inlineResults = [] angular.forEach(docs, function (doc) { inlineResults.push({ _: 'botInlineMediaResult', qID: '_sticker_' + doc.id, pFlags: {sticker: true}, id: doc.id, type: 'sticker', document: doc, send_message: {_: 'botInlineMessageMediaAuto'} }) }) var botResults = { pFlags: {gallery: true}, query_id: 0, results: inlineResults } botResults.text = message $scope.$broadcast('inline_results', botResults) delete $scope.draftMessage.inlineProgress }) } else { delete $scope.draftMessage.inlineProgress $scope.$broadcast('inline_results', false) return } } delete $scope.draftMessage.inlineProgress $scope.$broadcast('inline_results', false) return } var username = matches[1] var inlineBotPromise $scope.draftMessage.inlineProgress = true if (lastInlineBot && lastInlineBot.username == username) { inlineBotPromise = $q.when(lastInlineBot) } else { inlineBotPromise = AppInlineBotsManager.resolveInlineMention(username) } inlineBotPromise.then(function (inlineBot) { if (curJump != jump) { return } lastInlineBot = inlineBot $scope.$broadcast('inline_placeholder', { prefix: '@' + username + matches[2], placeholder: inlineBot.placeholder }) if (getInlineResultsTO) { $timeout.cancel(getInlineResultsTO) } getInlineResultsTO = $timeout(function () { var query = RichTextProcessor.parseEmojis(matches[3]) AppInlineBotsManager.getInlineResults($scope.curDialog.peerID, inlineBot.id, query, inlineBot.geo, '').then(function (botResults) { getInlineResultsTO = false if (curJump != jump) { return } botResults.text = message $scope.$broadcast('inline_results', botResults) delete $scope.draftMessage.inlineProgress }, function () { $scope.$broadcast('inline_results', false) delete $scope.draftMessage.inlineProgress }) }, 500) }, function (error) { $scope.$broadcast('inline_results', false) delete $scope.draftMessage.inlineProgress }) } function onTyping () { if (AppPeersManager.isBroadcast($scope.curDialog.peerID)) { return false } MtpApiManager.invokeApi('messages.setTyping', { peer: AppPeersManager.getInputPeerByID($scope.curDialog.peerID), action: {_: 'sendMessageTypingAction'} })['catch'](function (error) { error.handled = true }) } function onFilesSelected (newVal) { if (!angular.isArray(newVal) || !newVal.length) { return } var options = { replyToMsgID: $scope.draftMessage.replyToMsgID, isMedia: $scope.draftMessage.isMedia } delete $scope.draftMessage.replyToMsgID if (newVal[0].lastModified) { newVal.sort(function (file1, file2) { return file1.lastModified - file2.lastModified }) } for (var i = 0; i < newVal.length; i++) { AppMessagesManager.sendFile($scope.curDialog.peerID, newVal[i], options) $scope.$broadcast('ui_message_send') } fwdsSend() } function onStickerSelected (newVal) { if (!newVal) { return } var doc = AppDocsManager.getDoc(newVal) if (doc.id && doc.access_hash) { var inputMedia = { _: 'inputMediaDocument', id: { _: 'inputDocument', id: doc.id, access_hash: doc.access_hash } } var options = { replyToMsgID: $scope.draftMessage.replyToMsgID } AppMessagesManager.sendOther($scope.curDialog.peerID, inputMedia, options) $scope.$broadcast('ui_message_send') fwdsSend() replyClear(true) } delete $scope.draftMessage.sticker } function onCommandSelected (command) { if (!command) { return } AppMessagesManager.sendText($scope.curDialog.peerID, command, { clearDraft: true }) if (forceDraft == $scope.curDialog.peer) { forceDraft = false } fwdsSend() resetDraft() delete $scope.draftMessage.sticker delete $scope.draftMessage.text delete $scope.draftMessage.command delete $scope.draftMessage.inlineResultID $scope.$broadcast('ui_message_send') $scope.$broadcast('ui_peer_draft') } function onInlineResultSelected (qID) { if (!qID) { return } if (qID.substr(0, 11) == '_switch_pm_') { var botID = lastInlineBot.id var startParam = qID.substr(11) return AppInlineBotsManager.switchToPM($scope.curDialog.peerID, botID, startParam) } var options = { replyToMsgID: $scope.draftMessage.replyToMsgID, clearDraft: true } if (qID.substr(0, 9) == '_sticker_') { var docID = qID.substr(9) var doc = AppDocsManager.getDoc(docID) if (doc.id && doc.access_hash) { var inputMedia = { _: 'inputMediaDocument', id: { _: 'inputDocument', id: doc.id, access_hash: doc.access_hash } } AppMessagesManager.sendOther($scope.curDialog.peerID, inputMedia, options) } } else { AppInlineBotsManager.sendInlineResult($scope.curDialog.peerID, qID, options) } if (forceDraft == $scope.curDialog.peer) { forceDraft = false } fwdsSend() resetDraft() delete $scope.draftMessage.sticker delete $scope.draftMessage.text delete $scope.draftMessage.command delete $scope.draftMessage.inlineResultID $scope.$broadcast('ui_message_send') $scope.$broadcast('ui_peer_draft') } }) .controller('AppLangSelectController', function ($scope, _, Storage, ErrorService, AppRuntimeManager) { $scope.supportedLocales = Config.I18n.supported $scope.langNames = Config.I18n.languages $scope.curLocale = Config.I18n.locale $scope.form = {locale: Config.I18n.locale} $scope.localeSelect = function localeSelect (newLocale) { newLocale = newLocale || $scope.form.locale if ($scope.curLocale !== newLocale) { ErrorService.confirm({type: 'APPLY_LANG_WITH_RELOAD'}).then(function () { Storage.set({i18n_locale: newLocale}).then(function () { AppRuntimeManager.reload() }) }, function () { $scope.form.locale = $scope.curLocale }) } } }) .controller('AppFooterController', function ($scope, LayoutSwitchService) { $scope.switchLayout = function (mobile) { LayoutSwitchService.switchLayout(mobile) } }) .controller('PhotoModalController', function ($q, $scope, $rootScope, $modalInstance, AppPhotosManager, AppMessagesManager, AppPeersManager, AppWebPagesManager, PeersSelectService, ErrorService) { $scope.photo = AppPhotosManager.wrapForFull($scope.photoID) $scope.nav = {} $scope.download = function () { AppPhotosManager.downloadPhoto($scope.photoID) } if (!$scope.messageID) { return } $scope.forward = function () { var messageID = $scope.messageID PeersSelectService.selectPeer({canSend: true}).then(function (peerString) { $rootScope.$broadcast('history_focus', { peerString: peerString, attachment: { _: 'fwd_messages', id: [messageID] } }) }) } $scope.goToMessage = function () { var messageID = $scope.messageID var peerID = AppMessagesManager.getMessagePeer(AppMessagesManager.getMessage(messageID)) var peerString = AppPeersManager.getPeerString(peerID) $modalInstance.dismiss() $rootScope.$broadcast('history_focus', {peerString: peerString, messageID: messageID}) } $scope['delete'] = function () { var messageID = $scope.messageID ErrorService.confirm({type: 'MESSAGE_DELETE'}).then(function () { AppMessagesManager.deleteMessages([messageID]) }) } var peerID = AppMessagesManager.getMessagePeer(AppMessagesManager.getMessage($scope.messageID)) var inputPeer = AppPeersManager.getInputPeerByID(peerID) var inputQuery = '' var inputFilter = {_: 'inputMessagesFilterPhotos'} var list = [$scope.messageID] var preloaded = {} var maxID = $scope.messageID var hasMore = true preloaded[$scope.messageID] = true updatePrevNext() function preloadPhotos (sign) { // var preloadOffsets = sign < 0 ? [-1,-2,1,-3,2] : [1,2,-1,3,-2] var preloadOffsets = sign < 0 ? [-1, -2] : [1, 2] var index = list.indexOf($scope.messageID) angular.forEach(preloadOffsets, function (offset) { var messageID = list[index + offset] if (messageID !== undefined && preloaded[messageID] === undefined) { preloaded[messageID] = true var message = AppMessagesManager.getMessage(messageID) var photoID = message.media.photo.id AppPhotosManager.preloadPhoto(photoID) } }) } function updatePrevNext (count) { var index = list.indexOf($scope.messageID) if (hasMore) { if (count) { $scope.count = Math.max(count, list.length) } } else { $scope.count = list.length } $scope.pos = $scope.count - index $scope.nav.hasNext = index > 0 $scope.nav.hasPrev = hasMore || index < list.length - 1 $scope.canForward = $scope.canDelete = $scope.messageID > 0 } $scope.nav.next = function () { if (!$scope.nav.hasNext) { return false } movePosition(-1) } $scope.nav.prev = function () { if (!$scope.nav.hasPrev) { return false } movePosition(+1) } $scope.$on('history_delete', function (e, historyUpdate) { if (historyUpdate.peerID == peerID) { if (historyUpdate.msgs[$scope.messageID]) { if ($scope.nav.hasNext) { $scope.nav.next() } else if ($scope.nav.hasPrev) { $scope.nav.prev() } else { return $modalInstance.dismiss() } } var newList = [] for (var i = 0; i < list.length; i++) { if (!historyUpdate.msgs[list[i]]) { newList.push(list[i]) } } list = newList } }) if ($scope.webpageID) { $scope.webpage = AppWebPagesManager.wrapForHistory($scope.webpageID) return } AppMessagesManager.getSearch(peerID, inputQuery, inputFilter, 0, 1000).then(function (searchCachedResult) { if (searchCachedResult.history.indexOf($scope.messageID) >= 0) { list = searchCachedResult.history maxID = list[list.length - 1] updatePrevNext() preloadPhotos(+1) } loadMore() }, loadMore) var jump = 0 function movePosition (sign) { var curIndex = list.indexOf($scope.messageID) var index = curIndex >= 0 ? curIndex + sign : 0 var curJump = ++jump var promise = index >= list.length ? loadMore() : $q.when() promise.then(function () { if (curJump != jump) { return } var messageID = list[index] var message = AppMessagesManager.getMessage(messageID) var photoID = message && message.media && ((message.media.photo && message.media.photo.id) || (message.media.webpage && message.media.webpage.photo && message.media.webpage.photo.id)) if (!photoID) { console.error('Invalid photo message', index, list, messageID, message) return } $scope.messageID = messageID $scope.photoID = photoID $scope.photo = AppPhotosManager.wrapForFull($scope.photoID) preloaded[$scope.messageID] = true updatePrevNext() if (sign > 0 && hasMore && list.indexOf(messageID) + 1 >= list.length) { loadMore() } else { preloadPhotos(sign) } }) } var loadingPromise = false function loadMore () { if (loadingPromise) return loadingPromise return loadingPromise = AppMessagesManager.getSearch(peerID, inputQuery, inputFilter, maxID).then(function (searchResult) { if (searchResult.history.length) { maxID = searchResult.history[searchResult.history.length - 1] list = list.concat(searchResult.history) hasMore = list.length < searchResult.count } else { hasMore = false } updatePrevNext(searchResult.count) loadingPromise = false if (searchResult.history.length) { return $q.reject() } preloadPhotos(+1) }) } }) .controller('UserpicModalController', function ($q, $scope, $rootScope, $modalInstance, MtpApiManager, AppPhotosManager, AppUsersManager, AppPeersManager, AppMessagesManager, ApiUpdatesManager, PeersSelectService, ErrorService) { $scope.photo = AppPhotosManager.wrapForFull($scope.photoID) $scope.photo.thumb = { location: AppPhotosManager.choosePhotoSize($scope.photo, 0, 0).location } $scope.nav = {} $scope.canForward = true var list = [$scope.photoID] var maxID = $scope.photoID var preloaded = {} var myID = 0 var hasMore = true updatePrevNext() AppPhotosManager.getUserPhotos($scope.userID, 0, 1000).then(function (userpicCachedResult) { if (userpicCachedResult.photos.indexOf($scope.photoID) >= 0) { list = userpicCachedResult.photos maxID = list[list.length - 1] } hasMore = list.length < userpicCachedResult.count updatePrevNext() }) MtpApiManager.getUserID().then(function (id) { myID = id $scope.canDelete = $scope.photo.user_id == myID }) var jump = 0 function movePosition (sign, deleteCurrent) { var curIndex = list.indexOf($scope.photoID) var index = curIndex >= 0 ? curIndex + sign : 0 var curJump = ++jump var promise = index >= list.length ? loadMore() : $q.when() promise.then(function () { if (curJump != jump) { return } $scope.photoID = list[index] $scope.photo = AppPhotosManager.wrapForFull($scope.photoID) $scope.photo.thumb = { location: AppPhotosManager.choosePhotoSize($scope.photo, 0, 0).location } var newCount if (deleteCurrent) { list.splice(curIndex, 1) newCount = $scope.count - 1 } updatePrevNext(newCount) preloaded[$scope.photoID] = true updatePrevNext() if (sign > 0 && hasMore && list.indexOf($scope.photoID) + 1 >= list.length) { loadMore() } else { preloadPhotos(sign) } }) } function preloadPhotos (sign) { var preloadOffsets = sign < 0 ? [-1, -2] : [1, 2] var index = list.indexOf($scope.photoID) angular.forEach(preloadOffsets, function (offset) { var photoID = list[index + offset] if (photoID !== undefined && preloaded[photoID] === undefined) { preloaded[photoID] = true AppPhotosManager.preloadPhoto(photoID) } }) } var loadingPromise = false function loadMore () { if (loadingPromise) return loadingPromise return loadingPromise = AppPhotosManager.getUserPhotos($scope.userID, maxID).then(function (userpicResult) { if (userpicResult.photos.length) { maxID = userpicResult.photos[userpicResult.photos.length - 1] list = list.concat(userpicResult.photos) hasMore = list.length < userpicResult.count } else { hasMore = false } updatePrevNext(userpicResult.count) loadingPromise = false if (userpicResult.photos.length) { return $q.reject() } preloadPhotos(+1) }) } function updatePrevNext (count) { var index = list.indexOf($scope.photoID) if (hasMore) { if (count) { $scope.count = Math.max(count, list.length) } } else { $scope.count = list.length } $scope.pos = $scope.count - index $scope.nav.hasNext = index > 0 $scope.nav.hasPrev = hasMore || index < list.length - 1 $scope.canDelete = $scope.photo.user_id == myID } $scope.nav.next = function () { if (!$scope.nav.hasNext) { return false } movePosition(-1) } $scope.nav.prev = function () { if (!$scope.nav.hasPrev) { return false } movePosition(+1) } $scope.forward = function () { PeersSelectService.selectPeer({confirm_type: 'FORWARD_PEER', canSend: true}).then(function (peerString) { var peerID = AppPeersManager.getPeerID(peerString) AppMessagesManager.sendOther(peerID, { _: 'inputMediaPhoto', id: { _: 'inputPhoto', id: $scope.photoID, access_hash: $scope.photo.access_hash } }) $rootScope.$broadcast('history_focus', {peerString: peerString}) }) } $scope['delete'] = function () { var photoID = $scope.photoID var myUser = AppUsersManager.getUser(myID) var onDeleted = function () { if (!$scope.nav.hasNext && !$scope.nav.hasPrev) { return $modalInstance.dismiss() } movePosition($scope.nav.hasNext ? -1 : +1, true) } ErrorService.confirm({type: 'PHOTO_DELETE'}).then(function () { if (myUser && myUser.photo && myUser.photo.photo_id == photoID) { MtpApiManager.invokeApi('photos.updateProfilePhoto', { id: {_: 'inputPhotoEmpty'} }).then(function (updateResult) { ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateUserPhoto', user_id: myID, date: tsNow(true), photo: updateResult, previous: true } }) onDeleted() }) } else { MtpApiManager.invokeApi('photos.deletePhotos', { id: [{_: 'inputPhoto', id: photoID, access_hash: 0}] }).then(onDeleted) } }) } $scope.download = function () { AppPhotosManager.downloadPhoto($scope.photoID) } }) .controller('ChatpicModalController', function ($q, $scope, $rootScope, $modalInstance, MtpApiManager, AppPhotosManager, AppChatsManager, AppPeersManager, AppMessagesManager, ApiUpdatesManager, PeersSelectService, ErrorService) { $scope.photo = AppPhotosManager.wrapForFull($scope.photoID) $scope.photo.thumb = { location: AppPhotosManager.choosePhotoSize($scope.photo, 0, 0).location } var chat = AppChatsManager.getChat($scope.chatID) var isChannel = AppChatsManager.isChannel($scope.chatID) $scope.canForward = true $scope.canDelete = isChannel ? chat.pFlags.creator : true $scope.forward = function () { PeersSelectService.selectPeer({confirm_type: 'FORWARD_PEER', canSend: true}).then(function (peerString) { var peerID = AppPeersManager.getPeerID(peerString) AppMessagesManager.sendOther(peerID, { _: 'inputMediaPhoto', id: { _: 'inputPhoto', id: $scope.photoID, access_hash: $scope.photo.access_hash } }) $rootScope.$broadcast('history_focus', {peerString: peerString}) }) } $scope['delete'] = function () { ErrorService.confirm({type: 'PHOTO_DELETE'}).then(function () { $scope.photo.updating = true var apiPromise if (AppChatsManager.isChannel($scope.chatID)) { apiPromise = MtpApiManager.invokeApi('channels.editPhoto', { channel: AppChatsManager.getChannelInput($scope.chatID), photo: {_: 'inputChatPhotoEmpty'} }) } else { apiPromise = MtpApiManager.invokeApi('messages.editChatPhoto', { chat_id: AppChatsManager.getChatInput($scope.chatID), photo: {_: 'inputChatPhotoEmpty'} }) } apiPromise.then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates) $modalInstance.dismiss() $rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString($scope.chatID)}) })['finally'](function () { $scope.photo.updating = false }) }) } $scope.download = function () { AppPhotosManager.downloadPhoto($scope.photoID) } }) .controller('VideoModalController', function ($scope, $rootScope, $modalInstance, PeersSelectService, AppMessagesManager, AppDocsManager, AppPeersManager, ErrorService) { $scope.video = AppDocsManager.wrapVideoForFull($scope.docID) $scope.progress = {enabled: false} $scope.player = {} $scope.forward = function () { var messageID = $scope.messageID PeersSelectService.selectPeer({canSend: true}).then(function (peerString) { $rootScope.$broadcast('history_focus', { peerString: peerString, attachment: { _: 'fwd_messages', id: [messageID] } }) }) } $scope['delete'] = function () { var messageID = $scope.messageID ErrorService.confirm({type: 'MESSAGE_DELETE'}).then(function () { AppMessagesManager.deleteMessages([messageID]) }) } $scope.download = function () { AppDocsManager.saveDocFile($scope.docID) } $scope.$on('history_delete', function (e, historyUpdate) { if (historyUpdate && historyUpdate.msgs && historyUpdate.msgs[$scope.messageID]) { $modalInstance.dismiss() } }) }) .controller('DocumentModalController', function ($scope, $rootScope, $modalInstance, PeersSelectService, AppMessagesManager, AppDocsManager, AppPeersManager, ErrorService) { $scope.document = AppDocsManager.wrapForHistory($scope.docID) $scope.forward = function () { var messageID = $scope.messageID PeersSelectService.selectPeer({canSend: true}).then(function (peerString) { $rootScope.$broadcast('history_focus', { peerString: peerString, attachment: { _: 'fwd_messages', id: [messageID] } }) }) } $scope['delete'] = function () { var messageID = $scope.messageID ErrorService.confirm({type: 'MESSAGE_DELETE'}).then(function () { AppMessagesManager.deleteMessages([messageID]) }) } $scope.download = function () { AppDocsManager.saveDocFile($scope.docID) } $scope.$on('history_delete', function (e, historyUpdate) { if (historyUpdate && historyUpdate.msgs && historyUpdate.msgs[$scope.messageID]) { $modalInstance.dismiss() } }) }) .controller('EmbedModalController', function ($q, $scope, $rootScope, $modalInstance, AppPhotosManager, AppMessagesManager, AppPeersManager, AppWebPagesManager, PeersSelectService, ErrorService) { $scope.webpage = AppWebPagesManager.wrapForFull($scope.webpageID) $scope.nav = {} $scope.forward = function () { var messageID = $scope.messageID PeersSelectService.selectPeer({canSend: true}).then(function (peerString) { $rootScope.$broadcast('history_focus', { peerString: peerString, attachment: { _: 'fwd_messages', id: [messageID] } }) }) } $scope['delete'] = function () { var messageID = $scope.messageID ErrorService.confirm({type: 'MESSAGE_DELETE'}).then(function () { AppMessagesManager.deleteMessages([messageID]) }) } }) .controller('GameModalController', function ($q, $scope, $rootScope, $modalInstance, AppPhotosManager, AppMessagesManager, AppPeersManager, AppGamesManager, PeersSelectService, ErrorService) { $scope.game = AppGamesManager.wrapForFull($scope.gameID, $scope.messageID, $scope.embedUrl) var messageID = $scope.messageID var message = AppMessagesManager.getMessage(messageID) $scope.botID = message.viaBotID || message.fromID $scope.nav = {} $scope.forward = function (withMyScore) { PeersSelectService.selectPeer({canSend: true, confirm_type: 'INVITE_TO_GAME'}).then(function (peerString) { var peerID = AppPeersManager.getPeerID(peerString) AppMessagesManager.forwardMessages(peerID, [messageID], { withMyScore: withMyScore }).then(function () { $rootScope.$broadcast('history_focus', { peerString: peerString }) }) }) } $scope.$on('game_frame_event', function (e, eventData) { if (eventData.eventType == 'share_score') { $scope.forward(true) } }) }) .controller('UserModalController', function ($scope, $location, $rootScope, $modalInstance, AppProfileManager, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) { var peerString = AppUsersManager.getUserString($scope.userID) $scope.user = AppUsersManager.getUser($scope.userID) $scope.blocked = false $scope.settings = {notifications: true} AppProfileManager.getProfile($scope.userID, $scope.override).then(function (userFull) { $scope.blocked = userFull.pFlags.blocked $scope.bot_info = userFull.bot_info $scope.rAbout = userFull.rAbout NotificationsManager.getPeerMuted($scope.userID).then(function (muted) { $scope.settings.notifications = !muted $scope.$watch('settings.notifications', function (newValue, oldValue) { if (newValue === oldValue) { return false } NotificationsManager.getPeerSettings($scope.userID).then(function (settings) { settings.mute_until = newValue ? 0 : 2000000000 NotificationsManager.updatePeerSettings($scope.userID, settings) }) }) }) }) $scope.goToHistory = function () { $rootScope.$broadcast('history_focus', {peerString: peerString}) } $scope.flushHistory = function (justClear) { ErrorService.confirm({type: justClear ? 'HISTORY_FLUSH' : 'HISTORY_FLUSH_AND_DELETE'}).then(function () { AppMessagesManager.flushHistory($scope.userID, justClear).then(function () { if (justClear) { $scope.goToHistory() } else { $modalInstance.close() $location.url('/im') } }) }) } $scope.importContact = function (edit) { var scope = $rootScope.$new() scope.importContact = { phone: $scope.user.phone, first_name: $scope.user.first_name, last_name: $scope.user.last_name } $modal.open({ templateUrl: templateUrl(edit ? 'edit_contact_modal' : 'import_contact_modal'), controller: 'ImportContactModalController', windowClass: 'md_simple_modal_window mobile_modal', scope: scope }).result.then(function (foundUserID) { if ($scope.userID == foundUserID) { $scope.user = AppUsersManager.getUser($scope.userID) } }) } $scope.deleteContact = function () { AppUsersManager.deleteContacts([$scope.userID]).then(function () { $scope.user = AppUsersManager.getUser($scope.userID) }) } $scope.inviteToGroup = function () { PeersSelectService.selectPeer({ confirm_type: 'INVITE_TO_GROUP', noUsers: true }).then(function (peerString) { var peerID = AppPeersManager.getPeerID(peerString) var chatID = peerID < 0 ? -peerID : 0 AppMessagesManager.startBot($scope.user.id, chatID).then(function () { $rootScope.$broadcast('history_focus', {peerString: peerString}) }) }) } $scope.sendCommand = function (command) { AppMessagesManager.sendText($scope.userID, '/' + command) $rootScope.$broadcast('history_focus', { peerString: peerString }) } $scope.toggleBlock = function (block) { MtpApiManager.invokeApi(block ? 'contacts.block' : 'contacts.unblock', { id: AppUsersManager.getUserInput($scope.userID) }).then(function () { $scope.blocked = block }) } $scope.shareContact = function () { PeersSelectService.selectPeer({confirm_type: 'SHARE_CONTACT_PEER', canSend: true}).then(function (peerString) { var peerID = AppPeersManager.getPeerID(peerString) AppMessagesManager.sendOther(peerID, { _: 'inputMediaContact', phone_number: $scope.user.phone, first_name: $scope.user.first_name, last_name: $scope.user.last_name, user_id: $scope.user.id }) $rootScope.$broadcast('history_focus', {peerString: peerString}) }) } }) .controller('ChatModalController', function ($scope, $modalInstance, $location, $timeout, $rootScope, $modal, AppUsersManager, AppChatsManager, AppProfileManager, AppPhotosManager, MtpApiManager, MtpApiFileManager, NotificationsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, ContactsSelectService, ErrorService) { $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, {}) $scope.settings = {notifications: true} $scope.maxParticipants = 200 AppProfileManager.getChatFull($scope.chatID).then(function (chatFull) { $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, chatFull) $scope.$broadcast('ui_height') $scope.needMigrate = $scope.chatFull && $scope.chatFull.participants && $scope.chatFull.participants.participants && $scope.chatFull.participants.participants.length >= 200 if (Config.Modes.test || Config.Modes.debug) { $scope.needMigrate = true } NotificationsManager.getPeerMuted(-$scope.chatID).then(function (muted) { $scope.settings.notifications = !muted $scope.$watch('settings.notifications', function (newValue, oldValue) { if (newValue === oldValue) { return false } NotificationsManager.getPeerSettings(-$scope.chatID).then(function (settings) { if (newValue) { settings.mute_until = 0 } else { settings.mute_until = 2000000000 } NotificationsManager.updatePeerSettings(-$scope.chatID, settings) }) }) }) }) function onChatUpdated (updates) { ApiUpdatesManager.processUpdateMessage(updates) $rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString}) } $scope.leaveGroup = function () { ErrorService.confirm({type: 'HISTORY_LEAVE_AND_FLUSH'}).then(function () { MtpApiManager.invokeApi('messages.deleteChatUser', { chat_id: AppChatsManager.getChatInput($scope.chatID), user_id: {_: 'inputUserSelf'} }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates) AppMessagesManager.flushHistory(-$scope.chatID).then(function () { $modalInstance.close() $location.url('/im') }) }) }) } $scope.inviteToGroup = function () { var disabled = [] angular.forEach($scope.chatFull.participants.participants, function (participant) { disabled.push(participant.user_id) }) ContactsSelectService.selectContacts({disabled: disabled}).then(function (userIDs) { angular.forEach(userIDs, function (userID) { MtpApiManager.invokeApi('messages.addChatUser', { chat_id: AppChatsManager.getChatInput($scope.chatID), user_id: AppUsersManager.getUserInput(userID), fwd_limit: 100 }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates) }) }) $rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString}) }) } $scope.migrateToSuperGroup = function () { ErrorService.confirm({type: 'SUPERGROUP_MIGRATE'}).then(function () { MtpApiManager.invokeApi('messages.migrateChat', { chat_id: AppChatsManager.getChatInput($scope.chatID) }).then(onChatUpdated) }) } $scope.kickFromGroup = function (userID) { MtpApiManager.invokeApi('messages.deleteChatUser', { chat_id: AppChatsManager.getChatInput($scope.chatID), user_id: AppUsersManager.getUserInput(userID) }).then(onChatUpdated) } $scope.flushHistory = function (justClear) { ErrorService.confirm({type: justClear ? 'HISTORY_FLUSH' : 'HISTORY_FLUSH_AND_DELETE'}).then(function () { AppMessagesManager.flushHistory(-$scope.chatID, justClear).then(function () { if (justClear) { $rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString}) } else { $modalInstance.close() $location.url('/im') } }) }) } $scope.inviteViaLink = function () { var scope = $rootScope.$new() scope.chatID = $scope.chatID $modal.open({ templateUrl: templateUrl('chat_invite_link_modal'), controller: 'ChatInviteLinkModalController', scope: scope, windowClass: 'md_simple_modal_window' }) } $scope.photo = {} $scope.$watch('photo.file', onPhotoSelected) function onPhotoSelected (photo) { if (!photo || !photo.type || photo.type.indexOf('image') !== 0) { return } $scope.photo.updating = true MtpApiFileManager.uploadFile(photo).then(function (inputFile) { return MtpApiManager.invokeApi('messages.editChatPhoto', { chat_id: AppChatsManager.getChatInput($scope.chatID), photo: { _: 'inputChatUploadedPhoto', file: inputFile } }).then(onChatUpdated) })['finally'](function () { $scope.photo.updating = false }) } $scope.deletePhoto = function () { $scope.photo.updating = true MtpApiManager.invokeApi('messages.editChatPhoto', { chat_id: AppChatsManager.getChatInput($scope.chatID), photo: {_: 'inputChatPhotoEmpty'} }).then(onChatUpdated)['finally'](function () { $scope.photo.updating = false }) } $scope.editTitle = function () { var scope = $rootScope.$new() scope.chatID = $scope.chatID $modal.open({ templateUrl: templateUrl('chat_edit_modal'), controller: 'ChatEditModalController', scope: scope, windowClass: 'md_simple_modal_window mobile_modal' }) } $scope.hasRights = function (action) { return AppChatsManager.hasRights($scope.chatID, action) } }) .controller('ChannelModalController', function ($scope, $timeout, $rootScope, $modal, AppUsersManager, AppChatsManager, AppProfileManager, AppPhotosManager, MtpApiManager, MtpApiFileManager, NotificationsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager, ContactsSelectService, ErrorService) { $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, {}) $scope.settings = {notifications: true} $scope.isMegagroup = AppChatsManager.isMegagroup($scope.chatID) AppProfileManager.getChannelFull($scope.chatID, true).then(function (chatFull) { $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, chatFull) $scope.$broadcast('ui_height') NotificationsManager.getPeerMuted(-$scope.chatID).then(function (muted) { $scope.settings.notifications = !muted $scope.$watch('settings.notifications', function (newValue, oldValue) { if (newValue === oldValue) { return false } NotificationsManager.getPeerSettings(-$scope.chatID).then(function (settings) { if (newValue) { settings.mute_until = 0 } else { settings.mute_until = 2000000000 } NotificationsManager.updatePeerSettings(-$scope.chatID, settings) }) }) }) if ($scope.chatFull.chat && $scope.chatFull.chat.pFlags.creator && $scope.chatFull.exported_invite && $scope.chatFull.exported_invite._ == 'chatInviteEmpty') { AppProfileManager.getChatInviteLink($scope.chatID, true).then(function (link) { $scope.chatFull.exported_invite = {_: 'chatInviteExported', link: link} }) } }) AppProfileManager.getChannelParticipants($scope.chatID).then(function (participants) { $scope.participants = AppChatsManager.wrapParticipants($scope.chatID, participants) $scope.$broadcast('ui_height') }) function onChatUpdated (updates) { ApiUpdatesManager.processUpdateMessage(updates) $rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString}) } $scope.leaveChannel = function () { return ErrorService.confirm({type: $scope.isMegagroup ? 'MEGAGROUP_LEAVE' : 'CHANNEL_LEAVE'}).then(function () { MtpApiManager.invokeApi('channels.leaveChannel', { channel: AppChatsManager.getChannelInput($scope.chatID) }).then(onChatUpdated) }) } $scope.deleteChannel = function () { return ErrorService.confirm({type: $scope.isMegagroup ? 'MEGAGROUP_DELETE' : 'CHANNEL_DELETE'}).then(function () { MtpApiManager.invokeApi('channels.deleteChannel', { channel: AppChatsManager.getChannelInput($scope.chatID) }).then(onChatUpdated) }) } $scope.flushHistory = function () { ErrorService.confirm({type: 'HISTORY_FLUSH'}).then(function () { AppMessagesManager.flushHistory(-$scope.chatID).then(function () { $rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString}) }) }) } $scope.joinChannel = function () { MtpApiManager.invokeApi('channels.joinChannel', { channel: AppChatsManager.getChannelInput($scope.chatID) }).then(onChatUpdated) } $scope.inviteToChannel = function () { var disabled = [] angular.forEach(($scope.chatFull.participants || {}).participants || [], function (participant) { disabled.push(participant.user_id) }) ContactsSelectService.selectContacts({disabled: disabled}).then(function (userIDs) { var inputUsers = [] angular.forEach(userIDs, function (userID) { inputUsers.push(AppUsersManager.getUserInput(userID)) }) MtpApiManager.invokeApi('channels.inviteToChannel', { channel: AppChatsManager.getChannelInput($scope.chatID), users: inputUsers }).then(onChatUpdated) }) } $scope.kickFromChannel = function (userID) { MtpApiManager.invokeApi('channels.kickFromChannel', { channel: AppChatsManager.getChannelInput($scope.chatID), user_id: AppUsersManager.getUserInput(userID), kicked: true }).then(onChatUpdated) } $scope.shareLink = function ($event) { var scope = $rootScope.$new() scope.chatID = $scope.chatID $modal.open({ templateUrl: templateUrl('chat_invite_link_modal'), controller: 'ChatInviteLinkModalController', scope: scope, windowClass: 'md_simple_modal_window' }) return cancelEvent($event) } $scope.photo = {} $scope.$watch('photo.file', onPhotoSelected) function onPhotoSelected (photo) { if (!photo || !photo.type || photo.type.indexOf('image') !== 0) { return } $scope.photo.updating = true MtpApiFileManager.uploadFile(photo).then(function (inputFile) { return MtpApiManager.invokeApi('channels.editPhoto', { channel: AppChatsManager.getChannelInput($scope.chatID), photo: { _: 'inputChatUploadedPhoto', file: inputFile } }).then(onChatUpdated) })['finally'](function () { $scope.photo.updating = false }) } $scope.deletePhoto = function () { $scope.photo.updating = true MtpApiManager.invokeApi('channels.editPhoto', { channel: AppChatsManager.getChannelInput($scope.chatID), photo: {_: 'inputChatPhotoEmpty'} }).then(onChatUpdated)['finally'](function () { $scope.photo.updating = false }) } $scope.editChannel = function () { var scope = $rootScope.$new() scope.chatID = $scope.chatID $modal.open({ templateUrl: templateUrl($scope.isMegagroup ? 'megagroup_edit_modal' : 'channel_edit_modal'), controller: 'ChannelEditModalController', scope: scope, windowClass: 'md_simple_modal_window mobile_modal' }) } $scope.goToHistory = function () { $rootScope.$broadcast('history_focus', {peerString: $scope.chatFull.peerString}) } $scope.hasRights = function (action) { return AppChatsManager.hasRights($scope.chatID, action) } }) .controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, PasswordManager, ApiUpdatesManager, ChangelogNotifyService, LayoutSwitchService, WebPushApiManager, AppRuntimeManager, ErrorService, _) { $scope.profile = {} $scope.photo = {} $scope.version = Config.App.version MtpApiManager.getUserID().then(function (id) { $scope.profile = AppUsersManager.getUser(id) }) MtpApiManager.invokeApi('users.getFullUser', { id: {_: 'inputUserSelf'} }).then(function (userFullResult) { AppUsersManager.saveApiUser(userFullResult.user) if (userFullResult.profile_photo) { AppPhotosManager.savePhoto(userFullResult.profile_photo, { user_id: userFullResult.user.id }) } }) $scope.notify = {volume: 0.5} $scope.send = {} $scope.$watch('photo.file', onPhotoSelected) $scope.password = {_: 'account.noPassword'} updatePasswordState() var updatePasswordTimeout = false var stopped = false $scope.changePassword = function (options) { options = options || {} if (options.action == 'cancel_email') { return ErrorService.confirm({type: 'PASSWORD_ABORT_SETUP'}).then(function () { PasswordManager.updateSettings($scope.password, {email: ''}).then(updatePasswordState) }) } var scope = $rootScope.$new() scope.password = $scope.password angular.extend(scope, options) var modal = $modal.open({ scope: scope, templateUrl: templateUrl('password_update_modal'), controller: 'PasswordUpdateModalController', windowClass: 'md_simple_modal_window mobile_modal' }) modal.result['finally'](updatePasswordState) } $scope.showSessions = function () { $modal.open({ templateUrl: templateUrl('sessions_list_modal'), controller: 'SessionsListModalController', windowClass: 'md_simple_modal_window mobile_modal' }) } function updatePasswordState () { $timeout.cancel(updatePasswordTimeout) updatePasswordTimeout = false PasswordManager.getState().then(function (result) { $scope.password = result if (result._ == 'account.noPassword' && result.email_unconfirmed_pattern && !stopped) { updatePasswordTimeout = $timeout(updatePasswordState, 5000) } }) } $scope.$on('$destroy', function () { $timeout.cancel(updatePasswordTimeout) stopped = true }) function onPhotoSelected (photo) { if (!photo || !photo.type || photo.type.indexOf('image') !== 0) { return } $scope.photo.updating = true MtpApiFileManager.uploadFile(photo).then(function (inputFile) { MtpApiManager.invokeApi('photos.uploadProfilePhoto', { file: inputFile, caption: '', geo_point: {_: 'inputGeoPointEmpty'} }).then(function (updateResult) { AppUsersManager.saveApiUsers(updateResult.users) MtpApiManager.getUserID().then(function (id) { AppPhotosManager.savePhoto(updateResult.photo, { user_id: id }) ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateUserPhoto', user_id: id, date: tsNow(true), photo: AppUsersManager.getUser(id).photo, previous: true } }) $scope.photo = {} }) }) })['finally'](function () { delete $scope.photo.updating }) } $scope.deletePhoto = function () { $scope.photo.updating = true MtpApiManager.invokeApi('photos.updateProfilePhoto', { id: {_: 'inputPhotoEmpty'} }).then(function (updateResult) { MtpApiManager.getUserID().then(function (id) { ApiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { _: 'updateUserPhoto', user_id: id, date: tsNow(true), photo: updateResult, previous: true } }) $scope.photo = {} }) })['finally'](function () { delete $scope.photo.updating }) } $scope.editProfile = function () { $modal.open({ templateUrl: templateUrl('profile_edit_modal'), controller: 'ProfileEditModalController', windowClass: 'md_simple_modal_window mobile_modal' }) } $scope.changeUsername = function () { $modal.open({ templateUrl: templateUrl('username_edit_modal'), controller: 'UsernameEditModalController', windowClass: 'md_simple_modal_window mobile_modal' }) } $scope.terminateSessions = function () { ErrorService.confirm({type: 'TERMINATE_SESSIONS'}).then(function () { MtpApiManager.invokeApi('auth.resetAuthorizations', {}) }) } Storage.get('notify_nodesktop', 'send_ctrlenter', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush').then(function (settings) { $scope.notify.desktop = !settings[0] $scope.send.enter = settings[1] ? '' : '1' $scope.notify.pushAvailable = WebPushApiManager.isAvailable $scope.notify.push = !settings[5] if (settings[2] !== false) { $scope.notify.volume = settings[2] > 0 && settings[2] <= 1.0 ? settings[2] : 0 } else { $scope.notify.volume = 0.5 } $scope.notify.canVibrate = NotificationsManager.getVibrateSupport() $scope.notify.vibrate = !settings[3] $scope.notify.preview = !settings[4] $scope.notify.volumeOf4 = function () { return 1 + Math.ceil(($scope.notify.volume - 0.1) / 0.33) } $scope.toggleSound = function () { if ($scope.notify.volume) { $scope.notify.volume = 0 } else { $scope.notify.volume = 0.5 } } var testSoundPromise $scope.$watch('notify.volume', function (newValue, oldValue) { if (newValue !== oldValue) { Storage.set({notify_volume: newValue}) $rootScope.$broadcast('settings_changed') NotificationsManager.clear() if (testSoundPromise) { $timeout.cancel(testSoundPromise) } testSoundPromise = $timeout(function () { NotificationsManager.testSound(newValue) }, 500) } }) $scope.toggleDesktop = function () { $scope.notify.desktop = !$scope.notify.desktop if ($scope.notify.desktop) { Storage.remove('notify_nodesktop') } else { Storage.set({notify_nodesktop: true}) } $rootScope.$broadcast('settings_changed') } $scope.togglePush = function () { $scope.notify.push = !$scope.notify.push if ($scope.notify.push) { Storage.remove('notify_nopush') } else { Storage.set({notify_nopush: true}) } $rootScope.$broadcast('settings_changed') } $scope.togglePreview = function () { $scope.notify.preview = !$scope.notify.preview if ($scope.notify.preview) { Storage.remove('notify_nopreview') } else { Storage.set({notify_nopreview: true}) } $rootScope.$broadcast('settings_changed') } $scope.toggleVibrate = function () { $scope.notify.vibrate = !$scope.notify.vibrate if ($scope.notify.vibrate) { Storage.remove('notify_novibrate') } else { Storage.set({notify_novibrate: true}) } $rootScope.$broadcast('settings_changed') } $scope.toggleCtrlEnter = function (newValue) { $scope.send.enter = newValue if ($scope.send.enter) { Storage.remove('send_ctrlenter') } else { Storage.set({send_ctrlenter: true}) } $rootScope.$broadcast('settings_changed') } }) $scope.openChangelog = function () { ChangelogNotifyService.showChangelog(false) } $scope.logOut = function () { ErrorService.confirm({type: 'LOGOUT'}).then(function () { MtpApiManager.logOut().then(function () { location.hash = '/login' AppRuntimeManager.reload() }) }) } $scope.switchBackToDesktop = Config.Mobile && !Config.Navigator.mobile $scope.switchToDesktop = function () { LayoutSwitchService.switchLayout(false) } }) .controller('ChangelogModalController', function ($scope, $modal) { $scope.currentVersion = Config.App.version if (!$scope.lastVersion) { var versionParts = $scope.currentVersion.split('.') $scope.lastVersion = versionParts[0] + '.' + versionParts[1] + '.' + Math.max(0, versionParts[2] - 1) } $scope.changelogHidden = false $scope.changelogShown = false $scope.canShowVersion = function (curVersion) { if ($scope.changelogShown) { return true } var show = versionCompare(curVersion, $scope.lastVersion) >= 0 if (!show) { $scope.changelogHidden = true } return show } $scope.showAllVersions = function () { $scope.changelogShown = true $scope.changelogHidden = false $scope.$emit('ui_height') $scope.$broadcast('ui_height') } $scope.changeUsername = function () { $modal.open({ templateUrl: templateUrl('username_edit_modal'), controller: 'UsernameEditModalController', windowClass: 'md_simple_modal_window mobile_modal' }) } }) .controller('ProfileEditModalController', function ($scope, $modalInstance, AppUsersManager, MtpApiManager) { $scope.profile = {} $scope.error = {} MtpApiManager.getUserID().then(function (id) { var user = AppUsersManager.getUser(id) $scope.profile = { first_name: user.first_name, last_name: user.last_name } }) $scope.updateProfile = function () { $scope.profile.updating = true var flags = (1 << 0) | (1 << 1) MtpApiManager.invokeApi('account.updateProfile', { flags: flags, first_name: $scope.profile.first_name || '', last_name: $scope.profile.last_name || '' }).then(function (user) { $scope.error = {} AppUsersManager.saveApiUser(user) $modalInstance.close() }, function (error) { switch (error.type) { case 'FIRSTNAME_INVALID': $scope.error = {field: 'first_name'} error.handled = true break case 'LASTNAME_INVALID': $scope.error = {field: 'last_name'} error.handled = true break case 'NAME_NOT_MODIFIED': error.handled = true $modalInstance.close() break } })['finally'](function () { delete $scope.profile.updating }) } }) .controller('UsernameEditModalController', function ($scope, $modalInstance, AppUsersManager, MtpApiManager) { $scope.profile = {} $scope.error = {} MtpApiManager.getUserID().then(function (id) { var user = AppUsersManager.getUser(id) $scope.profile = { username: user.username } }) $scope.updateUsername = function () { $scope.profile.updating = true MtpApiManager.invokeApi('account.updateUsername', { username: $scope.profile.username || '' }).then(function (user) { $scope.checked = {} AppUsersManager.saveApiUser(user) $modalInstance.close() }, function (error) { switch (error.type) { case 'USERNAME_NOT_MODIFIED': error.handled = true $modalInstance.close() break } })['finally'](function () { delete $scope.profile.updating }) } $scope.$watch('profile.username', function (newVal) { if (!newVal || !newVal.length) { $scope.checked = {} return } MtpApiManager.invokeApi('account.checkUsername', { username: newVal }).then(function (valid) { if ($scope.profile.username !== newVal) { return } if (valid) { $scope.checked = {success: true} } else { $scope.checked = {error: true} } }, function (error) { if ($scope.profile.username !== newVal) { return } switch (error.type) { case 'USERNAME_INVALID': $scope.checked = {error: true} error.handled = true break } }) }) }) .controller('SessionsListModalController', function ($scope, $q, $timeout, _, MtpApiManager, ErrorService, $modalInstance) { $scope.slice = {limit: 20, limitDelta: 20} var updateSessionsTimeout = false var stopped = false function updateSessions () { $timeout.cancel(updateSessionsTimeout) MtpApiManager.invokeApi('account.getAuthorizations').then(function (result) { $scope.sessionsLoaded = true $scope.authorizations = result.authorizations var authorization for (var i = 0, len = $scope.authorizations.length; i < len; i++) { authorization = $scope.authorizations[i] authorization.current = (authorization.flags & 1) == 1 } $scope.authorizations.sort(function (sA, sB) { if (sA.current) { return -1 } if (sB.current) { return 1 } return sB.date_active - sA.date_active }) if (!stopped) { updateSessionsTimeout = $timeout(updateSessions, 5000) } }) } $scope.terminateSession = function (hash) { ErrorService.confirm({type: 'TERMINATE_SESSION'}).then(function () { MtpApiManager.invokeApi('account.resetAuthorization', {hash: hash}).then(updateSessions) }) } $scope.terminateAllSessions = function () { ErrorService.confirm({type: 'TERMINATE_SESSIONS'}).then(function () { MtpApiManager.invokeApi('auth.resetAuthorizations', {}) }) } updateSessions() $scope.$on('apiUpdate', function (e, update) { if (update._ == 'updateNewAuthorization') { updateSessions() } }) $scope.$on('$destroy', function () { $timeout.cancel(updateSessionsTimeout) stopped = true }) }) .controller('PasswordUpdateModalController', function ($scope, $q, _, PasswordManager, MtpApiManager, ErrorService, $modalInstance) { $scope.passwordSettings = {} $scope.updatePassword = function () { delete $scope.passwordSettings.error_field var confirmPromise if ($scope.action == 'disable') { confirmPromise = $q.when() } else { if (!$scope.passwordSettings.new_password) { $scope.passwordSettings.error_field = 'new_password' $scope.$broadcast('new_password_focus') return false } if ($scope.passwordSettings.new_password != $scope.passwordSettings.confirm_password) { $scope.passwordSettings.error_field = 'confirm_password' $scope.$broadcast('confirm_password_focus') return false } confirmPromise = $scope.passwordSettings.email ? $q.when() : ErrorService.confirm({type: 'RECOVERY_EMAIL_EMPTY'}) } $scope.passwordSettings.loading = true confirmPromise.then(function () { PasswordManager.updateSettings($scope.password, { cur_password: $scope.passwordSettings.cur_password || '', new_password: $scope.passwordSettings.new_password, email: $scope.passwordSettings.email, hint: $scope.passwordSettings.hint }).then(function (result) { delete $scope.passwordSettings.loading $modalInstance.close(true) if ($scope.action == 'disable') { ErrorService.alert( _('error_modal_password_disabled_title_raw'), _('error_modal_password_disabled_descripion_raw') ) } else { ErrorService.alert( _('error_modal_password_success_title_raw'), _('error_modal_password_success_descripion_raw') ) } }, function (error) { switch (error.type) { case 'PASSWORD_HASH_INVALID': case 'NEW_PASSWORD_BAD': $scope.passwordSettings.error_field = 'cur_password' error.handled = true $scope.$broadcast('cur_password_focus') break case 'NEW_PASSWORD_BAD': $scope.passwordSettings.error_field = 'new_password' error.handled = true break case 'EMAIL_INVALID': $scope.passwordSettings.error_field = 'email' error.handled = true break case 'EMAIL_UNCONFIRMED': ErrorService.alert( _('error_modal_email_unconfirmed_title_raw'), _('error_modal_email_unconfirmed_descripion_raw') ) $modalInstance.close(true) error.handled = true break } delete $scope.passwordSettings.loading }) }) } switch ($scope.action) { case 'disable': $scope.passwordSettings.new_password = '' break case 'create': onContentLoaded(function () { $scope.$broadcast('new_password_focus') }) break } $scope.$watch('passwordSettings.new_password', function (newValue) { var len = (newValue && newValue.length) || 0 if (!len) { $scope.passwordSettings.hint = '' } else if (len <= 3) { $scope.passwordSettings.hint = '***' } else { $scope.passwordSettings.hint = newValue.charAt(0) + (new Array(len - 1)).join('*') + newValue.charAt(len - 1) } $scope.$broadcast('value_updated') }) }) .controller('PasswordRecoveryModalController', function ($scope, $q, _, PasswordManager, MtpApiManager, ErrorService, $modalInstance) { $scope.checkCode = function () { $scope.recovery.updating = true PasswordManager.recover($scope.recovery.code, $scope.options).then(function (result) { ErrorService.alert( _('error_modal_password_disabled_title_raw'), _('error_modal_password_disabled_descripion_raw') ) $modalInstance.close(result) }, function (error) { delete $scope.recovery.updating switch (error.type) { case 'CODE_EMPTY': case 'CODE_INVALID': $scope.recovery.error_field = 'code' error.handled = true break case 'PASSWORD_EMPTY': case 'PASSWORD_RECOVERY_NA': case 'PASSWORD_RECOVERY_EXPIRED': $modalInstance.dismiss() error.handled = true break } }) } }) .controller('ContactsModalController', function ($scope, $rootScope, $timeout, $modal, $modalInstance, MtpApiManager, AppUsersManager, ErrorService) { $scope.contacts = [] $scope.foundPeers = [] $scope.search = {} $scope.slice = {limit: 20, limitDelta: 20} var jump = 0 var i resetSelected() $scope.disabledContacts = {} if ($scope.disabled) { for (i = 0; i < $scope.disabled.length; i++) { $scope.disabledContacts[$scope.disabled[i]] = true } } if ($scope.selected) { for (i = 0; i < $scope.selected.length; i++) { if (!$scope.selectedContacts[$scope.selected[i]]) { $scope.selectedContacts[$scope.selected[i]] = true $scope.selectedCount++ } } } function resetSelected () { $scope.selectedContacts = {} $scope.selectedCount = 0 } function updateContacts (query) { var curJump = ++jump var doneIDs = [] AppUsersManager.getContacts(query).then(function (contactsList) { if (curJump != jump) return $scope.contacts = [] $scope.slice.limit = 20 angular.forEach(contactsList, function (userID) { var contact = { userID: userID, user: AppUsersManager.getUser(userID) } doneIDs.push(userID) $scope.contacts.push(contact) }) $scope.contactsEmpty = query ? false : !$scope.contacts.length $scope.$broadcast('contacts_change') }) if (query && query.length >= 5) { $timeout(function () { if (curJump != jump) return MtpApiManager.invokeApi('contacts.search', {q: query, limit: 10}).then(function (result) { AppUsersManager.saveApiUsers(result.users) if (curJump != jump) return angular.forEach(result.results, function (contactFound) { var userID = contactFound.user_id if (doneIDs.indexOf(userID) != -1) return $scope.contacts.push({ userID: userID, user: AppUsersManager.getUser(userID), peerString: AppUsersManager.getUserString(userID), found: true }) }) }, function (error) { if (error.code == 400) { error.handled = true } }) }, 500) } } $scope.$watch('search.query', updateContacts) $scope.$on('contacts_update', function () { updateContacts(($scope.search && $scope.search.query) || '') }) $scope.toggleEdit = function (enabled) { $scope.action = enabled ? 'edit' : '' $scope.multiSelect = enabled resetSelected() } $scope.contactSelect = function (userID) { if ($scope.disabledContacts[userID]) { return false } if (!$scope.multiSelect) { return $modalInstance.close(userID) } if ($scope.selectedContacts[userID]) { delete $scope.selectedContacts[userID] $scope.selectedCount-- } else { $scope.selectedContacts[userID] = true $scope.selectedCount++ } } $scope.submitSelected = function () { if ($scope.selectedCount > 0) { var selectedUserIDs = [] angular.forEach($scope.selectedContacts, function (t, userID) { selectedUserIDs.push(userID) }) return $modalInstance.close(selectedUserIDs) } } $scope.deleteSelected = function () { if ($scope.selectedCount > 0) { var selectedUserIDs = [] angular.forEach($scope.selectedContacts, function (t, userID) { selectedUserIDs.push(userID) }) AppUsersManager.deleteContacts(selectedUserIDs).then(function () { $scope.toggleEdit(false) }) } } $scope.importContact = function () { AppUsersManager.openImportContact().then(function (foundContact) { if (foundContact) { $rootScope.$broadcast('history_focus', { peerString: AppUsersManager.getUserString(foundContact) }) } }) } }) .controller('PeerSelectController', function ($scope, $modalInstance, $q, AppPeersManager, ErrorService) { $scope.selectedPeers = {} $scope.selectedPeerIDs = [] $scope.selectedCount = 0 if ($scope.shareLinkPromise) { $scope.shareLink = {loading: true} $scope.shareLinkPromise.then(function (url) { $scope.shareLink = {url: url} }, function () { delete $scope.shareLink }) } $scope.dialogSelect = function (peerString) { var peerID if (!$scope.multiSelect) { var promise if ($scope.confirm_type) { peerID = AppPeersManager.getPeerID(peerString) var peerData = AppPeersManager.getPeer(peerID) promise = ErrorService.confirm({ type: $scope.confirm_type, peer_id: peerID, peer_data: peerData }) } else { promise = $q.when() } promise.then(function () { $modalInstance.close(peerString) }) return } peerID = AppPeersManager.getPeerID(peerString) if ($scope.selectedPeers[peerID]) { delete $scope.selectedPeers[peerID] $scope.selectedCount-- var pos = $scope.selectedPeerIDs.indexOf(peerID) if (pos >= 0) { $scope.selectedPeerIDs.splice(pos, 1) } } else { $scope.selectedPeers[peerID] = AppPeersManager.getPeer(peerID) $scope.selectedCount++ $scope.selectedPeerIDs.unshift(peerID) } } $scope.submitSelected = function () { if ($scope.selectedCount > 0) { var selectedPeerStrings = [] angular.forEach($scope.selectedPeers, function (t, peerID) { selectedPeerStrings.push(AppPeersManager.getPeerString(peerID)) }) return $modalInstance.close(selectedPeerStrings) } } $scope.toggleSearch = function () { $scope.$broadcast('dialogs_search_toggle') } }) .controller('ChatCreateModalController', function ($scope, $modalInstance, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, ApiUpdatesManager) { $scope.group = {name: ''} $scope.createGroup = function () { if (!$scope.group.name) { return } $scope.group.creating = true var inputUsers = [] angular.forEach($scope.userIDs, function (userID) { inputUsers.push(AppUsersManager.getUserInput(userID)) }) return MtpApiManager.invokeApi('messages.createChat', { title: $scope.group.name, users: inputUsers }).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 } } $modalInstance.close() } })['finally'](function () { delete $scope.group.creating }) } }) .controller('ChatEditModalController', function ($scope, $modalInstance, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, ApiUpdatesManager) { var chat = AppChatsManager.getChat($scope.chatID) $scope.group = {name: chat.title} $scope.updateGroup = function () { if (!$scope.group.name) { return } if ($scope.group.name == chat.title) { return $modalInstance.close() } $scope.group.updating = true var apiPromise if (AppChatsManager.isChannel($scope.chatID)) { apiPromise = MtpApiManager.invokeApi('channels.editTitle', { channel: AppChatsManager.getChannelInput($scope.chatID), title: $scope.group.name }) } else { apiPromise = MtpApiManager.invokeApi('messages.editChatTitle', { chat_id: AppChatsManager.getChatInput($scope.chatID), title: $scope.group.name }) } return apiPromise.then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates) var peerString = AppChatsManager.getChatString($scope.chatID) $rootScope.$broadcast('history_focus', {peerString: peerString}) })['finally'](function () { delete $scope.group.updating }) } }) .controller('ChannelEditModalController', function ($q, $scope, $modalInstance, $rootScope, MtpApiManager, AppUsersManager, AppChatsManager, AppProfileManager, ApiUpdatesManager) { var channel = AppChatsManager.getChat($scope.chatID) var initial = {title: channel.title} $scope.channel = {title: channel.title} AppProfileManager.getChannelFull($scope.chatID).then(function (channelFull) { initial.about = channelFull.about $scope.channel.about = channelFull.about }) $scope.updateChannel = function () { if (!$scope.channel.title.length) { return } var promises = [] if ($scope.channel.title != initial.title) { promises.push(editTitle()) } if ($scope.channel.about != initial.about) { promises.push(editAbout()) } $scope.channel.updating = true return $q.all(promises).then(function () { var peerString = AppChatsManager.getChatString($scope.chatID) $rootScope.$broadcast('history_focus', {peerString: peerString}) })['finally'](function () { delete $scope.channel.updating }) } function editTitle () { return MtpApiManager.invokeApi('channels.editTitle', { channel: AppChatsManager.getChannelInput($scope.chatID), title: $scope.channel.title }).then(function (updates) { ApiUpdatesManager.processUpdateMessage(updates) }) } function editAbout () { return MtpApiManager.invokeApi('channels.editAbout', { channel: AppChatsManager.getChannelInput($scope.chatID), about: $scope.channel.about }) } }) .controller('ChatInviteLinkModalController', function (_, $scope, $timeout, $modalInstance, AppChatsManager, AppProfileManager, ErrorService) { $scope.exportedInvite = {link: _('group_invite_link_loading_raw')} var isChannel = AppChatsManager.isChannel($scope.chatID) var isMegagroup = AppChatsManager.isMegagroup($scope.chatID) function selectLink () { $timeout(function () { $scope.$broadcast('ui_invite_select') }, 100) } function updateLink (force) { var chat = AppChatsManager.getChat($scope.chatID) if (chat.username) { $scope.exportedInvite = {link: 'https://t.me/' + chat.username, short: true} selectLink() return } if (force) { $scope.exportedInvite.revoking = true } AppProfileManager.getChatInviteLink($scope.chatID, force).then(function (link) { $scope.exportedInvite = {link: link, canRevoke: true} selectLink() })['finally'](function () { delete $scope.exportedInvite.revoking }) } $scope.revokeLink = function () { ErrorService.confirm({ type: isChannel && !isMegagroup ? 'REVOKE_CHANNEL_INVITE_LINK' : 'REVOKE_GROUP_INVITE_LINK' }).then(function () { updateLink(true) }) } updateLink() }) .controller('ImportContactModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, ErrorService, PhonebookContactsService) { if ($scope.importContact === undefined) { $scope.importContact = {} } $scope.phonebookAvailable = PhonebookContactsService.isAvailable() $scope.doImport = function () { if ($scope.importContact && $scope.importContact.phone) { $scope.progress = {enabled: true} AppUsersManager.importContact( $scope.importContact.phone, $scope.importContact.first_name || '', $scope.importContact.last_name || '' ).then(function (foundUserID) { if (!foundUserID) { ErrorService.show({ error: {code: 404, type: 'USER_NOT_USING_TELEGRAM'} }) } $modalInstance.close(foundUserID) })['finally'](function () { delete $scope.progress.enabled }) } } $scope.importPhonebook = function () { PhonebookContactsService.openPhonebookImport().result.then(function (foundContacts) { if (foundContacts) { $modalInstance.close(foundContacts[0]) } else { $modalInstance.dismiss() } }) } }) .controller('CountrySelectModalController', function ($scope, $modalInstance, $rootScope, _) { $scope.search = {} $scope.slice = {limit: 20, limitDelta: 20} var searchIndex = SearchIndexManager.createIndex() for (var i = 0; i < Config.CountryCodes.length; i++) { var searchString = Config.CountryCodes[i][0] searchString += ' ' + _(Config.CountryCodes[i][1] + '_raw') searchString += ' ' + Config.CountryCodes[i].slice(2).join(' ') SearchIndexManager.indexObject(i, searchString, searchIndex) } $scope.$watch('search.query', function (newValue) { var filtered = false var results = {} if (angular.isString(newValue) && newValue.length) { filtered = true results = SearchIndexManager.search(newValue, searchIndex) } $scope.countries = [] $scope.slice.limit = 20 var j for (var i = 0; i < Config.CountryCodes.length; i++) { if (!filtered || results[i]) { for (j = 2; j < Config.CountryCodes[i].length; j++) { $scope.countries.push({name: _(Config.CountryCodes[i][1] + '_raw'), code: Config.CountryCodes[i][j]}) } } } if (String.prototype.localeCompare) { $scope.countries.sort(function (a, b) { return a.name.localeCompare(b.name) }) } }) }) .controller('PhonebookModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, PhonebookContactsService, ErrorService) { $scope.search = {} $scope.phonebook = [] $scope.selectedContacts = {} $scope.selectedCount = 0 $scope.slice = {limit: 20, limitDelta: 20} $scope.progress = {enabled: false} $scope.multiSelect = true var searchIndex = SearchIndexManager.createIndex() var phonebookReady = false PhonebookContactsService.getPhonebookContacts().then(function (phonebook) { for (var i = 0; i < phonebook.length; i++) { SearchIndexManager.indexObject(i, phonebook[i].first_name + ' ' + phonebook[i].last_name + ' ' + phonebook[i].phones.join(' '), searchIndex) } $scope.phonebook = phonebook $scope.toggleSelection(true) phonebookReady = true updateList() }, function (error) { ErrorService.show({ error: {code: 403, type: 'PHONEBOOK_GET_CONTACTS_FAILED', originalError: error} }) }) function updateList () { var filtered = false var results = {} if (angular.isString($scope.search.query) && $scope.search.query.length) { filtered = true results = SearchIndexManager.search($scope.search.query, searchIndex) $scope.contacts = [] delete $scope.contactsEmpty for (var i = 0; i < $scope.phonebook.length; i++) { if (!filtered || results[i]) { $scope.contacts.push($scope.phonebook[i]) } } } else { $scope.contacts = $scope.phonebook $scope.contactsEmpty = !$scope.contacts.length } $scope.slice.limit = 20 } $scope.$watch('search.query', function (newValue) { if (phonebookReady) { updateList() } }) $scope.contactSelect = function (i) { if (!$scope.multiSelect) { return $modalInstance.close($scope.phonebook[i]) } if ($scope.selectedContacts[i]) { delete $scope.selectedContacts[i] $scope.selectedCount-- } else { $scope.selectedContacts[i] = true $scope.selectedCount++ } } $scope.toggleSelection = function (fill) { if (!$scope.selectedCount || fill) { $scope.selectedCount = $scope.phonebook.length for (var i = 0; i < $scope.phonebook.length; i++) { $scope.selectedContacts[i] = true } } else { $scope.selectedCount = 0 $scope.selectedContacts = {} } } $scope.submitSelected = function () { if ($scope.selectedCount <= 0) { $modalInstance.dismiss() } var selectedContacts = [] angular.forEach($scope.selectedContacts, function (t, i) { selectedContacts.push($scope.phonebook[i]) }) ErrorService.confirm({ type: 'CONTACTS_IMPORT_PERFORM' }).then(function () { $scope.progress.enabled = true AppUsersManager.importContacts(selectedContacts).then(function (foundContacts) { if (!foundContacts.length) { ErrorService.show({ error: {code: 404, type: 'USERS_NOT_USING_TELEGRAM'} }) } $modalInstance.close(foundContacts) })['finally'](function () { $scope.progress.enabled = false }) }) } }) .controller('StickersetModalController', function ($scope, $rootScope, $modalInstance, MtpApiManager, RichTextProcessor, AppStickersManager, AppDocsManager, AppMessagesManager, LocationParamsService) { $scope.slice = {limit: 20, limitDelta: 20} var fullSet AppStickersManager.getStickerset($scope.inputStickerset).then(function (result) { $scope.$broadcast('ui_height') $scope.stickersetLoaded = true fullSet = result $scope.stickerset = result.set $scope.stickersetInstalled = result.set.pFlags.installed == true $scope.documents = result.documents $scope.stickerEmojis = {} $scope.stickerDimensions = {} angular.forEach($scope.documents, function (doc) { $scope.stickerEmojis[doc.id] = RichTextProcessor.wrapRichText(doc.stickerEmojiRaw, { noLinks: true, noLinebreaks: true, emojiIconSize: 26 }) var dim = calcImageInBox(doc.w, doc.h, 192, 192) $scope.stickerDimensions[doc.id] = {width: dim.w, height: dim.h} }) }) $scope.toggleInstalled = function (installed) { AppStickersManager.installStickerset(fullSet, !installed).then(function () { $scope.stickersetInstalled = installed }) } $scope.chooseSticker = function (docID) { var doc = AppDocsManager.getDoc(docID) if (!doc.id || !doc.access_hash || !$rootScope.selectedPeerID) { return } var inputMedia = { _: 'inputMediaDocument', id: { _: 'inputDocument', id: doc.id, access_hash: doc.access_hash } } AppMessagesManager.sendOther($rootScope.selectedPeerID, inputMedia) $modalInstance.close(doc.id) } $scope.share = function () { LocationParamsService.shareUrl('https://t.me/addstickers/' + $scope.stickerset.short_name, $scope.stickerset.title) } })