webogram-i2p/app/js/services.js

5960 lines
180 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* Webogram v0.4.7 - messaging web application for MTProto
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
'use strict';
/* Services */
angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, qSync, MtpApiFileManager, MtpApiManager, RichTextProcessor, ErrorService, Storage, _) {
var users = {},
usernames = {},
cachedPhotoLocations = {},
contactsFillPromise,
contactsList,
contactsIndex = SearchIndexManager.createIndex(),
myID,
serverTimeOffset = 0;
Storage.get('server_time_offset').then(function (to) {
if (to) {
serverTimeOffset = to;
}
});
MtpApiManager.getUserID().then(function (id) {
myID = id;
});
function fillContacts () {
if (contactsFillPromise) {
return contactsFillPromise;
}
return contactsFillPromise = MtpApiManager.invokeApi('contacts.getContacts', {
hash: ''
}).then(function (result) {
var userID, searchText, i;
contactsList = [];
saveApiUsers(result.users);
for (var i = 0; i < result.contacts.length; i++) {
userID = result.contacts[i].user_id;
contactsList.push(userID);
SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex);
}
return contactsList;
});
}
function getUserSearchText (id) {
var user = users[id];
if (!user) {
return false;
}
return (user.first_name || '') + ' ' + (user.last_name || '') + ' ' + (user.phone || '') + ' ' + (user.username || '');
}
function getContacts (query) {
return fillContacts().then(function (contactsList) {
if (angular.isString(query) && query.length) {
var results = SearchIndexManager.search(query, contactsIndex),
filteredContactsList = [];
for (var i = 0; i < contactsList.length; i++) {
if (results[contactsList[i]]) {
filteredContactsList.push(contactsList[i])
}
}
contactsList = filteredContactsList;
}
return contactsList;
});
};
function userNameClean (username) {
return username && username.toLowerCase() || '';
}
function resolveUsername (username) {
var searchUserName = userNameClean(username);
var foundUserID = usernames[searchUserName];
if (foundUserID &&
userNameClean(users[foundUserID].username) == searchUserName) {
return qSync.when(foundUserID);
}
return MtpApiManager.invokeApi('contacts.resolveUsername', {username: username}).then(function (resolveResult) {
saveApiUser(resolveResult);
return resolveResult.id;
});
}
function saveApiUsers (apiUsers) {
angular.forEach(apiUsers, saveApiUser);
};
function saveApiUser (apiUser, noReplace) {
if (!angular.isObject(apiUser) ||
noReplace && angular.isObject(users[apiUser.id]) && users[apiUser.id].first_name) {
return;
}
var userID = apiUser.id;
if (apiUser.phone) {
apiUser.rPhone = $filter('phoneNumber')(apiUser.phone);
}
apiUser.num = (Math.abs(userID) % 8) + 1;
if (apiUser.first_name) {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true});
apiUser.rFullName = apiUser.last_name ? RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}) : apiUser.rFirstName;
} else {
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || _('user_first_name_deleted');
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || apiUser.rPhone || _('user_name_deleted');
}
if (apiUser.username) {
usernames[userNameClean(apiUser.username)] = userID;
}
apiUser.sortName = SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''));
apiUser.pFlags = {
self: (apiUser.flags & (1 << 10)) > 0,
contact: (apiUser.flags & (1 << 11)) > 0,
mutual: (apiUser.flags & (1 << 12)) > 0,
deleted: (apiUser.flags & (1 << 13)) > 0,
bot: (apiUser.flags & (1 << 14)) > 0,
botNoPrivacy: (apiUser.flags & (1 << 15)) > 0,
botNoGroups: (apiUser.flags & (1 << 16)) > 0
};
var nameWords = apiUser.sortName.split(' ');
var firstWord = nameWords.shift();
var lastWord = nameWords.pop();
apiUser.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1));
if (apiUser.pFlags.bot) {
apiUser.sortStatus = -1;
} else {
apiUser.sortStatus = getUserStatusForSort(apiUser.status);
}
var result = users[userID];
if (result === undefined) {
result = users[userID] = apiUser;
} else {
safeReplaceObject(result, apiUser);
}
$rootScope.$broadcast('user_update', userID);
if (cachedPhotoLocations[userID] !== undefined) {
safeReplaceObject(cachedPhotoLocations[userID], apiUser && apiUser.photo && apiUser.photo.photo_small || {empty: true});
}
};
function getUserStatusForSort(status) {
if (status) {
var expires = status.expires || status.was_online;
if (expires) {
return expires;
}
var timeNow = tsNow(true) + serverTimeOffset;
switch (status._) {
case 'userStatusRecently':
return tsNow(true) + serverTimeOffset - 86400 * 3;
case 'userStatusLastWeek':
return tsNow(true) + serverTimeOffset - 86400 * 7;
case 'userStatusLastMonth':
return tsNow(true) + serverTimeOffset - 86400 * 30;
}
}
return 0;
}
function getUser (id) {
if (angular.isObject(id)) {
return id;
}
return users[id] || {id: id, deleted: true, num: 1};
}
function getSelf() {
return getUser(myID);
}
function isBot(id) {
return users[id] && users[id].pFlags.bot;
}
function hasUser(id) {
return angular.isObject(users[id]);
}
function getUserPhoto(id, placeholder) {
var user = getUser(id);
if (id == 333000) {
return {
placeholder: 'img/placeholders/DialogListAvatarSystem@2x.png'
}
};
if (cachedPhotoLocations[id] === undefined) {
cachedPhotoLocations[id] = user && user.photo && user.photo.photo_small || {empty: true};
}
return {
num: user.num,
placeholder: 'img/placeholders/' + placeholder + 'Avatar' + user.num + '@2x.png',
location: cachedPhotoLocations[id]
};
}
function getUserString (id) {
var user = getUser(id);
return 'u' + id + (user.access_hash ? '_' + user.access_hash : '');
}
function getUserInput (id) {
var user = getUser(id);
if (user.pFlags.self) {
return {_: 'inputUserSelf'};
}
return {
_: 'inputUser',
user_id: id,
access_hash: user.access_hash || 0
};
}
function updateUsersStatuses () {
var timestampNow = tsNow(true) + serverTimeOffset;
angular.forEach(users, function (user) {
if (user.status &&
user.status._ == 'userStatusOnline' &&
user.status.expires < timestampNow) {
user.status = user.status.wasStatus ||
{_: 'userStatusOffline', was_online: user.status.expires};
delete user.status.wasStatus;
$rootScope.$broadcast('user_update', user.id);
}
});
}
function forceUserOnline (id) {
if (isBot(id)) {
return;
}
var user = getUser(id);
if (user &&
user.status &&
user.status._ != 'userStatusOnline' &&
user.status._ != 'userStatusEmpty') {
var wasStatus;
if (user.status._ != 'userStatusOffline') {
delete user.status.wasStatus;
wasStatus != angular.copy(user.status);
}
user.status = {
_: 'userStatusOnline',
expires: tsNow(true) + serverTimeOffset + 60,
wasStatus: wasStatus
};
user.sortStatus = getUserStatusForSort(user.status);
$rootScope.$broadcast('user_update', id);
}
}
function wrapForFull (id) {
var user = getUser(id);
return user;
}
function openUser (userID, override) {
var scope = $rootScope.$new();
scope.userID = userID;
scope.override = override || {};
var modalInstance = $modal.open({
templateUrl: templateUrl('user_modal'),
controller: 'UserModalController',
scope: scope,
windowClass: 'user_modal_window mobile_modal',
backdrop: 'single'
});
};
function importContact (phone, firstName, lastName) {
return MtpApiManager.invokeApi('contacts.importContacts', {
contacts: [{
_: 'inputPhoneContact',
client_id: '1',
phone: phone,
first_name: firstName,
last_name: lastName
}],
replace: false
}).then(function (importedContactsResult) {
saveApiUsers(importedContactsResult.users);
var foundUserID = false;
angular.forEach(importedContactsResult.imported, function (importedContact) {
onContactUpdated(foundUserID = importedContact.user_id, true);
});
return foundUserID || false;
});
};
function importContacts (contacts) {
var inputContacts = [],
i, j;
for (i = 0; i < contacts.length; i++) {
for (j = 0; j < contacts[i].phones.length; j++) {
inputContacts.push({
_: 'inputPhoneContact',
client_id: (i << 16 | j).toString(10),
phone: contacts[i].phones[j],
first_name: contacts[i].first_name,
last_name: contacts[i].last_name
});
}
}
return MtpApiManager.invokeApi('contacts.importContacts', {
contacts: inputContacts,
replace: false
}).then(function (importedContactsResult) {
saveApiUsers(importedContactsResult.users);
var result = [];
angular.forEach(importedContactsResult.imported, function (importedContact) {
onContactUpdated(importedContact.user_id, true);
result.push(importedContact.user_id);
});
return result;
});
};
function deleteContacts (userIDs) {
var ids = []
angular.forEach(userIDs, function (userID) {
ids.push(getUserInput(userID))
});
return MtpApiManager.invokeApi('contacts.deleteContacts', {
id: ids
}).then(function () {
angular.forEach(userIDs, function (userID) {
onContactUpdated(userID, false);
});
})
}
function onContactUpdated (userID, isContact) {
if (angular.isArray(contactsList)) {
var curPos = curIsContact = contactsList.indexOf(parseInt(userID)),
curIsContact = curPos != -1;
if (isContact != curIsContact) {
if (isContact) {
contactsList.push(userID);
SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex);
} else {
contactsList.splice(curPos, 1);
}
$rootScope.$broadcast('contacts_update', userID);
}
}
}
function openImportContact () {
return $modal.open({
templateUrl: templateUrl('import_contact_modal'),
controller: 'ImportContactModalController',
windowClass: 'md_simple_modal_window mobile_modal'
}).result.then(function (foundUserID) {
if (!foundUserID) {
return $q.reject();
}
return foundUserID;
});
};
function setUserStatus (userID, offline) {
if (isBot(userID)) {
return;
}
var user = users[userID];
if (user) {
var status = offline ? {
_: 'userStatusOffline',
was_online: tsNow(true) + serverTimeOffset
} : {
_: 'userStatusOnline',
expires: tsNow(true) + serverTimeOffset + 500
};
user.status = status;
user.sortStatus = getUserStatusForSort(user.status);
$rootScope.$broadcast('user_update', userID);
}
}
$rootScope.$on('apiUpdate', function (e, update) {
// console.log('on apiUpdate', update);
switch (update._) {
case 'updateUserStatus':
var userID = update.user_id,
user = users[userID];
if (user) {
user.status = update.status;
user.sortStatus = getUserStatusForSort(user.status);
$rootScope.$broadcast('user_update', userID);
}
break;
case 'updateUserPhoto':
var userID = update.user_id;
if (users[userID]) {
forceUserOnline(userID);
safeReplaceObject(users[userID].photo, update.photo);
if (cachedPhotoLocations[userID] !== undefined) {
safeReplaceObject(cachedPhotoLocations[userID], update.photo && update.photo.photo_small || {empty: true});
}
$rootScope.$broadcast('user_update', userID);
}
break;
case 'updateContactLink':
onContactUpdated(update.user_id, update.my_link._ == 'contactLinkContact');
break;
}
});
setInterval(updateUsersStatuses, 60000);
return {
getContacts: getContacts,
saveApiUsers: saveApiUsers,
saveApiUser: saveApiUser,
getUser: getUser,
getSelf: getSelf,
getUserInput: getUserInput,
setUserStatus: setUserStatus,
forceUserOnline: forceUserOnline,
getUserPhoto: getUserPhoto,
getUserString: getUserString,
getUserSearchText: getUserSearchText,
hasUser: hasUser,
isBot: isBot,
importContact: importContact,
importContacts: importContacts,
deleteContacts: deleteContacts,
wrapForFull: wrapForFull,
openUser: openUser,
resolveUsername: resolveUsername,
openImportContact: openImportContact
}
})
.service('PhonebookContactsService', function ($q, $modal, $sce, FileManager) {
return {
isAvailable: isAvailable,
openPhonebookImport: openPhonebookImport,
getPhonebookContacts: getPhonebookContacts
};
function isAvailable () {
if (Config.Mobile && Config.Navigator.ffos && Config.Modes.packed) {
try {
return navigator.mozContacts && navigator.mozContacts.getAll;
} catch (e) {
console.error(dT(), 'phonebook n/a', e);
return false;
}
}
return false;
}
function openPhonebookImport () {
return $modal.open({
templateUrl: templateUrl('phonebook_modal'),
controller: 'PhonebookModalController',
windowClass: 'phonebook_modal_window mobile_modal'
});
}
function getPhonebookContacts () {
try {
var request = window.navigator.mozContacts.getAll({});
} catch (e) {
return $q.reject(e);
}
var deferred = $q.defer(),
contacts = [],
count = 0;
request.onsuccess = function () {
if (this.result) {
var contact = {
id: count,
first_name: (this.result.givenName || []).join(' '),
last_name: (this.result.familyName || []).join(' '),
phones: []
};
if (this.result.tel != undefined) {
for (var i = 0; i < this.result.tel.length; i++) {
contact.phones.push(this.result.tel[i].value);
}
}
if (this.result.photo && this.result.photo[0]) {
try {
contact.photo = FileManager.getUrl(this.result.photo[0]);
} catch (e) {}
}
if (!contact.photo) {
contact.photo = 'img/placeholders/UserAvatar' + ((Math.abs(count) % 8) + 1) + '@2x.png';
}
contact.photo = $sce.trustAsResourceUrl(contact.photo);
count++;
contacts.push(contact);
}
if (!this.result || count >= 1000) {
deferred.resolve(contacts);
return;
}
this['continue']();
}
request.onerror = function (e) {
console.log('phonebook error', e, e.type, e.message);
deferred.reject(e);
}
return deferred.promise;
}
})
.service('AppChatsManager', function ($q, $rootScope, $modal, _, MtpApiFileManager, MtpApiManager, AppUsersManager, AppPhotosManager, RichTextProcessor) {
var chats = {},
chatsFull = {},
chatFullPromises = {},
cachedPhotoLocations = {};
function saveApiChats (apiChats) {
angular.forEach(apiChats, saveApiChat);
};
function saveApiChat (apiChat) {
if (!angular.isObject(apiChat)) {
return;
}
apiChat.rTitle = RichTextProcessor.wrapRichText(apiChat.title, {noLinks: true, noLinebreaks: true}) || _('chat_title_deleted');
var titleWords = SearchIndexManager.cleanSearchText(apiChat.title || '').split(' ');
var firstWord = titleWords.shift();
var lastWord = titleWords.pop();
apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1));
apiChat.num = (Math.abs(apiChat.id >> 1) % (Config.Mobile ? 4 : 8)) + 1;
if (chats[apiChat.id] === undefined) {
chats[apiChat.id] = apiChat;
} else {
safeReplaceObject(chats[apiChat.id], apiChat);
$rootScope.$broadcast('chat_update', apiChat.id);
}
if (cachedPhotoLocations[apiChat.id] !== undefined) {
safeReplaceObject(cachedPhotoLocations[apiChat.id], apiChat && apiChat.photo && apiChat.photo.photo_small || {empty: true});
}
};
function getChat (id) {
return chats[id] || {id: id, deleted: true};
}
function getChatFull(id) {
if (chatsFull[id] !== undefined) {
if (chats[id].version == chatsFull[id].participants.version ||
chats[id].left) {
return $q.when(chatsFull[id]);
}
}
if (chatFullPromises[id] !== undefined) {
return chatFullPromises[id];
}
return chatFullPromises[id] = MtpApiManager.invokeApi('messages.getFullChat', {
chat_id: id
}).then(function (result) {
saveApiChats(result.chats);
AppUsersManager.saveApiUsers(result.users);
if (result.full_chat && result.full_chat.chat_photo.id) {
AppPhotosManager.savePhoto(result.full_chat.chat_photo);
}
delete chatFullPromises[id];
$rootScope.$broadcast('chat_full_update', id);
return chatsFull[id] = result.full_chat;
});
}
function getChatInviteLink (id, force) {
return getChatFull(id).then(function (chatFull) {
if (!force &&
chatFull.exported_invite &&
chatFull.exported_invite._ == 'chatInviteExported') {
return chatFull.exported_invite.link;
}
return MtpApiManager.invokeApi('messages.exportChatInvite', {
chat_id: id
}).then(function (exportedInvite) {
if (chatsFull[id] !== undefined) {
chatsFull[id].exported_invite = exportedInvite;
}
return exportedInvite.link;
});
});
}
function hasChat (id) {
return angular.isObject(chats[id]);
}
function getChatPhoto(id, placeholder) {
var chat = getChat(id);
if (cachedPhotoLocations[id] === undefined) {
cachedPhotoLocations[id] = chat && chat.photo && chat.photo.photo_small || {empty: true};
}
return {
placeholder: 'img/placeholders/' + placeholder + 'Avatar' + (Config.Mobile ? chat.num : Math.ceil(chat.num / 2)) + '@2x.png',
location: cachedPhotoLocations[id]
};
}
function getChatString (id) {
var chat = getChat(id);
return 'g' + id;
}
function wrapForFull (id, fullChat) {
var chatFull = angular.copy(fullChat),
chat = getChat(id);
if (chatFull.participants && chatFull.participants._ == 'chatParticipants') {
MtpApiManager.getUserID().then(function (myID) {
chatFull.isAdmin = (myID == chatFull.participants.admin_id);
angular.forEach(chatFull.participants.participants, function(participant){
participant.user = AppUsersManager.getUser(participant.user_id);
participant.canLeave = myID == participant.user_id;
participant.canKick = !participant.canLeave && (chatFull.isAdmin || myID == participant.inviter_id);
});
});
}
chatFull.thumb = {
placeholder: 'img/placeholders/GroupAvatar'+(Config.Mobile ? chat.num : Math.ceil(chat.num / 2))+'@2x.png',
location: chat && chat.photo && chat.photo.photo_small,
width: 72,
height: 72,
size: 0
};
chatFull.peerString = getChatString(id);
chatFull.chat = chat;
return chatFull;
}
function openChat (chatID, accessHash) {
var scope = $rootScope.$new();
scope.chatID = chatID;
var modalInstance = $modal.open({
templateUrl: templateUrl('chat_modal'),
controller: 'ChatModalController',
scope: scope,
windowClass: 'chat_modal_window mobile_modal'
});
}
$rootScope.$on('apiUpdate', function (e, update) {
// console.log('on apiUpdate', update);
switch (update._) {
case 'updateChatParticipants':
var participants = update.participants;
var chatFull = chatsFull[participants.id];
if (chatFull !== undefined) {
chatFull.participants = update.participants;
$rootScope.$broadcast('chat_full_update', chatID);
}
break;
case 'updateChatParticipantAdd':
var chatFull = chatsFull[update.chat_id];
if (chatFull !== undefined) {
var participants = chatFull.participants.participants || [];
for (var i = 0, length = participants.length; i < length; i++) {
if (participants[i].user_id == update.user_id) {
return;
}
}
participants.push({
_: 'chatParticipant',
user_id: update.user_id,
inviter_id: update.inviter_id,
date: tsNow(true)
});
chatFull.participants.version = update.version;
$rootScope.$broadcast('chat_full_update', update.chat_id);
}
break;
case 'updateChatParticipantDelete':
var chatFull = chatsFull[update.chat_id];
if (chatFull !== undefined) {
var participants = chatFull.participants.participants || [];
for (var i = 0, length = participants.length; i < length; i++) {
if (participants[i].user_id == update.user_id) {
participants.splice(i, 1);
chatFull.participants.version = update.version;
$rootScope.$broadcast('chat_full_update', update.chat_id);
return;
}
}
}
break;
}
});
return {
saveApiChats: saveApiChats,
saveApiChat: saveApiChat,
getChat: getChat,
getChatFull: getChatFull,
getChatPhoto: getChatPhoto,
getChatString: getChatString,
getChatInviteLink: getChatInviteLink,
hasChat: hasChat,
wrapForFull: wrapForFull,
openChat: openChat
}
})
.service('AppPeersManager', function (AppUsersManager, AppChatsManager, MtpApiManager) {
return {
getInputPeer: function (peerString) {
var isUser = peerString.charAt(0) == 'u',
peerParams = peerString.substr(1).split('_');
return isUser
? {_: 'inputPeerUser', user_id: peerParams[0], access_hash: peerParams[1]}
: {_: 'inputPeerChat', chat_id: peerParams[0]};
},
getInputPeerByID: function (peerID) {
if (peerID > 0) {
return {
_: 'inputPeerUser',
user_id: peerID,
access_hash: AppUsersManager.getUser(peerID).access_hash || 0
};
} else if (peerID < 0) {
return {
_: 'inputPeerChat',
chat_id: -peerID
};
}
},
getPeerSearchText: function (peerID) {
var text;
if (peerID > 0) {
text = '%pu ' + AppUsersManager.getUserSearchText(peerID);
} else if (peerID < 0) {
var chat = AppChatsManager.getChat(-peerID);
text = '%pg ' + (chat.title || '');
}
return text;
},
getPeerString: function (peerID) {
if (peerID > 0) {
return AppUsersManager.getUserString(peerID);
}
return AppChatsManager.getChatString(-peerID);
},
getOutputPeer: function (peerID) {
return peerID > 0
? {_: 'peerUser', user_id: peerID}
: {_: 'peerChat', chat_id: -peerID};
},
getPeerID: function (peerString) {
if (angular.isObject(peerString)) {
return peerString.user_id
? peerString.user_id
: -peerString.chat_id;
}
var isUser = peerString.charAt(0) == 'u',
peerParams = peerString.substr(1).split('_');
return isUser ? peerParams[0] : -peerParams[0] || 0;
},
getPeer: function (peerID) {
return peerID > 0
? AppUsersManager.getUser(peerID)
: AppChatsManager.getChat(-peerID);
},
getPeerPhoto: function (peerID, userPlaceholder, chatPlaceholder) {
return peerID > 0
? AppUsersManager.getUserPhoto(peerID, userPlaceholder)
: AppChatsManager.getChatPhoto(-peerID, chatPlaceholder)
}
}
})
.service('AppProfileManager', function ($q, AppUsersManager, AppChatsManager, AppPhotosManager, NotificationsManager, MtpApiManager, RichTextProcessor) {
var botInfos = {};
function saveBotInfo (botInfo) {
var botID = botInfo && botInfo.user_id;
if (!botID) {
return false;
}
var commands = {};
angular.forEach(botInfo.commands, function (botCommand) {
commands[botCommand.command] = botCommand.description;
})
return botInfos[botID] = {
id: botID,
version: botInfo.version,
shareText: botInfo.share_text,
description: botInfo.description,
rAbout: RichTextProcessor.wrapRichText(botInfo.share_text, {noLinebreaks: true}),
commands: commands
};
}
function getProfile (id, override) {
return MtpApiManager.invokeApi('users.getFullUser', {
id: AppUsersManager.getUserInput(id)
}).then(function (userFull) {
if (override && override.phone_number) {
userFull.user.phone = override.phone_number;
if (override.first_name || override.last_name) {
userFull.user.first_name = override.first_name;
userFull.user.last_name = override.last_name;
}
AppUsersManager.saveApiUser(userFull.user);
} else {
AppUsersManager.saveApiUser(userFull.user, true);
}
AppPhotosManager.savePhoto(userFull.profile_photo, {
user_id: id
});
NotificationsManager.savePeerSettings(id, userFull.notify_settings);
userFull.bot_info = saveBotInfo(userFull.bot_info);
return userFull;
});
}
function getPeerBots (peerID) {
var peerBots = [];
if (peerID >= 0) {
if (!AppUsersManager.isBot(peerID)) {
return $q.when(peerBots);
}
return getProfile(peerID).then(function (userFull) {
var botInfo = userFull.bot_info;
if (botInfo && botInfo._ != 'botInfoEmpty') {
peerBots.push(botInfo);
}
return peerBots;
});
}
return AppChatsManager.getChatFull(-peerID).then(function (chatFull) {
angular.forEach(chatFull.bot_info, function (botInfo) {
peerBots.push(saveBotInfo(botInfo));
});
return peerBots;
});
}
return {
getProfile: getProfile,
getPeerBots: getPeerBots
}
})
.service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, AppProfileManager, FileManager, TelegramMeWebService, ErrorService, StatusManager, _) {
var messagesStorage = {};
var messagesForHistory = {};
var messagesForDialogs = {};
var historiesStorage = {};
var dialogsStorage = {count: null, dialogs: []};
var pendingByRandomID = {};
var pendingByMessageID = {};
var pendingAfterMsgs = {};
var sendFilePromise = $q.when();
var tempID = -1;
var dialogsIndex = SearchIndexManager.createIndex(),
cachedResults = {query: false};
var lastSearchFilter = {},
lastSearchResults = [];
var needSingleMessages = [],
fetchSingleMessagesTimeout = false;
var serverTimeOffset = 0,
timestampNow = tsNow(true),
midnightNoOffset = timestampNow - (timestampNow % 86400),
midnightOffseted = new Date(),
midnightOffset;
Storage.get('server_time_offset').then(function (to) {
if (to) {
serverTimeOffset = to;
}
});
var maxSeenID = false;
if (Config.Modes.packed) {
Storage.get('max_seen_msg').then(function (maxID) {
maxSeenID = maxID || 0;
});
}
var dateOrTimeFilter = $filter('dateOrTime');
var fwdMessagesPluralize = _.pluralize('conversation_forwarded_X_messages');
midnightOffseted.setHours(0);
midnightOffseted.setMinutes(0);
midnightOffseted.setSeconds(0);
midnightOffset = midnightNoOffset - (Math.floor(+midnightOffseted / 1000));
NotificationsManager.start();
function getDialogs (query, maxID, limit) {
var curDialogStorage = dialogsStorage;
if (angular.isString(query) && query.length) {
if (!limit || cachedResults.query !== query) {
cachedResults.query = query;
var results = SearchIndexManager.search(query, dialogsIndex);
cachedResults.dialogs = [];
angular.forEach(dialogsStorage.dialogs, function (dialog) {
if (results[dialog.peerID]) {
cachedResults.dialogs.push(dialog);
}
});
cachedResults.count = cachedResults.dialogs.length;
}
curDialogStorage = cachedResults;
} else {
cachedResults.query = false;
}
var offset = 0;
if (maxID > 0) {
for (offset = 0; offset < curDialogStorage.dialogs.length; offset++) {
if (maxID > curDialogStorage.dialogs[offset].top_message) {
break;
}
}
}
if (curDialogStorage.count !== null && curDialogStorage.dialogs.length == curDialogStorage.count ||
curDialogStorage.dialogs.length >= offset + (limit || 1)
) {
return $q.when({
count: curDialogStorage.count,
dialogs: curDialogStorage.dialogs.slice(offset, offset + (limit || 20))
});
}
limit = limit || 20;
return MtpApiManager.invokeApi('messages.getDialogs', {
offset: offset,
limit: limit,
max_id: maxID || 0
}).then(function (dialogsResult) {
TelegramMeWebService.setAuthorized(true);
AppUsersManager.saveApiUsers(dialogsResult.users);
AppChatsManager.saveApiChats(dialogsResult.chats);
// return {
// count: 0,
// dialogs: []
// };
saveMessages(dialogsResult.messages);
if (maxID > 0) {
for (offset = 0; offset < curDialogStorage.dialogs.length; offset++) {
if (maxID > curDialogStorage.dialogs[offset].top_message) {
break;
}
}
}
curDialogStorage.count = dialogsResult.count || dialogsResult.dialogs.length;
if (!maxID && curDialogStorage.dialogs.length) {
incrementMaxSeenID(curDialogStorage.dialogs[0].top_message);
}
curDialogStorage.dialogs.splice(offset, curDialogStorage.dialogs.length - offset);
angular.forEach(dialogsResult.dialogs, function (dialog) {
var peerID = AppPeersManager.getPeerID(dialog.peer),
peerText = AppPeersManager.getPeerSearchText(peerID);
SearchIndexManager.indexObject(peerID, peerText, dialogsIndex);
curDialogStorage.dialogs.push({
peerID: peerID,
top_message: dialog.top_message,
unread_count: dialog.unread_count
});
if (historiesStorage[peerID] === undefined) {
var historyStorage = {count: null, history: [dialog.top_message], pending: []};
historiesStorage[peerID] = historyStorage;
var message = getMessage(dialog.top_message);
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID});
}
}
NotificationsManager.savePeerSettings(peerID, dialog.notify_settings);
if (
dialog.unread_count > 0 &&
maxSeenID &&
dialog.top_message > maxSeenID
) {
var message = getMessage(dialog.top_message);
var notifyPeer = message.flags & 16 ? message.from_id : peerID;
if (message.unread && !message.out) {
NotificationsManager.getPeerMuted(notifyPeer).then(function (muted) {
if (!muted) {
notifyAboutMessage(message);
}
});
}
}
});
return {
count: curDialogStorage.count,
dialogs: curDialogStorage.dialogs.slice(offset, offset + limit)
};
});
}
function requestHistory (inputPeer, maxID, limit, offset) {
return MtpApiManager.invokeApi('messages.getHistory', {
peer: inputPeer,
offset: offset || 0,
limit: limit || 0,
max_id: maxID || 0
}, {noErrorBox: true}).then(function (historyResult) {
AppUsersManager.saveApiUsers(historyResult.users);
AppChatsManager.saveApiChats(historyResult.chats);
saveMessages(historyResult.messages);
var peerID = AppPeersManager.getPeerID(inputPeer);
if (
peerID < 0 ||
!AppUsersManager.isBot(peerID) ||
(historyResult.messages.length == limit && limit < historyResult.count)
) {
return historyResult;
}
return AppProfileManager.getProfile(peerID).then(function (userFull) {
var description = userFull.bot_info && userFull.bot_info.description;
if (description) {
var messageID = tempID--;
var message = {
_: 'messageService',
id: messageID,
from_id: peerID,
to_id: AppPeersManager.getOutputPeer(peerID),
flags: 0,
date: tsNow(true) + serverTimeOffset,
action: {
_: 'messageActionBotIntro',
description: description
}
};
saveMessages([message]);
historyResult.messages.push(message);
if (historyResult.count) {
historyResult.count++;
}
}
return historyResult;
});
});
}
function fillHistoryStorage (inputPeer, maxID, fullLimit, historyStorage) {
// console.log('fill history storage', inputPeer, maxID, fullLimit, angular.copy(historyStorage));
return requestHistory (inputPeer, maxID, fullLimit).then(function (historyResult) {
historyStorage.count = historyResult.count || historyResult.messages.length;
var offset = 0;
if (!maxID && historyResult.messages.length) {
maxID = historyResult.messages[0].id + 1;
}
if (maxID > 0) {
for (offset = 0; offset < historyStorage.history.length; offset++) {
if (maxID > historyStorage.history[offset]) {
break;
}
}
}
historyStorage.history.splice(offset, historyStorage.history.length - offset);
angular.forEach(historyResult.messages, function (message) {
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: AppPeersManager.getPeerID(inputPeer)});
}
historyStorage.history.push(message.id);
});
fullLimit -= historyResult.messages.length;
if (fullLimit > 0 && historyStorage.history.length < historyStorage.count) {
maxID = historyStorage.history[historyStorage.history.length - 1];
return fillHistoryStorage(inputPeer, maxID, fullLimit, historyStorage);
}
return true;
});
};
function wrapHistoryResult (peerID, result) {
return $q.when(result);
}
function getHistory (inputPeer, maxID, limit, backLimit, prerendered) {
var peerID = AppPeersManager.getPeerID(inputPeer),
historyStorage = historiesStorage[peerID],
offset = 0,
offsetNotFound = false,
unreadOffset = false,
unreadSkip = false;
prerendered = prerendered ? Math.min(50, prerendered) : 0;
if (historyStorage === undefined) {
historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []};
}
if (!limit && !maxID) {
var foundDialog = getDialogByPeerID(peerID);
if (foundDialog && foundDialog[0] && foundDialog[0].unread_count > 1) {
var unreadCount = foundDialog[0].unread_count;
if (unreadSkip = (unreadCount > 50)) {
limit = 20;
unreadOffset = 16;
offset = unreadCount - unreadOffset;
} else {
limit = Math.max(10, prerendered, unreadCount + 2);
unreadOffset = unreadCount;
}
}
else if (Config.Mobile) {
limit = 20;
}
}
else if (maxID > 0) {
offsetNotFound = true;
for (offset = 0; offset < historyStorage.history.length; offset++) {
if (maxID > historyStorage.history[offset]) {
offsetNotFound = false;
break;
}
}
}
if (!offsetNotFound && (
historyStorage.count !== null && historyStorage.history.length == historyStorage.count ||
historyStorage.history.length >= offset + (limit || 1)
)) {
if (backLimit) {
backLimit = Math.min(offset, backLimit);
offset = Math.max(0, offset - backLimit);
limit += backLimit;
} else {
limit = limit || (offset ? 20 : (prerendered || 5));
}
var history = historyStorage.history.slice(offset, offset + limit);
if (!maxID && historyStorage.pending.length) {
history = historyStorage.pending.slice().concat(history);
}
return wrapHistoryResult(peerID, {
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
unreadSkip: unreadSkip
});
}
if (!backLimit && !limit) {
limit = prerendered || 20;
}
if (offsetNotFound) {
offset = 0;
}
if (backLimit || unreadSkip || maxID && historyStorage.history.indexOf(maxID) == -1) {
if (backLimit) {
offset = -backLimit;
limit += backLimit;
}
return requestHistory(inputPeer, maxID, limit, offset).then(function (historyResult) {
historyStorage.count = historyResult.count || historyResult.messages.length;
var history = [];
angular.forEach(historyResult.messages, function (message) {
history.push(message.id);
});
if (!maxID && historyStorage.pending.length) {
history = historyStorage.pending.slice().concat(history);
}
return wrapHistoryResult(peerID, {
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
unreadSkip: unreadSkip
});
})
}
return fillHistoryStorage(inputPeer, maxID, limit, historyStorage).then(function () {
offset = 0;
if (maxID > 0) {
for (offset = 0; offset < historyStorage.history.length; offset++) {
if (maxID > historyStorage.history[offset]) {
break;
}
}
}
var history = historyStorage.history.slice(offset, offset + limit);
if (!maxID && historyStorage.pending.length) {
history = historyStorage.pending.slice().concat(history);
}
return wrapHistoryResult(peerID, {
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
unreadSkip: unreadSkip
});
});
}
function getReplyKeyboard (peerID) {
return (historiesStorage[peerID] || {}).reply_markup || false;
}
function mergeReplyKeyboard (historyStorage, message) {
// console.log('merge', message.id, message.reply_markup, historyStorage.reply_markup);
if (!message.reply_markup &&
!message.out &&
!message.action) {
return false;
}
var messageReplyMarkup = message.reply_markup;
var lastReplyMarkup = historyStorage.reply_markup;
if (messageReplyMarkup) {
if (lastReplyMarkup && lastReplyMarkup.id >= message.id) {
return false;
}
if (messageReplyMarkup.pFlags.selective &&
!(message.flags & 16)) {
return false;
}
if (historyStorage.maxOutID &&
message.id < historyStorage.maxOutID &&
messageReplyMarkup.pFlags.one_time) {
messageReplyMarkup.pFlags.hidden = true;
}
messageReplyMarkup = angular.extend({
id: message.id
}, messageReplyMarkup);
if (messageReplyMarkup._ != 'replyKeyboardHide') {
messageReplyMarkup.fromID = message.from_id;
}
historyStorage.reply_markup = messageReplyMarkup;
// console.log('set', historyStorage.reply_markup);
return true;
}
if (message.out) {
if (lastReplyMarkup) {
if (lastReplyMarkup.pFlags.one_time &&
!lastReplyMarkup.pFlags.hidden &&
(message.id > lastReplyMarkup.id || message.id < 0) &&
message.message) {
lastReplyMarkup.pFlags.hidden = true;
// console.log('set', historyStorage.reply_markup);
return true;
}
} else if (!historyStorage.maxOutID ||
message.id > historyStorage.maxOutID) {
historyStorage.maxOutID = message.id;
}
}
if (message.action &&
message.action._ == 'messageActionChatDeleteUser' &&
(lastReplyMarkup
? message.action.user_id == lastReplyMarkup.fromID
: AppUsersManager.isBot(message.action.user_id)
)
) {
historyStorage.reply_markup = {
_: 'replyKeyboardHide',
id: message.id,
flags: 0,
pFlags: {}
};
// console.log('set', historyStorage.reply_markup);
return true;
}
return false;
}
function getSearch (inputPeer, query, inputFilter, maxID, limit) {
var foundMsgs = [],
useSearchCache = !query,
peerID = AppPeersManager.getPeerID(inputPeer),
newSearchFilter = {peer: peerID, filter: inputFilter},
sameSearchCache = useSearchCache && angular.equals(lastSearchFilter, newSearchFilter);
if (useSearchCache && !sameSearchCache) {
lastSearchFilter = newSearchFilter;
lastSearchResults = [];
}
if (!maxID && !query) {
var historyStorage = historiesStorage[peerID];
if (historyStorage !== undefined && historyStorage.history.length) {
var neededContents = {},
neededLimit = limit || 20,
i, message;
switch (inputFilter._) {
case 'inputMessagesFilterPhotos':
neededContents['messageMediaPhoto'] = true;
break;
case 'inputMessagesFilterVideo':
neededContents['messageMediaVideo'] = true;
break;
case 'inputMessagesFilterPhotoVideo':
neededContents['messageMediaPhoto'] = true;
neededContents['messageMediaVideo'] = true;
break;
case 'inputMessagesFilterDocument':
neededContents['messageMediaDocument'] = true;
break;
case 'inputMessagesFilterAudio':
neededContents['messageMediaAudio'] = true;
break;
}
for (i = 0; i < historyStorage.history.length; i++) {
message = messagesStorage[historyStorage.history[i]];
if (message.media && neededContents[message.media._]) {
foundMsgs.push(message.id);
if (foundMsgs.length >= neededLimit) {
break;
}
}
}
}
// console.log(dT(), sameSearchCache, foundMsgs, lastSearchResults);
if (foundMsgs.length < neededLimit && lastSearchResults.length && sameSearchCache) {
var minID = foundMsgs.length ? foundMsgs[foundMsgs.length - 1] : 0xFFFFFFFF;
for (var i = 0; i < lastSearchResults.length; i++) {
if (lastSearchResults[i] < minID) {
foundMsgs.push(lastSearchResults[i]);
if (foundMsgs.length >= neededLimit) {
break;
}
}
}
}
// console.log(dT(), foundMsgs);
}
if (foundMsgs.length || limit == 1000) {
if (useSearchCache) {
lastSearchResults = listMergeSorted(lastSearchResults, foundMsgs);
}
return $q.when({
count: null,
history: foundMsgs
});
}
return MtpApiManager.invokeApi('messages.search', {
peer: inputPeer,
q: query || '',
filter: inputFilter || {_: 'inputMessagesFilterEmpty'},
min_date: 0,
max_date: 0,
limit: limit || 20,
max_id: maxID || 0
}).then(function (searchResult) {
AppUsersManager.saveApiUsers(searchResult.users);
AppChatsManager.saveApiChats(searchResult.chats);
saveMessages(searchResult.messages);
var foundCount = searchResult.count || searchResult.messages.length;
foundMsgs = [];
angular.forEach(searchResult.messages, function (message) {
foundMsgs.push(message.id);
});
if (useSearchCache) {
lastSearchResults = listMergeSorted(lastSearchResults, foundMsgs);
}
return {
count: foundCount,
history: foundMsgs
};
}, function (error) {
if (error.code == 400) {
error.handled = true;
}
return $q.reject(error);
});
}
function getMessage (messageID) {
return messagesStorage[messageID] || {deleted: true};
}
function deleteMessages (messageIDs) {
return MtpApiManager.invokeApi('messages.deleteMessages', {
id: messageIDs
}).then(function (affectedMessages) {
ApiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateDeleteMessages',
messages: messageIDs,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
});
return messageIDs;
});
}
function processAffectedHistory (inputPeer, affectedHistory, method) {
ApiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updatePts',
pts: affectedHistory.pts,
pts_count: affectedHistory.pts_count
}
});
if (!affectedHistory.offset) {
return $q.when();
}
return MtpApiManager.invokeApi(method, {
peer: inputPeer,
offset: affectedHistory.offset,
max_id: 0
}).then(function (affectedHistory) {
return processAffectedHistory(inputPeer, affectedHistory, method);
});
}
function readHistory (inputPeer) {
// console.trace('start read');
var peerID = AppPeersManager.getPeerID(inputPeer),
historyStorage = historiesStorage[peerID],
foundDialog = getDialogByPeerID(peerID);
if (!foundDialog[0] || !foundDialog[0].unread_count) {
if (!historyStorage || !historyStorage.history.length) {
return false;
}
var messageID,
message,
foundUnread = false;
for (i = historyStorage.history.length; i >= 0; i--) {
messageID = historyStorage.history[i];
message = messagesStorage[messageID];
// console.log('ms', message);
if (message && !message.out && message.unread) {
foundUnread = true;
break;
}
}
if (!foundUnread) {
return false;
}
}
if (historyStorage.readPromise) {
return historyStorage.readPromise;
}
historyStorage.readPromise = MtpApiManager.invokeApi('messages.readHistory', {
peer: inputPeer,
offset: 0,
max_id: 0
}).then(function (affectedHistory) {
return processAffectedHistory(inputPeer, affectedHistory, 'messages.readHistory');
}).then(function () {
if (foundDialog[0]) {
// console.log('done read history', peerID);
foundDialog[0].unread_count = 0;
$rootScope.$broadcast('dialog_unread', {peerID: peerID, count: 0});
$rootScope.$broadcast('messages_read');
}
})['finally'](function () {
delete historyStorage.readPromise;
});
if (historyStorage && historyStorage.history.length) {
var messageID, message, i, peerID, foundDialog, dialog;
for (i = 0; i < historyStorage.history.length; i++) {
messageID = historyStorage.history[i];
message = messagesStorage[messageID];
if (message && !message.out) {
message.unread = false;
if (messagesForHistory[messageID]) {
messagesForHistory[messageID].unread = false;
}
if (messagesForDialogs[messageID]) {
messagesForDialogs[messageID].unread = false;
}
NotificationsManager.cancel('msg' + messageID);
}
}
}
NotificationsManager.soundReset(AppPeersManager.getPeerString(peerID))
return historyStorage.readPromise;
}
function readMessages (messageIDs) {
MtpApiManager.invokeApi('messages.readMessageContents', {
id: messageIDs
}).then(function (affectedMessages) {
ApiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateReadMessagesContents',
messages: messageIDs,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
});
});
}
function flushHistory (inputPeer) {
// console.log('start flush');
var peerID = AppPeersManager.getPeerID(inputPeer),
historyStorage = historiesStorage[peerID];
return MtpApiManager.invokeApi('messages.deleteHistory', {
peer: inputPeer,
offset: 0
}).then(function (affectedHistory) {
return processAffectedHistory(inputPeer, affectedHistory, 'messages.deleteHistory');
}).then(function () {
var foundDialog = getDialogByPeerID(peerID);
if (foundDialog[0]) {
dialogsStorage.dialogs.splice(foundDialog[1], 1);
}
delete historiesStorage[peerID];
$rootScope.$broadcast('dialog_flush', {peerID: peerID});
});
}
function saveMessages (apiMessages) {
angular.forEach(apiMessages, function (apiMessage) {
apiMessage.unread = apiMessage.flags & 1 ? true : false;
apiMessage.out = apiMessage.flags & 2 ? true : false;
apiMessage.media_unread = apiMessage.flags & 32 ? true : false;
messagesStorage[apiMessage.id] = apiMessage;
apiMessage.date -= serverTimeOffset;
var mediaContext = {
user_id: apiMessage.from_id,
date: apiMessage.date
};
if (apiMessage.media) {
switch (apiMessage.media._) {
case 'messageMediaEmpty':
delete apiMessage.media;
break;
case 'messageMediaPhoto':
AppPhotosManager.savePhoto(apiMessage.media.photo, mediaContext);
break;
case 'messageMediaVideo':
AppVideoManager.saveVideo(apiMessage.media.video, mediaContext);
break;
case 'messageMediaDocument':
AppDocsManager.saveDoc(apiMessage.media.document, mediaContext);
break;
case 'messageMediaAudio':
AppAudioManager.saveAudio(apiMessage.media.audio);
break;
case 'messageMediaWebPage':
AppWebPagesManager.saveWebPage(apiMessage.media.webpage, apiMessage.id, mediaContext);
break;
}
}
if (apiMessage.action && apiMessage.action._ == 'messageActionChatEditPhoto') {
AppPhotosManager.savePhoto(apiMessage.action.photo, mediaContext);
}
if (apiMessage.reply_markup) {
apiMessage.reply_markup.pFlags = {
resize: (apiMessage.reply_markup.flags & 1) > 0,
one_time: (apiMessage.reply_markup.flags & 2) > 0,
selective: (apiMessage.reply_markup.flags & 4) > 0
};
}
});
}
function sendText(peerID, text, options) {
if (!angular.isString(text) || !text.length) {
return;
}
options = options || {};
var messageID = tempID--,
randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)],
randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(),
historyStorage = historiesStorage[peerID],
inputPeer = AppPeersManager.getInputPeerByID(peerID),
flags = 0,
replyToMsgID = options.replyToMsgID,
message;
if (historyStorage === undefined) {
historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []};
}
MtpApiManager.getUserID().then(function (fromID) {
if (peerID != fromID) {
flags |= 2;
if (!AppUsersManager.isBot(peerID)) {
flags |= 1;
}
}
if (replyToMsgID) {
flags |= 8;
}
message = {
_: 'message',
id: messageID,
from_id: fromID,
to_id: AppPeersManager.getOutputPeer(peerID),
flags: flags,
date: tsNow(true) + serverTimeOffset,
message: text,
random_id: randomIDS,
reply_to_msg_id: replyToMsgID,
pending: true
};
var toggleError = function (on) {
var historyMessage = messagesForHistory[messageID];
if (on) {
message.error = true;
if (historyMessage) {
historyMessage.error = true;
}
} else {
delete message.error;
if (historyMessage) {
delete historyMessage.error;
}
}
$rootScope.$broadcast('messages_pending');
}
message.send = function () {
toggleError(false);
var sentRequestOptions = {};
if (pendingAfterMsgs[peerID]) {
sentRequestOptions.afterMessageID = pendingAfterMsgs[peerID].messageID;
}
var flags = 0;
if (replyToMsgID) {
flags |= 1;
}
MtpApiManager.invokeApi('messages.sendMessage', {
flags: flags,
peer: inputPeer,
message: text,
random_id: randomID,
reply_to_msg_id: replyToMsgID
}, sentRequestOptions).then(function (sentMessage) {
message.date = sentMessage.date;
message.id = sentMessage.id;
message.media = sentMessage.media;
ApiUpdatesManager.processUpdateMessage({
_: 'updates',
users: [],
chats: [],
seq: 0,
updates: [{
_: 'updateMessageID',
random_id: randomIDS,
id: sentMessage.id
}, {
_: 'updateNewMessage',
message: message,
pts: sentMessage.pts,
pts_count: sentMessage.pts_count
}]
});
}, function (error) {
toggleError(true);
})['finally'](function () {
if (pendingAfterMsgs[peerID] === sentRequestOptions) {
delete pendingAfterMsgs[peerID];
}
});
pendingAfterMsgs[peerID] = sentRequestOptions;
};
saveMessages([message]);
historyStorage.pending.unshift(messageID);
$rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true});
// setTimeout(function () {
message.send();
// }, 5000);
});
pendingByRandomID[randomIDS] = [peerID, messageID];
};
function sendFile(peerID, file, options) {
options = options || {};
var messageID = tempID--,
randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)],
randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(),
historyStorage = historiesStorage[peerID],
inputPeer = AppPeersManager.getInputPeerByID(peerID),
flags = 0,
replyToMsgID = options.replyToMsgID,
attachType, apiFileName, realFileName;
if (!options.isMedia) {
attachType = 'document';
apiFileName = 'document.' + file.type.split('/')[1];
} else if (['image/jpeg', 'image/png', 'image/bmp'].indexOf(file.type) >= 0) {
attachType = 'photo';
apiFileName = 'photo.' + file.type.split('/')[1];
} else if (file.type.substr(0, 6) == 'audio/' || ['video/ogg'].indexOf(file.type) >= 0) {
attachType = 'audio';
apiFileName = 'audio.' + (file.type.split('/')[1] == 'ogg' ? 'ogg' : 'mp3');
} else if (file.type.substr(0, 6) == 'video/') {
attachType = 'video';
apiFileName = 'video.mp4';
} else {
attachType = 'document';
apiFileName = 'document.' + file.type.split('/')[1];
}
if (historyStorage === undefined) {
historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []};
}
MtpApiManager.getUserID().then(function (fromID) {
if (peerID != fromID) {
flags |= 2;
if (!AppUsersManager.isBot(peerID)) {
flags |= 1;
}
}
if (replyToMsgID) {
flags |= 8;
}
var media = {
_: 'messageMediaPending',
type: attachType,
file_name: file.name || apiFileName,
size: file.size,
progress: {percent: 1, total: file.size}
};
var message = {
_: 'message',
id: messageID,
from_id: fromID,
to_id: AppPeersManager.getOutputPeer(peerID),
flags: flags,
date: tsNow(true) + serverTimeOffset,
message: '',
media: media,
random_id: randomIDS,
reply_to_msg_id: replyToMsgID,
pending: true
};
var toggleError = function (on) {
var historyMessage = messagesForHistory[messageID];
if (on) {
message.error = true;
if (historyMessage) {
historyMessage.error = true;
}
} else {
delete message.error;
if (historyMessage) {
delete historyMessage.error;
}
}
$rootScope.$broadcast('messages_pending');
}
var uploaded = false,
uploadPromise;
message.send = function () {
var sendFileDeferred = $q.defer();
sendFilePromise.then(function () {
if (!uploaded || message.error) {
uploaded = false;
uploadPromise = MtpApiFileManager.uploadFile(file);
}
uploadPromise.then(function (inputFile) {
inputFile.name = apiFileName;
uploaded = true;
var inputMedia;
switch (attachType) {
case 'photo':
inputMedia = {_: 'inputMediaUploadedPhoto', file: inputFile};
break;
case 'video':
inputMedia = {_: 'inputMediaUploadedVideo', file: inputFile, duration: 0, w: 0, h: 0, mime_type: file.type};
break;
case 'audio':
inputMedia = {_: 'inputMediaUploadedAudio', file: inputFile, duration: 0, mime_type: file.type};
break;
case 'document':
default:
inputMedia = {_: 'inputMediaUploadedDocument', file: inputFile, mime_type: file.type, attributes: [
{_: 'documentAttributeFilename', file_name: file.name}
]};
}
var flags = 0;
if (replyToMsgID) {
flags |= 1;
}
MtpApiManager.invokeApi('messages.sendMedia', {
flags: flags,
peer: inputPeer,
media: inputMedia,
random_id: randomID,
reply_to_msg_id: replyToMsgID
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
}, function (error) {
if (attachType == 'photo' &&
error.code == 400 &&
error.type == 'PHOTO_INVALID_DIMENSIONS') {
error.handled = true;
attachType = 'document';
message.send();
return;
}
toggleError(true);
});
}, function (error) {
toggleError(true);
}, function (progress) {
// console.log('upload progress', progress);
media.progress.done = progress.done;
media.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update', {peerID: peerID});
});
media.progress.cancel = function () {
if (!uploaded) {
sendFileDeferred.resolve();
uploadPromise.cancel();
cancelPendingMessage(randomIDS);
}
}
uploadPromise['finally'](function () {
sendFileDeferred.resolve();
});
});
sendFilePromise = sendFileDeferred.promise;
};
saveMessages([message]);
historyStorage.pending.unshift(messageID);
$rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true});
message.send();
});
pendingByRandomID[randomIDS] = [peerID, messageID];
}
function sendOther(peerID, inputMedia, options) {
options = options || {};
var messageID = tempID--,
randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)],
randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(),
historyStorage = historiesStorage[peerID],
inputPeer = AppPeersManager.getInputPeerByID(peerID),
replyToMsgID = options.replyToMsgID;
if (historyStorage === undefined) {
historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []};
}
MtpApiManager.getUserID().then(function (fromID) {
var media;
switch (inputMedia._) {
case 'inputMediaContact':
media = angular.extend({}, inputMedia, {_: 'messageMediaContact'});
break;
case 'inputMediaPhoto':
media = {
_: 'messageMediaPhoto',
photo: AppPhotosManager.getPhoto(inputMedia.id.id)
};
break;
case 'inputMediaDocument':
media = {
_: 'messageMediaDocument',
'document': AppDocsManager.getDoc(inputMedia.id.id)
};
break;
}
var flags = 0;
if (peerID != fromID) {
flags |= 2;
if (!AppUsersManager.isBot(peerID)) {
flags |= 1;
}
}
var message = {
_: 'message',
id: messageID,
from_id: fromID,
to_id: AppPeersManager.getOutputPeer(peerID),
flags: flags,
date: tsNow(true) + serverTimeOffset,
message: '',
media: media,
random_id: randomIDS,
pending: true
};
var toggleError = function (on) {
var historyMessage = messagesForHistory[messageID];
if (on) {
message.error = true;
if (historyMessage) {
historyMessage.error = true;
}
} else {
delete message.error;
if (historyMessage) {
delete historyMessage.error;
}
}
$rootScope.$broadcast('messages_pending');
}
message.send = function () {
var flags = 0;
if (replyToMsgID) {
flags |= 1;
}
MtpApiManager.invokeApi('messages.sendMedia', {
flags: flags,
peer: inputPeer,
media: inputMedia,
random_id: randomID,
reply_to_msg_id: replyToMsgID
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
}, function (error) {
toggleError(true);
});
};
saveMessages([message]);
historyStorage.pending.unshift(messageID);
$rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true});
message.send();
});
pendingByRandomID[randomIDS] = [peerID, messageID];
}
function forwardMessages (peerID, msgIDs) {
msgIDs = msgIDs.sort();
var randomIDs = [];
var i;
var len = msgIDs.length;
for (var i = 0; i < msgIDs.length; i++) {
randomIDs.push([nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]);
}
return MtpApiManager.invokeApi('messages.forwardMessages', {
peer: AppPeersManager.getInputPeerByID(peerID),
id: msgIDs,
random_id: randomIDs
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
});
};
function startBot (botID, chatID, startParam) {
if (startParam) {
var randomID = bigint(nextRandomInt(0xFFFFFFFF)).shiftLeft(32).add(bigint(nextRandomInt(0xFFFFFFFF))).toString();
return MtpApiManager.invokeApi('messages.startBot', {
bot: AppUsersManager.getUserInput(botID),
chat_id: chatID,
random_id: randomID,
start_param: startParam
});
}
var peerID = chatID ? -chatID : botID;
var inputPeer = AppPeersManager.getInputPeerByID(peerID);
if (chatID) {
return MtpApiManager.invokeApi('messages.addChatUser', {
chat_id: chatID,
user_id: AppUsersManager.getUserInput(botID)
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
}, function (error) {
if (error && error.type == 'USER_ALREADY_PARTICIPANT') {
var bot = AppUsersManager.getUser(botID);
sendText(-chatID, '/start@' + bot.username);
error.handled = true;
}
});
}
return sendText(botID, '/start');
}
function cancelPendingMessage (randomID) {
var pendingData = pendingByRandomID[randomID];
console.log('pending', randomID, pendingData);
if (pendingData) {
var peerID = pendingData[0],
tempID = pendingData[1],
historyStorage = historiesStorage[peerID],
pos = historyStorage.pending.indexOf(tempID);
ApiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateDeleteMessages',
messages: [tempID]
}
});
if (pos != -1) {
historyStorage.pending.splice(pos, 1);
}
delete messagesForHistory[tempID];
delete messagesStorage[tempID];
return true;
}
return false;
}
function finalizePendingMessage(randomID, finalMessage) {
var pendingData = pendingByRandomID[randomID];
// console.log('pdata', randomID, pendingData);
if (pendingData) {
var peerID = pendingData[0],
tempID = pendingData[1],
historyStorage = historiesStorage[peerID],
message,
historyMessage;
// console.log('pending', randomID, historyStorage.pending);
var pos = historyStorage.pending.indexOf(tempID);
if (pos != -1) {
historyStorage.pending.splice(pos, 1);
}
if (message = messagesStorage[tempID]) {
delete message.pending;
delete message.error;
delete message.random_id;
delete message.send;
}
if (historyMessage = messagesForHistory[tempID]) {
messagesForHistory[finalMessage.id] = angular.extend(historyMessage, wrapForHistory(finalMessage.id));
delete historyMessage.pending;
delete historyMessage.error;
delete historyMessage.random_id;
delete historyMessage.send;
$rootScope.$broadcast('messages_pending');
}
delete messagesForHistory[tempID];
delete messagesStorage[tempID];
return message;
}
return false;
}
function openChatInviteLink (hash) {
return MtpApiManager.invokeApi('messages.checkChatInvite', {
hash: hash
}).then(function (chatInvite) {
var chatTitle;
if (chatInvite._ == 'chatInviteAlready') {
AppChatsManager.saveApiChat(chatInvite.chat);
if (!chatInvite.chat.left) {
return $rootScope.$broadcast('history_focus', {
peerString: AppChatsManager.getChatString(chatInvite.chat.id)
});
}
chatTitle = chatInvite.chat.title;
} else {
chatTitle = chatInvite.title;
}
ErrorService.confirm({
type: 'JOIN_GROUP_BY_LINK',
title: chatTitle
}).then(function () {
return MtpApiManager.invokeApi('messages.importChatInvite', {
hash: hash
}).then(function (updates) {
ApiUpdatesManager.processUpdateMessage(updates);
if (updates.updates && updates.updates.length) {
for (var i = 0, len = updates.updates.length, update; i < len; i++) {
update = updates.updates[i];
if (update._ == 'updateNewMessage') {
$rootScope.$broadcast('history_focus', {peerString: AppChatsManager.getChatString(update.message.to_id.chat_id)
});
break;
}
}
}
});
});
});
}
function getMessagePeer (message) {
var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0;
if (toID < 0) {
return toID;
} else if (message.out || message.flags & 2) {
return toID;
}
return message.from_id;
}
function wrapForDialog (msgID, unreadCount) {
var useCache = unreadCount != -1;
if (useCache && messagesForDialogs[msgID] !== undefined) {
return messagesForDialogs[msgID];
}
var message = angular.copy(messagesStorage[msgID]);
if (!message || !message.to_id) {
return message;
}
if (message.chatID = message.to_id.chat_id) {
message.peerID = -message.chatID;
message.peerData = AppChatsManager.getChat(message.chatID);
} else {
message.peerID = message.out ? message.to_id.user_id : message.from_id;
message.peerData = AppUsersManager.getUser(message.peerID);
}
message.peerString = AppPeersManager.getPeerString(message.peerID);
message.peerPhoto = AppPeersManager.getPeerPhoto(message.peerID, 'User', 'Group');
message.unreadCount = unreadCount;
if (message._ == 'messageService' && message.action.user_id) {
message.action.user = AppUsersManager.getUser(message.action.user_id);
}
if (message.message && message.message.length) {
message.richMessage = RichTextProcessor.wrapRichText(message.message.substr(0, 64), {noLinks: true, noLinebreaks: true});
}
message.dateText = dateOrTimeFilter(message.date);
if (useCache) {
messagesForDialogs[msgID] = message;
}
return message;
}
function wrapForHistory (msgID) {
if (messagesForHistory[msgID] !== undefined) {
return messagesForHistory[msgID];
}
var message = angular.copy(messagesStorage[msgID]) || {id: msgID};
if (message.media && message.media.progress !== undefined) {
message.media.progress = messagesStorage[msgID].media.progress;
}
var fromUser = AppUsersManager.getUser(message.from_id);
var fromBot = fromUser.pFlags.bot && fromUser.username || false;
var withBot = (fromBot ||
message.to_id && (
message.to_id.chat_id ||
message.to_id.user_id && AppUsersManager.isBot(message.to_id.user_id)
)
);
if (message.media) {
if (message.media.caption &&
message.media.caption.length) {
message.media.rCaption = RichTextProcessor.wrapRichText(message.media.caption, {
noCommands: !withBot,
fromBot: fromBot
});
}
switch (message.media._) {
case 'messageMediaPhoto':
message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id);
break;
case 'messageMediaVideo':
message.media.video = AppVideoManager.wrapForHistory(message.media.video.id);
break;
case 'messageMediaDocument':
message.media.document = AppDocsManager.wrapForHistory(message.media.document.id);
break;
case 'messageMediaAudio':
message.media.audio = AppAudioManager.wrapForHistory(message.media.audio.id);
break;
case 'messageMediaGeo':
var mapUrl = 'https://maps.google.com/?q=' + message.media.geo['lat'] + ',' + message.media.geo['long'];
message.media.mapUrl = $sce.trustAsResourceUrl(mapUrl);
break;
case 'messageMediaVenue':
var mapUrl;
if (message.media.provider == 'foursquare' &&
message.media.venue_id) {
mapUrl = 'https://foursquare.com/v/' + encodeURIComponent(message.media.venue_id);
} else {
mapUrl = 'https://maps.google.com/?q=' + message.media.geo['lat'] + ',' + message.media.geo['long'];
}
message.media.mapUrl = $sce.trustAsResourceUrl(mapUrl);
break;
case 'messageMediaContact':
message.media.rFullName = RichTextProcessor.wrapRichText(
message.media.first_name + ' ' + (message.media.last_name || ''),
{noLinks: true, noLinebreaks: true}
);
break;
case 'messageMediaWebPage':
if (!message.media.webpage ||
message.media.webpage._ == 'webPageEmpty' ||
Config.Mobile) {
delete message.media;
break;
}
message.media.webpage = AppWebPagesManager.wrapForHistory(message.media.webpage.id);
break;
}
}
else if (message.action) {
switch (message.action._) {
case 'messageActionChatEditPhoto':
message.action.photo = AppPhotosManager.wrapForHistory(message.action.photo.id);
break;
case 'messageActionChatCreate':
case 'messageActionChatEditTitle':
message.action.rTitle = RichTextProcessor.wrapRichText(message.action.title, {noLinks: true, noLinebreaks: true}) || _('chat_title_deleted');
break;
case 'messageActionBotIntro':
message.action.rDescription = RichTextProcessor.wrapRichText(message.action.description, {
noCommands: !withBot,
fromBot: fromBot
});
break;
}
}
var replyToMsgID = message.reply_to_msg_id;
if (replyToMsgID) {
if (messagesStorage[replyToMsgID]) {
message.reply_to_msg = wrapForDialog(replyToMsgID);
} else {
message.reply_to_msg = {id: replyToMsgID, loading: true};
if (needSingleMessages.indexOf(replyToMsgID) == -1) {
needSingleMessages.push(replyToMsgID);
if (fetchSingleMessagesTimeout === false) {
fetchSingleMessagesTimeout = setTimeout(fetchSingleMessages, 100);
}
}
}
}
if (message.message && message.message.length) {
var options = {
noCommands: !withBot,
fromBot: fromBot
};
if (!Config.Navigator.mobile) {
options.extractUrlEmbed = true;
}
if (message.flags & 16) {
var user = AppUsersManager.getSelf();
if (user) {
options.highlightUsername = user.username;
}
}
message.richMessage = RichTextProcessor.wrapRichText(message.message, options);
if (options.extractedUrlEmbed) {
message.richUrlEmbed = options.extractedUrlEmbed;
}
}
return messagesForHistory[msgID] = message;
}
function wrapReplyMarkup (replyMarkup) {
if (!replyMarkup ||
replyMarkup._ == 'replyKeyboardHide') {
return false;
}
if (replyMarkup.wrapped) {
return replyMarkup;
}
var count = replyMarkup.rows && replyMarkup.rows.length || 0;
if (count > 0 && count <= 4 && !replyMarkup.pFlags.resize) {
replyMarkup.splitCount = count;
}
replyMarkup.wrapped = true;
angular.forEach(replyMarkup.rows, function (markupRow) {
angular.forEach(markupRow.buttons, function (markupButton) {
markupButton.rText = RichTextProcessor.wrapRichText(markupButton.text, {noLinks: true, noLinebreaks: true});
})
})
if (nextRandomInt(1)) {
replyMarkup.rows = replyMarkup.rows.slice(0, 2);
}
return replyMarkup;
}
function fetchSingleMessages () {
if (fetchSingleMessagesTimeout !== false) {
clearTimeout(fetchSingleMessagesTimeout);
fetchSingleMessagesTimeout = false;
}
if (!needSingleMessages.length) {
return;
}
var msgIDs = needSingleMessages.slice();
needSingleMessages = [];
MtpApiManager.invokeApi('messages.getMessages', {
id: msgIDs
}).then(function (getMessagesResult) {
AppUsersManager.saveApiUsers(getMessagesResult.users);
AppChatsManager.saveApiChats(getMessagesResult.chats);
saveMessages(getMessagesResult.messages);
$rootScope.$broadcast('messages_downloaded', msgIDs);
})
}
function regroupWrappedHistory (history, limit) {
if (!history || !history.length) {
return false;
}
var start = 0,
len = history.length,
end = len,
i, curDay, prevDay, curMessage, prevMessage, curGrouped, prevGrouped,
wasUpdated = false,
groupFwd = !Config.Mobile;
if (limit > 0) {
end = Math.min(limit, len);
} else if (limit < 0) {
start = Math.max(0, end + limit);
}
for (i = start; i < end; i++) {
curMessage = history[i];
curDay = Math.floor((curMessage.date + midnightOffset) / 86400);
prevGrouped = prevMessage && prevMessage.grouped;
curGrouped = curMessage.grouped;
if (curDay === prevDay) {
if (curMessage.needDate) {
delete curMessage.needDate;
wasUpdated = true;
}
} else if (!i || prevMessage) {
if (!curMessage.needDate) {
curMessage.needDate = true;
wasUpdated = true;
}
}
if (curMessage.fwd_from_id &&
curMessage.media &&
curMessage.media.document &&
curMessage.media.document.sticker &&
(curMessage.from_id != (prevMessage || {}).from_id || !(prevMessage || {}).fwd_from_id)) {
delete curMessage.fwd_from_id;
curMessage._ = 'message';
}
if (prevMessage &&
curMessage.from_id == prevMessage.from_id &&
!prevMessage.fwd_from_id == !curMessage.fwd_from_id &&
!prevMessage.action &&
!curMessage.action &&
curMessage.date < prevMessage.date + 900) {
var singleLine = curMessage.message && curMessage.message.length < 70 && curMessage.message.indexOf("\n") == -1 && !curMessage.reply_to_msg_id;
if (groupFwd && curMessage.fwd_from_id && curMessage.fwd_from_id == prevMessage.fwd_from_id) {
curMessage.grouped = singleLine ? 'im_grouped_fwd_short' : 'im_grouped_fwd';
} else {
curMessage.grouped = !curMessage.fwd_from_id && singleLine ? 'im_grouped_short' : 'im_grouped';
}
if (groupFwd && curMessage.fwd_from_id) {
if (!prevMessage.grouped) {
prevMessage.grouped = 'im_grouped_fwd_start';
}
if (curMessage.grouped && i == len - 1) {
curMessage.grouped += ' im_grouped_fwd_end';
}
}
} else if (prevMessage || !i) {
delete curMessage.grouped;
if (groupFwd && prevMessage && prevMessage.grouped && prevMessage.fwd_from_id) {
prevMessage.grouped += ' im_grouped_fwd_end';
}
}
if (!wasUpdated && prevGrouped != (prevMessage && prevMessage.grouped)) {
wasUpdated = true;
}
prevMessage = curMessage;
prevDay = curDay;
}
if (!wasUpdated && curGrouped != (prevMessage && prevMessage.grouped)) {
wasUpdated = true;
}
return wasUpdated;
}
function getDialogByPeerID (peerID) {
for (var i = 0; i < dialogsStorage.dialogs.length; i++) {
if (dialogsStorage.dialogs[i].peerID == peerID) {
return [dialogsStorage.dialogs[i], i];
}
}
return [];
}
function incrementMaxSeenID (maxID) {
if (maxSeenID !== false && maxID && maxID > maxSeenID) {
Storage.set({
max_seen_msg: maxID
});
}
}
function notifyAboutMessage (message, options) {
options = options || {};
var peerID = getMessagePeer(message);
var fromUser = AppUsersManager.getUser(message.from_id);
var fromPhoto = AppUsersManager.getUserPhoto(message.from_id, 'User');
var peerString;
var notification = {},
notificationMessage = false,
notificationPhoto;
var notifySettings = NotificationsManager.getNotifySettings();
if (message.fwd_from_id && options.fwd_count) {
notificationMessage = fwdMessagesPluralize(options.fwd_count);
} else if (message.message) {
if (notifySettings.nopreview) {
notificationMessage = _('conversation_message_sent');
} else {
notificationMessage = RichTextProcessor.wrapPlainText(message.message);
}
} else if (message.media) {
switch (message.media._) {
case 'messageMediaPhoto': notificationMessage = _('conversation_media_photo_raw'); break;
case 'messageMediaVideo': notificationMessage = _('conversation_media_video_raw'); break;
case 'messageMediaDocument':
if (message.media.document.sticker) {
notificationMessage = _('conversation_media_sticker');
var stickerEmoji = message.media.document.stickerEmojiRaw;
if (stickerEmoji !== undefined) {
notificationMessage = RichTextProcessor.wrapPlainText(stickerEmoji) + ' (' + notificationMessage + ')';
}
} else {
notificationMessage = message.media.document.file_name || _('conversation_media_document_raw');
}
break;
case 'messageMediaAudio': notificationMessage = _('conversation_media_audio_raw'); break;
case 'messageMediaGeo':
case 'messageMediaVenue': notificationMessage = _('conversation_media_location_raw'); break;
case 'messageMediaContact': notificationMessage = _('conversation_media_contact_raw'); break;
default: notificationMessage = _('conversation_media_attachment_raw'); break;
}
} else if (message._ == 'messageService') {
switch (message.action._) {
case 'messageActionChatCreate': notificationMessage = _('conversation_group_created_raw'); break;
case 'messageActionChatEditTitle': notificationMessage = _('conversation_group_renamed_raw'); break;
case 'messageActionChatEditPhoto': notificationMessage = _('conversation_group_photo_updated_raw'); break;
case 'messageActionChatDeletePhoto': notificationMessage = _('conversation_group_photo_removed_raw'); break;
case 'messageActionChatAddUser':
notificationMessage = message.action.user_id == message.from_id ? _('conversation_returned_to_group') : _('conversation_invited_user_message_raw');
break;
case 'messageActionChatDeleteUser':
notificationMessage = message.action.user_id == message.from_id ? _('conversation_left_group') : _('conversation_kicked_user_message_raw');
break;
case 'messageActionChatJoinedByLink':
notificationMessage = _('conversation_joined_by_link');
break;
}
}
if (peerID > 0) {
notification.title = (fromUser.first_name || '') +
(fromUser.first_name && fromUser.last_name ? ' ' : '') +
(fromUser.last_name || '');
if (!notification.title) {
notification.title = fromUser.phone || _('conversation_unknown_user_raw');
}
notificationPhoto = fromPhoto;
peerString = AppUsersManager.getUserString(peerID);
} else {
notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) +
' @ ' +
(AppChatsManager.getChat(-peerID).title || _('conversation_unknown_chat_raw'));
notificationPhoto = AppChatsManager.getChatPhoto(-peerID, 'Group');
peerString = AppChatsManager.getChatString(-peerID);
}
notification.title = RichTextProcessor.wrapPlainText(notification.title);
notification.onclick = function () {
$rootScope.$broadcast('history_focus', {
peerString: peerString,
messageID: message.flags & 16 ? message.id : 0,
});
};
notification.message = notificationMessage;
notification.image = notificationPhoto.placeholder;
notification.key = 'msg' + message.id;
notification.tag = peerString;
if (notificationPhoto.location && !notificationPhoto.location.empty) {
MtpApiFileManager.downloadSmallFile(notificationPhoto.location, notificationPhoto.size).then(function (blob) {
notification.image = FileManager.getUrl(blob, 'image/jpeg');
if (message.unread) {
NotificationsManager.notify(notification);
}
});
} else {
NotificationsManager.notify(notification);
}
}
if (window.navigator.mozSetMessageHandler) {
window.navigator.mozSetMessageHandler('activity', function(activityRequest) {
var source = activityRequest.source;
console.log(dT(), 'Received activity', source.name, source.data);
if (source.name === 'share' && source.data.blobs.length > 0) {
PeersSelectService.selectPeers({confirm_type: 'EXT_SHARE_PEER'}).then(function (peerStrings) {
angular.forEach(peerStrings, function (peerString) {
var peerID = AppPeersManager.getPeerID(peerString);
angular.forEach(source.data.blobs, function (blob) {
sendFile(peerID, blob, {isMedia: true});
});
})
if (peerStrings.length == 1) {
$rootScope.$broadcast('history_focus', {peerString: peerStrings[0]});
}
});
}
});
}
var newMessagesHandlePromise = false;
var newMessagesToHandle = {};
var newDialogsHandlePromise = false;
var newDialogsToHandle = {};
var notificationsHandlePromise = false;
var notificationsToHandle = {};
function handleNewMessages () {
$timeout.cancel(newMessagesHandlePromise);
newMessagesHandlePromise = false;
$rootScope.$broadcast('history_multiappend', newMessagesToHandle);
newMessagesToHandle = {};
}
function handleNewDialogs () {
$timeout.cancel(newDialogsHandlePromise);
newDialogsHandlePromise = false;
$rootScope.$broadcast('dialogs_multiupdate', newDialogsToHandle);
newDialogsToHandle = {};
}
function handleNotifications () {
$timeout.cancel(notificationsHandlePromise);
notificationsHandlePromise = false;
var timeout = $rootScope.idle.isIDLE && StatusManager.isOtherDeviceActive() ? 30000 : 1000;
angular.forEach(notificationsToHandle, function (notifyPeerToHandle) {
notifyPeerToHandle.isMutedPromise.then(function (muted) {
var topMessage = notifyPeerToHandle.top_message;
if (muted ||
!topMessage.unread) {
return;
}
setTimeout(function () {
if (topMessage.unread) {
notifyAboutMessage(topMessage, {
fwd_count: notifyPeerToHandle.fwd_count
});
}
}, timeout);
});
});
notificationsToHandle = {};
}
$rootScope.$on('apiUpdate', function (e, update) {
// if (update._ != 'updateUserStatus') {
// console.log('on apiUpdate', update);
// }
switch (update._) {
case 'updateMessageID':
pendingByMessageID[update.id] = update.random_id;
break;
case 'updateNewMessage':
var message = update.message,
peerID = getMessagePeer(message),
historyStorage = historiesStorage[peerID];
if (historyStorage !== undefined) {
var history = historyStorage.history;
if (history.indexOf(message.id) != -1) {
return false;
}
var topMsgID = history[0];
history.unshift(message.id);
if (message.id > 0 && message.id < topMsgID) {
history.sort(function (a, b) {
return b - a;
});
}
if (historyStorage.count !== null) {
historyStorage.count++;
}
} else {
historyStorage = historiesStorage[peerID] = {
count: null,
history: [message.id],
pending: []
};
}
saveMessages([message]);
if (mergeReplyKeyboard(historyStorage, message)) {
$rootScope.$broadcast('history_reply_markup', {peerID: peerID})
}
if (!message.out) {
AppUsersManager.forceUserOnline(message.from_id);
}
var randomID = pendingByMessageID[message.id],
pendingMessage;
if (randomID) {
if (pendingMessage = finalizePendingMessage(randomID, message)) {
$rootScope.$broadcast('history_update', {peerID: peerID});
}
delete pendingByMessageID[message.id];
}
if (!pendingMessage) {
if (newMessagesToHandle[peerID] === undefined) {
newMessagesToHandle[peerID] = [];
}
newMessagesToHandle[peerID].push(message.id);
if (!newMessagesHandlePromise) {
newMessagesHandlePromise = $timeout(handleNewMessages, 0);
}
}
var foundDialog = getDialogByPeerID(peerID);
var dialog;
var inboxUnread = !message.out && message.unread;
if (foundDialog.length) {
dialog = foundDialog[0];
if (foundDialog[1] > 0) {
dialogsStorage.dialogs.splice(foundDialog[1], 1);
dialogsStorage.dialogs.unshift(dialog);
}
dialog.top_message = message.id;
if (inboxUnread) {
dialog.unread_count++;
}
} else {
SearchIndexManager.indexObject(peerID, AppPeersManager.getPeerSearchText(peerID), dialogsIndex);
dialog = {
peerID: peerID,
unread_count: inboxUnread ? 1 : 0,
top_message: message.id
};
dialogsStorage.dialogs.unshift(dialog);
}
newDialogsToHandle[peerID] = dialog;
if (!newDialogsHandlePromise) {
newDialogsHandlePromise = $timeout(handleNewDialogs, 0);
}
if (inboxUnread && ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE)) {
var notifyPeer = message.flags & 16 ? message.from_id : peerID;
var notifyPeerToHandle = notificationsToHandle[notifyPeer];
if (notifyPeerToHandle === undefined) {
notifyPeerToHandle = notificationsToHandle[notifyPeer] = {
isMutedPromise: NotificationsManager.getPeerMuted(notifyPeer),
fwd_count: 0,
from_id: 0
};
}
if (notifyPeerToHandle.from_id != message.from_id) {
notifyPeerToHandle.from_id = message.from_id;
notifyPeerToHandle.fwd_count = 0;
}
if (message.fwd_from_id) {
notifyPeerToHandle.fwd_count++;
}
notifyPeerToHandle.top_message = message;
if (!notificationsHandlePromise) {
notificationsHandlePromise = $timeout(handleNotifications, 1000);
}
}
incrementMaxSeenID(message.id);
break;
case 'updateReadHistoryInbox':
case 'updateReadHistoryOutbox':
var maxID = update.max_id;
var isOut = update._ == 'updateReadHistoryOutbox';
var peerID = AppPeersManager.getPeerID(update.peer);
var foundDialog = getDialogByPeerID(peerID);
var history = (historiesStorage[peerID] || {}).history || [];
var newUnreadCount = false;
var length = history.length;
var foundAffected = false;
var messageID, message, i;
if (peerID > 0 && isOut) {
AppUsersManager.forceUserOnline(peerID);
}
for (i = 0; i < length; i++) {
messageID = history[i];
if (messageID > maxID) {
continue;
}
message = messagesStorage[messageID];
if (message.out != isOut) {
continue;
}
if (!message.unread) {
break;
}
// console.log('read', messageID, message.unread, message);
if (message && message.unread) {
message.unread = false;
if (messagesForHistory[messageID]) {
messagesForHistory[messageID].unread = false;
if (!foundAffected) {
foundAffected = true;
}
}
if (messagesForDialogs[messageID]) {
messagesForDialogs[messageID].unread = false;
}
if (!message.out) {
if (foundDialog) {
newUnreadCount = --foundDialog[0].unread_count;
}
NotificationsManager.cancel('msg' + messageID);
}
}
}
if (newUnreadCount !== false) {
$rootScope.$broadcast('dialog_unread', {peerID: peerID, count: newUnreadCount});
}
if (foundAffected) {
$rootScope.$broadcast('messages_read');
}
break;
case 'updateReadMessagesContents':
var messages = update.messages;
var len = messages.length;
var i, messageID, message, historyMessage;
for (i = 0; i < len; i++) {
messageID = messages[i];
if (message = messagesStorage[messageID]) {
delete message.media_unread;
}
if (historyMessage = messagesForHistory[messageID]) {
delete historyMessage.media_unread;
}
}
break;
case 'updateDeleteMessages':
var dialogsUpdated = {},
historiesUpdated = {},
messageID, message, i, peerID, foundDialog, history;
for (i = 0; i < update.messages.length; i++) {
messageID = update.messages[i];
message = messagesStorage[messageID];
if (message) {
peerID = getMessagePeer(message);
history = historiesUpdated[peerID] || (historiesUpdated[peerID] = {count: 0, unread: 0, msgs: {}});
if (!message.out && message.unread) {
history.unread++;
NotificationsManager.cancel('msg' + messageID);
}
history.count++;
history.msgs[messageID] = true;
if (messagesForHistory[messageID]) {
messagesForHistory[messageID].deleted = true;
delete messagesForHistory[messageID];
}
if (messagesForDialogs[messageID]) {
messagesForDialogs[messageID].deleted = true;
delete messagesForDialogs[messageID];
}
message.deleted = true;
messagesStorage[messageID] = {
deleted: true,
id: messageID,
from_id: message.from_id,
to_id: message.to_id,
flags: message.flags,
out: message.out,
unread: message.unread,
date: message.date
};
}
}
angular.forEach(historiesUpdated, function (updatedData, peerID) {
var foundDialog = getDialogByPeerID(peerID);
if (foundDialog) {
if (updatedData.unread) {
foundDialog[0].unread_count -= updatedData.unread;
$rootScope.$broadcast('dialog_unread', {peerID: peerID, count: foundDialog[0].unread_count});
}
}
var historyStorage = historiesStorage[peerID];
if (historyStorage !== undefined) {
var newHistory = [],
newPending = [];
for (var i = 0; i < historyStorage.history.length; i++) {
if (!updatedData.msgs[historyStorage.history[i]]) {
newHistory.push(historyStorage.history[i]);
}
}
historyStorage.history = newHistory;
if (updatedData.count &&
historyStorage.count !== null &&
historyStorage.count > 0) {
historyStorage.count -= updatedData.count;
if (historyStorage.count < 0) {
historyStorage.count = 0;
}
}
for (var i = 0; i < historyStorage.pending.length; i++) {
if (!updatedData.msgs[historyStorage.pending[i]]) {
newPending.push(historyStorage.pending[i]);
}
}
historyStorage.pending = newPending;
$rootScope.$broadcast('history_delete', {peerID: peerID, msgs: updatedData.msgs});
}
});
break;
}
});
$rootScope.$on('webpage_updated', function (e, eventData) {
angular.forEach(eventData.msgs, function (msgID) {
var historyMessage = messagesForHistory[msgID];
if (historyMessage) {
historyMessage.media = {
_: 'messageMediaWebPage',
webpage: AppWebPagesManager.wrapForHistory(eventData.id)
};
}
})
})
return {
getDialogs: getDialogs,
getHistory: getHistory,
getSearch: getSearch,
getMessage: getMessage,
getReplyKeyboard: getReplyKeyboard,
readHistory: readHistory,
readMessages: readMessages,
flushHistory: flushHistory,
deleteMessages: deleteMessages,
saveMessages: saveMessages,
sendText: sendText,
sendFile: sendFile,
sendOther: sendOther,
forwardMessages: forwardMessages,
startBot: startBot,
openChatInviteLink: openChatInviteLink,
getMessagePeer: getMessagePeer,
wrapForDialog: wrapForDialog,
wrapForHistory: wrapForHistory,
wrapReplyMarkup: wrapReplyMarkup,
regroupWrappedHistory: regroupWrappedHistory
}
})
.service('AppPhotosManager', function ($modal, $window, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager, FileManager) {
var photos = {},
windowW = $(window).width(),
windowH = $(window).height();
function savePhoto (apiPhoto, context) {
if (context) {
angular.extend(apiPhoto, context);
}
photos[apiPhoto.id] = apiPhoto;
angular.forEach(apiPhoto.sizes, function (photoSize) {
if (photoSize._ == 'photoCachedSize') {
MtpApiFileManager.saveSmallFile(photoSize.location, photoSize.bytes);
// Memory
photoSize.size = photoSize.bytes.length;
delete photoSize.bytes;
photoSize._ = 'photoSize';
}
});
};
function choosePhotoSize (photo, width, height) {
if (Config.Navigator.retina) {
width *= 2;
height *= 2;
}
var bestPhotoSize = {_: 'photoSizeEmpty'},
bestDiff = 0xFFFFFF;
angular.forEach(photo.sizes, function (photoSize) {
var diff = Math.abs(photoSize.w * photoSize.h - width * height);
if (diff < bestDiff) {
bestPhotoSize = photoSize;
bestDiff = diff;
}
});
// console.log('choosing', photo, width, height, bestPhotoSize);
return bestPhotoSize;
}
function getUserPhotos (userID, maxID, limit) {
var inputUser = AppUsersManager.getUserInput(userID);
return MtpApiManager.invokeApi('photos.getUserPhotos', {
user_id: inputUser,
offset: 0,
limit: limit || 20,
max_id: maxID || 0
}).then(function (photosResult) {
AppUsersManager.saveApiUsers(photosResult.users);
var photoIDs = [];
var context = {user_id: userID};
for (var i = 0; i < photosResult.photos.length; i++) {
savePhoto(photosResult.photos[i], context);
photoIDs.push(photosResult.photos[i].id)
}
return {
count: photosResult.count || photosResult.photos.length,
photos: photoIDs
};
});
}
function preloadPhoto (photoID) {
if (!photos[photoID]) {
return;
}
var photo = photos[photoID];
var fullWidth = $(window).width() - (Config.Mobile ? 20 : 32);
var fullHeight = $($window).height() - (Config.Mobile ? 150 : 116);
if (fullWidth > 800) {
fullWidth -= 208;
}
var fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight);
if (fullPhotoSize && !fullPhotoSize.preloaded) {
fullPhotoSize.preloaded = true;
if (fullPhotoSize.size) {
MtpApiFileManager.downloadFile(fullPhotoSize.location.dc_id, {
_: 'inputFileLocation',
volume_id: fullPhotoSize.location.volume_id,
local_id: fullPhotoSize.location.local_id,
secret: fullPhotoSize.location.secret
}, fullPhotoSize.size);
} else {
MtpApiFileManager.downloadSmallFile(fullPhotoSize.location);
}
}
};
$rootScope.preloadPhoto = preloadPhoto;
function getPhoto (photoID) {
return photos[photoID] || {_: 'photoEmpty'};
}
function wrapForHistory (photoID, options) {
options = options || {};
var photo = angular.copy(photos[photoID]) || {_: 'photoEmpty'},
width = options.website ? 100 : Math.min(windowW - 80, Config.Mobile ? 210 : 260),
height = options.website ? 100 : Math.min(windowH - 100, Config.Mobile ? 210 : 260),
thumbPhotoSize = choosePhotoSize(photo, width, height),
thumb = {
placeholder: 'img/placeholders/PhotoThumbConversation.gif',
width: width,
height: height
};
// console.log('chosen photo size', photoID, thumbPhotoSize);
if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') {
var dim = calcImageInBox(thumbPhotoSize.w, thumbPhotoSize.h, width, height);
thumb.width = dim.w;
thumb.height = dim.h;
thumb.location = thumbPhotoSize.location;
thumb.size = thumbPhotoSize.size;
} else {
thumb.width = 100;
thumb.height = 100;
}
photo.thumb = thumb;
return photo;
}
function wrapForFull (photoID) {
var photo = wrapForHistory(photoID);
var fullWidth = $(window).width() - (Config.Mobile ? 0 : 32);
var fullHeight = $($window).height() - (Config.Mobile ? 0 : 116);
if (!Config.Mobile && fullWidth > 800) {
fullWidth -= 208;
}
var fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight);
var full = {
placeholder: 'img/placeholders/PhotoThumbModal.gif'
};
full.width = fullWidth;
full.height = fullHeight;
if (fullPhotoSize && fullPhotoSize._ != 'photoSizeEmpty') {
var wh = calcImageInBox(fullPhotoSize.w, fullPhotoSize.h, fullWidth, fullHeight, true);
full.width = wh.w;
full.height = wh.h;
full.modalWidth = Math.max(full.width, Math.min(400, fullWidth));
full.location = fullPhotoSize.location;
full.size = fullPhotoSize.size;
}
photo.full = full;
return photo;
}
function openPhoto (photoID, list) {
if (!photoID || photoID === '0') {
return false;
}
var scope = $rootScope.$new(true);
scope.photoID = photoID;
var controller = 'PhotoModalController';
if (list && list.p > 0) {
controller = 'UserpicModalController';
scope.userID = list.p;
}
else if (list && list.p < 0) {
controller = 'ChatpicModalController';
scope.chatID = -list.p;
}
else if (list && list.m > 0) {
scope.messageID = list.m;
if (list.w) {
scope.webpageID = list.w;
}
}
var modalInstance = $modal.open({
templateUrl: templateUrl('photo_modal'),
windowTemplateUrl: templateUrl('media_modal_layout'),
controller: controller,
scope: scope,
windowClass: 'photo_modal_window'
});
}
function downloadPhoto (photoID) {
var photo = photos[photoID],
ext = 'jpg',
mimeType = 'image/jpeg',
fileName = 'photo' + photoID + '.' + ext,
fullWidth = Math.max(screen.width || 0, $(window).width() - 36, 800),
fullHeight = Math.max(screen.height || 0, $($window).height() - 150, 800),
fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight),
inputFileLocation = {
_: 'inputFileLocation',
volume_id: fullPhotoSize.location.volume_id,
local_id: fullPhotoSize.location.local_id,
secret: fullPhotoSize.location.secret
};
FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) {
if (writableFileEntry) {
MtpApiFileManager.downloadFile(
fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {
mime: mimeType,
toFileEntry: writableFileEntry
}).then(function () {
// console.log('file save done');
}, function (e) {
console.log('photo download failed', e);
});
}
}, function () {
var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation);
if (cachedBlob) {
return FileManager.download(cachedBlob, mimeType, fileName);
}
MtpApiFileManager.downloadFile(
fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType}
).then(function (blob) {
FileManager.download(blob, mimeType, fileName);
}, function (e) {
console.log('photo download failed', e);
});
});
};
$rootScope.openPhoto = openPhoto;
return {
savePhoto: savePhoto,
preloadPhoto: preloadPhoto,
getUserPhotos: getUserPhotos,
getPhoto: getPhoto,
choosePhotoSize: choosePhotoSize,
wrapForHistory: wrapForHistory,
wrapForFull: wrapForFull,
openPhoto: openPhoto,
downloadPhoto: downloadPhoto
}
})
.service('AppWebPagesManager', function ($modal, $sce, $window, $rootScope, MtpApiManager, AppPhotosManager, RichTextProcessor) {
var webpages = {};
var pendingWebPages = {};
function saveWebPage (apiWebPage, messageID, mediaContext) {
if (apiWebPage.photo && apiWebPage.photo._ === 'photo') {
AppPhotosManager.savePhoto(apiWebPage.photo, mediaContext);
} else {
delete apiWebPage.photo;
}
apiWebPage.rTitle = RichTextProcessor.wrapRichText(
apiWebPage.title || apiWebPage.author,
{noLinks: true, noLinebreaks: true}
);
var contextHashtag = '';
if (apiWebPage.site_name == 'GitHub') {
var matches = apiWebPage.url.match(/(https?:\/\/github\.com\/[^\/]+\/[^\/]+)/);
if (matches) {
contextHashtag = matches[0] + '/issues/{1}';
}
}
apiWebPage.rDescription = RichTextProcessor.wrapRichText(
apiWebPage.description, {
contextSite: apiWebPage.site_name || 'external',
contextHashtag: contextHashtag
}
);
if (messageID) {
if (pendingWebPages[apiWebPage.id] === undefined) {
pendingWebPages[apiWebPage.id] = {};
}
pendingWebPages[apiWebPage.id][messageID] = true;
webpages[apiWebPage.id] = apiWebPage;
}
if (webpages[apiWebPage.id] === undefined) {
webpages[apiWebPage.id] = apiWebPage;
} else {
safeReplaceObject(webpages[apiWebPage.id], apiWebPage);
}
if (!messageID &&
pendingWebPages[apiWebPage.id] !== undefined) {
var msgs = [];
angular.forEach(pendingWebPages[apiWebPage.id], function (t, msgID) {
msgs.push(msgID);
});
$rootScope.$broadcast('webpage_updated', {
id: apiWebPage.id,
msgs: msgs
});
}
};
function openEmbed (webpageID, messageID) {
var scope = $rootScope.$new(true);
scope.webpageID = webpageID;
scope.messageID = messageID;
$modal.open({
templateUrl: templateUrl('embed_modal'),
windowTemplateUrl: templateUrl('media_modal_layout'),
controller: 'EmbedModalController',
scope: scope,
windowClass: 'photo_modal_window'
});
}
function wrapForHistory (webPageID) {
var webPage = angular.copy(webpages[webPageID]) || {_: 'webPageEmpty'};
if (webPage.photo && webPage.photo.id) {
webPage.photo = AppPhotosManager.wrapForHistory(webPage.photo.id, {website: webPage.type != 'photo' && webPage.type != 'video'});
}
return webPage;
}
function wrapForFull (webPageID) {
var webPage = wrapForHistory(webPageID);
if (!webPage.embed_url) {
return webPage;
}
var fullWidth = $(window).width() - (Config.Mobile ? 0 : 10);
var fullHeight = $($window).height() - (Config.Mobile ? 92 : 150);
if (!Config.Mobile && fullWidth > 800) {
fullWidth -= 208;
}
var full = {
width: fullWidth,
height: fullHeight,
};
if (!webPage.embed_width || !webPage.embed_height) {
full.height = full.width = Math.min(fullWidth, fullHeight);
} else {
var wh = calcImageInBox(webPage.embed_width, webPage.embed_height, fullWidth, fullHeight);
full.width = wh.w;
full.height = wh.h;
}
var embedTag = Config.Modes.chrome_packed ? 'webview' : 'iframe';
var embedType = webPage.embed_type != 'iframe' ? webPage.embed_type || 'text/html' : 'text/html';
var embedHtml = '<' + embedTag + ' src="' + encodeEntities(webPage.embed_url) + '" type="' + encodeEntities(embedType) + '" frameborder="0" border="0" webkitallowfullscreen mozallowfullscreen allowfullscreen width="' + full.width + '" height="' + full.height + '" style="width: ' + full.width + 'px; height: ' + full.height + 'px;"></' + embedTag + '>';
full.html = $sce.trustAs('html', embedHtml);
webPage.full = full;
return webPage;
}
$rootScope.$on('apiUpdate', function (e, update) {
switch (update._) {
case 'updateWebPage':
saveWebPage(update.webpage);
break;
}
});
return {
saveWebPage: saveWebPage,
openEmbed: openEmbed,
wrapForFull: wrapForFull,
wrapForHistory: wrapForHistory
}
})
.service('AppVideoManager', function ($sce, $rootScope, $modal, $window, MtpApiFileManager, AppUsersManager, FileManager, qSync) {
var videos = {},
videosForHistory = {},
windowW = $(window).width(),
windowH = $(window).height();
function saveVideo (apiVideo, context) {
if (context) {
angular.extend(apiVideo, context);
}
videos[apiVideo.id] = apiVideo;
if (apiVideo.thumb && apiVideo.thumb._ == 'photoCachedSize') {
MtpApiFileManager.saveSmallFile(apiVideo.thumb.location, apiVideo.thumb.bytes);
// Memory
apiVideo.thumb.size = apiVideo.thumb.bytes.length;
delete apiVideo.thumb.bytes;
apiVideo.thumb._ = 'photoSize';
}
};
function wrapForHistory (videoID) {
if (videosForHistory[videoID] !== undefined) {
return videosForHistory[videoID];
}
var video = angular.copy(videos[videoID]),
width = Math.min(windowW - 80, Config.Mobile ? 210 : 150),
height = Math.min(windowH - 100, Config.Mobile ? 210 : 150),
thumbPhotoSize = video.thumb,
thumb = {
placeholder: 'img/placeholders/VideoThumbConversation.gif',
width: width,
height: height
};
if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') {
if ((thumbPhotoSize.w / thumbPhotoSize.h) > (width / height)) {
thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w);
}
else {
thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h);
if (thumb.width > width) {
thumb.height = parseInt(thumb.height * width / thumb.width);
thumb.width = width;
}
}
thumb.location = thumbPhotoSize.location;
thumb.size = thumbPhotoSize.size;
}
video.thumb = thumb;
return videosForHistory[videoID] = video;
}
function wrapForFull (videoID) {
var video = wrapForHistory(videoID),
fullWidth = Math.min($(window).width() - (Config.Mobile ? 0 : 60), 542),
fullHeight = $($window).height() - (Config.Mobile ? 92 : 150),
fullPhotoSize = video,
full = {
placeholder: 'img/placeholders/VideoThumbModal.gif',
width: fullWidth,
height: fullHeight,
};
if (!video.w || !video.h) {
full.height = full.width = Math.min(fullWidth, fullHeight);
} else {
var wh = calcImageInBox(video.w, video.h, fullWidth, fullHeight);
full.width = wh.w;
full.height = wh.h;
}
video.full = full;
video.fullThumb = angular.copy(video.thumb);
video.fullThumb.width = full.width;
video.fullThumb.height = full.height;
return video;
}
function openVideo (videoID, messageID) {
var scope = $rootScope.$new(true);
scope.videoID = videoID;
scope.messageID = messageID;
return $modal.open({
templateUrl: templateUrl('video_modal'),
windowTemplateUrl: templateUrl('media_modal_layout'),
controller: 'VideoModalController',
scope: scope,
windowClass: 'video_modal_window'
});
}
function updateVideoDownloaded (videoID) {
var video = videos[videoID],
historyVideo = videosForHistory[videoID] || video || {},
inputFileLocation = {
_: 'inputVideoFileLocation',
id: videoID,
access_hash: video.access_hash
};
// historyVideo.progress = {enabled: true, percent: 10, total: video.size};
if (historyVideo.downloaded === undefined) {
MtpApiFileManager.getDownloadedFile(inputFileLocation, video.size).then(function () {
historyVideo.downloaded = true;
}, function () {
historyVideo.downloaded = false;
});
}
}
function downloadVideo (videoID, toFileEntry) {
var video = videos[videoID],
historyVideo = videosForHistory[videoID] || video || {},
mimeType = video.mime_type || 'video/ogg',
inputFileLocation = {
_: 'inputVideoFileLocation',
id: videoID,
access_hash: video.access_hash
};
if (historyVideo.downloaded && !toFileEntry) {
var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation);
if (cachedBlob) {
return qSync.when(cachedBlob);
}
}
historyVideo.progress = {enabled: !historyVideo.downloaded, percent: 1, total: video.size};
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {
mime: mimeType,
toFileEntry: toFileEntry
});
downloadPromise.then(function (blob) {
FileManager.getFileCorrectUrl(blob, mimeType).then(function (url) {
historyVideo.url = $sce.trustAsResourceUrl(url);
});
delete historyVideo.progress;
historyVideo.downloaded = true;
console.log('video save done');
}, function (e) {
console.log('video download failed', e);
historyVideo.progress.enabled = false;
}, function (progress) {
console.log('dl progress', progress);
historyVideo.progress.enabled = true;
historyVideo.progress.done = progress.done;
historyVideo.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
});
historyVideo.progress.cancel = downloadPromise.cancel;
return downloadPromise;
}
function saveVideoFile (videoID) {
var video = videos[videoID],
mimeType = video.mime_type || 'video/mp4',
fileExt = mimeType.split('.')[1] || 'mp4',
fileName = 't_video' + videoID + '.' + fileExt,
historyVideo = videosForHistory[videoID] || video || {};
FileManager.chooseSave(fileName, fileExt, mimeType).then(function (writableFileEntry) {
if (writableFileEntry) {
downloadVideo(videoID, writableFileEntry);
}
}, function () {
downloadVideo(videoID).then(function (blob) {
FileManager.download(blob, mimeType, fileName);
});
});
}
return {
saveVideo: saveVideo,
wrapForHistory: wrapForHistory,
wrapForFull: wrapForFull,
openVideo: openVideo,
updateVideoDownloaded: updateVideoDownloaded,
downloadVideo: downloadVideo,
saveVideoFile: saveVideoFile
}
})
.service('AppDocsManager', function ($sce, $rootScope, $modal, $window, $q, RichTextProcessor, MtpApiFileManager, FileManager, qSync) {
var docs = {},
docsForHistory = {},
windowW = $(window).width(),
windowH = $(window).height();
function saveDoc (apiDoc, context) {
docs[apiDoc.id] = apiDoc;
if (context) {
angular.extend(apiDoc, context);
}
if (apiDoc.thumb && apiDoc.thumb._ == 'photoCachedSize') {
MtpApiFileManager.saveSmallFile(apiDoc.thumb.location, apiDoc.thumb.bytes);
// Memory
apiDoc.thumb.size = apiDoc.thumb.bytes.length;
delete apiDoc.thumb.bytes;
apiDoc.thumb._ = 'photoSize';
}
angular.forEach(apiDoc.attributes, function (attribute) {
switch (attribute._) {
case 'documentAttributeFilename':
apiDoc.file_name = attribute.file_name;
break;
case 'documentAttributeAudio':
apiDoc.duration = attribute.duration;
apiDoc.audioTitle = attribute.title;
apiDoc.audioPerformer = attribute.performer;
break;
case 'documentAttributeVideo':
apiDoc.duration = attribute.duration;
break;
case 'documentAttributeSticker':
apiDoc.sticker = 1;
if (attribute.alt !== undefined) {
apiDoc.sticker = 2;
apiDoc.stickerEmojiRaw = attribute.alt;
apiDoc.stickerEmoji = RichTextProcessor.wrapRichText(apiDoc.stickerEmojiRaw, {noLinks: true, noLinebreaks: true});
}
if (attribute.stickerset) {
if (attribute.stickerset._ == 'inputStickerSetEmpty') {
delete attribute.stickerset;
}
else if (attribute.stickerset._ == 'inputStickerSetID') {
apiDoc.stickerSetInput = attribute.stickerset;
}
}
break;
case 'documentAttributeImageSize':
apiDoc.w = attribute.w;
apiDoc.h = attribute.h;
break;
}
});
apiDoc.file_name = apiDoc.file_name || '';
};
function getDoc (docID) {
return docs[docID] || {_: 'documentEmpty'};
}
function wrapForHistory (docID) {
if (docsForHistory[docID] !== undefined) {
return docsForHistory[docID];
}
var doc = angular.copy(docs[docID]),
isGif = doc.mime_type == 'image/gif',
isSticker = doc.mime_type.substr(0, 6) == 'image/' && doc.sticker,
thumbPhotoSize = doc.thumb,
width, height;
if (isGif) {
width = Math.min(windowW - 80, 260);
height = Math.min(windowH - 100, 260);
}
else if (isSticker) {
width = Math.min(windowW - 80, Config.Mobile ? 128 : 192);
height = Math.min(windowH - 100, Config.Mobile ? 128 : 192);
} else {
width = height = 100;
}
var thumb = {
width: width,
height: height
};
if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') {
var dim = calcImageInBox(thumbPhotoSize.w, thumbPhotoSize.h, width, height);
thumb.width = dim.w;
thumb.height = dim.h;
thumb.location = thumbPhotoSize.location;
thumb.size = thumbPhotoSize.size;
}
else if (isSticker) {
var dim = calcImageInBox(doc.w, doc.h, width, height);
thumb.width = dim.w;
thumb.height = dim.h;
}
else {
thumb = false;
}
doc.thumb = thumb;
doc.withPreview = !Config.Mobile && doc.mime_type.match(/^(image\/)/) ? 1 : 0;
if (isGif && doc.thumb) {
doc.isSpecial = 'gif';
}
else if (isSticker) {
doc.isSpecial = 'sticker';
}
else if (doc.mime_type.substr(0, 6) == 'audio/') {
doc.isSpecial = 'audio';
}
return docsForHistory[docID] = doc;
}
function updateDocDownloaded (docID) {
var doc = docs[docID],
historyDoc = docsForHistory[docID] || doc || {},
inputFileLocation = {
_: 'inputDocumentFileLocation',
id: docID,
access_hash: doc.access_hash,
file_name: doc.file_name
};
if (historyDoc.downloaded === undefined) {
MtpApiFileManager.getDownloadedFile(inputFileLocation, doc.size).then(function () {
historyDoc.downloaded = true;
}, function () {
historyDoc.downloaded = false;
});
}
}
function downloadDoc (docID, toFileEntry) {
var doc = docs[docID],
historyDoc = docsForHistory[docID] || doc || {},
inputFileLocation = {
_: 'inputDocumentFileLocation',
id: docID,
access_hash: doc.access_hash,
file_name: doc.file_name
};
if (historyDoc.downloaded && !toFileEntry) {
var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation);
if (cachedBlob) {
return qSync.when(cachedBlob);
}
}
historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size};
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
mime: doc.mime_type || 'application/octet-stream',
toFileEntry: toFileEntry
});
downloadPromise.then(function (blob) {
delete historyDoc.progress;
if (blob) {
FileManager.getFileCorrectUrl(blob, doc.mime_type).then(function (url) {
historyDoc.url = $sce.trustAsResourceUrl(url);
})
historyDoc.downloaded = true;
}
console.log('file save done');
}, function (e) {
console.log('document download failed', e);
historyDoc.progress.enabled = false;
}, function (progress) {
console.log('dl progress', progress);
historyDoc.progress.enabled = true;
historyDoc.progress.done = progress.done;
historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
});
historyDoc.progress.cancel = downloadPromise.cancel;
return downloadPromise;
}
function openDoc (docID, messageID) {
var scope = $rootScope.$new(true);
scope.docID = docID;
scope.messageID = messageID;
var modalInstance = $modal.open({
templateUrl: templateUrl('document_modal'),
windowTemplateUrl: templateUrl('media_modal_layout'),
controller: 'DocumentModalController',
scope: scope,
windowClass: 'document_modal_window'
});
}
function saveDocFile (docID) {
var doc = docs[docID],
historyDoc = docsForHistory[docID] || doc || {};
var ext = (doc.file_name.split('.', 2) || [])[1] || '';
FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) {
if (writableFileEntry) {
downloadDoc(docID, writableFileEntry);
}
}, function () {
downloadDoc(docID).then(function (blob) {
FileManager.download(blob, doc.mime_type, doc.file_name);
});
});
}
return {
saveDoc: saveDoc,
getDoc: getDoc,
wrapForHistory: wrapForHistory,
updateDocDownloaded: updateDocDownloaded,
downloadDoc: downloadDoc,
openDoc: openDoc,
saveDocFile: saveDocFile
}
})
.service('AppAudioManager', function ($sce, $rootScope, $modal, $window, MtpApiFileManager, FileManager, qSync) {
var audios = {};
var audiosForHistory = {};
function saveAudio (apiAudio) {
audios[apiAudio.id] = apiAudio;
};
function wrapForHistory (audioID) {
if (audiosForHistory[audioID] !== undefined) {
return audiosForHistory[audioID];
}
var audio = angular.copy(audios[audioID]);
return audiosForHistory[audioID] = audio;
}
function updateAudioDownloaded (audioID) {
var audio = audios[audioID],
historyAudio = audiosForHistory[audioID] || audio || {},
inputFileLocation = {
_: 'inputAudioFileLocation',
id: audioID,
access_hash: audio.access_hash
};
// historyAudio.progress = {enabled: !historyAudio.downloaded, percent: 10, total: audio.size};
if (historyAudio.downloaded === undefined) {
MtpApiFileManager.getDownloadedFile(inputFileLocation, audio.size).then(function () {
historyAudio.downloaded = true;
}, function () {
historyAudio.downloaded = false;
});
}
}
function downloadAudio (audioID, toFileEntry) {
var audio = audios[audioID],
historyAudio = audiosForHistory[audioID] || audio || {},
mimeType = audio.mime_type || 'audio/ogg',
inputFileLocation = {
_: 'inputAudioFileLocation',
id: audioID,
access_hash: audio.access_hash
};
if (historyAudio.downloaded && !toFileEntry) {
var cachedBlob = MtpApiFileManager.getCachedFile(inputFileLocation);
if (cachedBlob) {
return qSync.when(cachedBlob);
}
}
historyAudio.progress = {enabled: !historyAudio.downloaded, percent: 1, total: audio.size};
var downloadPromise = MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size, {
mime: mimeType,
toFileEntry: toFileEntry
});
downloadPromise.then(function (blob) {
FileManager.getFileCorrectUrl(blob, mimeType).then(function (url) {
historyAudio.url = $sce.trustAsResourceUrl(url);
});
delete historyAudio.progress;
historyAudio.downloaded = true;
console.log('audio save done');
}, function (e) {
console.log('audio download failed', e);
historyAudio.progress.enabled = false;
}, function (progress) {
console.log('dl progress', progress);
historyAudio.progress.enabled = true;
historyAudio.progress.done = progress.done;
historyAudio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
});
historyAudio.progress.cancel = downloadPromise.cancel;
return downloadPromise;
}
function saveAudioFile (audioID) {
var audio = audios[audioID],
mimeType = audio.mime_type || 'audio/ogg',
fileExt = mimeType.split('.')[1] || 'ogg',
fileName = 't_audio' + audioID + '.' + fileExt,
historyAudio = audiosForHistory[audioID] || audio || {};
FileManager.chooseSave(fileName, fileExt, mimeType).then(function (writableFileEntry) {
if (writableFileEntry) {
downloadAudio(audioID, writableFileEntry);
}
}, function () {
downloadAudio(audioID).then(function (blob) {
FileManager.download(blob, mimeType, fileName);
});
});
}
return {
saveAudio: saveAudio,
wrapForHistory: wrapForHistory,
updateAudioDownloaded: updateAudioDownloaded,
downloadAudio: downloadAudio,
saveAudioFile: saveAudioFile
}
})
.service('AppStickersManager', function ($q, $rootScope, $modal, _, FileManager, MtpApiManager, MtpApiFileManager, AppDocsManager, Storage) {
var currentStickers = [];
var currentStickersets = [];
var installedStickersets = {};
var stickersetItems = {};
var applied = false;
var started = false;
return {
start: start,
openStickersetLink: openStickersetLink,
openStickerset: openStickerset,
installStickerset: installStickerset,
getStickers: getStickers,
getStickerset: getStickerset,
getStickersImages: getStickersImages
};
function start () {
if (!started) {
started = true;
setTimeout(getStickers, 1000);
}
}
function processRawStickers(stickers) {
if (applied !== stickers.hash) {
applied = stickers.hash;
var i, j, len1, len2, doc, set, setItems, fullSet;
currentStickersets = [];
currentStickers = [];
len1 = stickers.sets.length;
for (i = 0; i < len1; i++) {
set = stickers.sets[i];
fullSet = stickers.fullSets[set.id];
len2 = fullSet.documents.length;
setItems = [];
for (j = 0; j < len2; j++) {
doc = fullSet.documents[j];
AppDocsManager.saveDoc(doc);
currentStickers.push(doc.id);
setItems.push(doc.id);
}
currentStickersets.push({
id: set.id,
title: set.title,
short_name: set.short_name,
installed: (set.flags & (1 << 0)) > 0,
disabled: (set.flags & (1 << 1)) > 0,
official: (set.flags & (1 << 2)) > 0,
docIDs: setItems
});
installedStickersets[set.id] = true;
}
}
return currentStickersets;
}
function getStickers (force) {
return Storage.get('all_stickers').then(function (stickers) {
var layer = Config.Schema.API.layer;
if (stickers.layer != layer) {
stickers = false;
}
if (stickers && stickers.date > tsNow(true) && !force) {
return processRawStickers(stickers);
}
return MtpApiManager.invokeApi('messages.getAllStickers', {
hash: stickers && stickers.hash || ''
}).then(function (newStickers) {
var notModified = newStickers._ == 'messages.allStickersNotModified';
if (notModified) {
newStickers = stickers;
}
newStickers.date = tsNow(true) + 3600;
newStickers.layer = layer;
delete newStickers._;
if (notModified) {
Storage.set({all_stickers: newStickers});
return processRawStickers(newStickers);
}
return getStickerSets(newStickers).then(function () {
Storage.set({all_stickers: newStickers});
return processRawStickers(newStickers);
});
});
})
}
function getStickerSets (allStickers) {
var promises = [];
var cachedSets = allStickers.fullSets || {};
allStickers.fullSets = {};
angular.forEach(allStickers.sets, function (shortSet) {
var fullSet = cachedSets[shortSet.id];
if (fullSet && fullSet.set.hash == shortSet.hash) {
allStickers.fullSets[shortSet.id] = fullSet;
} else {
var promise = MtpApiManager.invokeApi('messages.getStickerSet', {
stickerset: {
_: 'inputStickerSetID',
id: shortSet.id,
access_hash: shortSet.access_hash
}
}).then(function (fullSet) {
allStickers.fullSets[shortSet.id] = fullSet;
});
promises.push(promise);
}
});
return $q.all(promises);
}
function downloadStickerThumb (docID) {
var doc = AppDocsManager.getDoc(docID);
var thumbLocation = angular.copy(doc.thumb.location);
thumbLocation.sticker = true;
return MtpApiFileManager.downloadSmallFile(thumbLocation).then(function (blob) {
return {
id: doc.id,
src: FileManager.getUrl(blob, 'image/webp')
};
});
}
function getStickersImages () {
var promises = [];
angular.forEach(currentStickers, function (docID) {
promises.push(downloadStickerThumb (docID));
});
return $q.all(promises);
}
function getStickerset (inputStickerset) {
return MtpApiManager.invokeApi('messages.getStickerSet', {
stickerset: inputStickerset
}).then(function (result) {
for (var i = 0; i < result.documents.length; i++) {
AppDocsManager.saveDoc(result.documents[i]);
}
result.installed = installedStickersets[result.set.id] !== undefined;
return result;
});
}
function installStickerset (set, uninstall) {
var method = uninstall
? 'messages.uninstallStickerSet'
: 'messages.installStickerSet';
var inputStickerset = {
_: 'inputStickerSetID',
id: set.id,
access_hash: set.access_hash
};
return MtpApiManager.invokeApi(method, {
stickerset: inputStickerset,
disabled: false
}).then(function (result) {
if (uninstall) {
delete installedStickersets[set.id];
} else {
installedStickersets[set.id] = true;
}
getStickers(true);
});
}
function openStickersetLink (shortName) {
return openStickerset({
_: 'inputStickerSetShortName',
short_name: shortName
});
}
function openStickerset (inputStickerset) {
var scope = $rootScope.$new(true);
scope.inputStickerset = inputStickerset;
var modal = $modal.open({
templateUrl: templateUrl('stickerset_modal'),
controller: 'StickersetModalController',
scope: scope,
windowClass: 'stickerset_modal_window mobile_modal'
});
}
})
.service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) {
var curState = {};
var myID = 0;
MtpApiManager.getUserID().then(function (id) {
myID = id;
});
var syncPending = false;
var syncLoading = true;
var pendingSeqUpdates = {};
var pendingPtsUpdates = [];
function popPendingSeqUpdate () {
var nextSeq = curState.seq + 1,
pendingUpdatesData = pendingSeqUpdates[nextSeq];
if (!pendingUpdatesData) {
return false;
}
var updates = pendingUpdatesData.updates;
var i, length;
for (var i = 0, length = updates.length; i < length; i++) {
saveUpdate(updates[i]);
}
curState.seq = pendingUpdatesData.seq;
if (pendingUpdatesData.date && curState.date < pendingUpdatesData.date) {
curState.date = pendingUpdatesData.date;
}
delete pendingSeqUpdates[nextSeq];
if (!popPendingSeqUpdate() &&
syncPending &&
syncPending.seqAwaiting &&
curState.seq >= syncPending.seqAwaiting) {
if (!syncPending.ptsAwaiting) {
clearTimeout(syncPending.timeout);
syncPending = false;
} else {
delete syncPending.seqAwaiting;
}
}
return true;
}
function popPendingPtsUpdate () {
if (!pendingPtsUpdates.length) {
return false;
}
pendingPtsUpdates.sort(function (a, b) {
return a.pts - b.pts;
});
var curPts = curState.pts;
var goodPts = false;
var goodIndex = false;
var update;
for (var i = 0, length = pendingPtsUpdates.length; i < length; i++) {
update = pendingPtsUpdates[i];
curPts += update.pts_count;
if (curPts >= update.pts) {
goodPts = update.pts;
goodIndex = i;
}
}
if (!goodPts) {
return false;
}
curState.pts = goodPts;
for (i = 0; i <= goodIndex; i++) {
update = pendingPtsUpdates[i];
saveUpdate(update);
}
pendingPtsUpdates.splice(goodIndex, length - goodIndex);
if (!pendingPtsUpdates.length && syncPending) {
if (!syncPending.seqAwaiting) {
clearTimeout(syncPending.timeout);
syncPending = false;
} else {
delete syncPending.ptsAwaiting;
}
}
return true;
}
function forceGetDifference () {
if (!syncLoading) {
getDifference();
}
}
function processUpdateMessage (updateMessage) {
var processOpts = {
date: updateMessage.date,
seq: updateMessage.seq,
seqStart: updateMessage.seq_start
};
switch (updateMessage._) {
case 'updatesTooLong':
case 'new_session_created':
forceGetDifference();
break;
case 'updateShort':
processUpdate(updateMessage.update, processOpts);
break;
case 'updateShortMessage':
case 'updateShortChatMessage':
var isOut = updateMessage.flags & 2;
var fromID = updateMessage.from_id || (isOut ? myID : updateMessage.user_id);
var toID = updateMessage.chat_id
? -updateMessage.chat_id
: (isOut ? updateMessage.user_id : myID);
processUpdate({
_: 'updateNewMessage',
message: {
_: 'message',
flags: updateMessage.flags,
id: updateMessage.id,
from_id: fromID,
to_id: AppPeersManager.getOutputPeer(toID),
date: updateMessage.date,
message: updateMessage.message,
fwd_from_id: updateMessage.fwd_from_id,
fwd_date: updateMessage.fwd_date,
reply_to_msg_id: updateMessage.reply_to_msg_id,
},
pts: updateMessage.pts,
pts_count: updateMessage.pts_count
}, processOpts);
break;
case 'updatesCombined':
case 'updates':
AppUsersManager.saveApiUsers(updateMessage.users);
AppChatsManager.saveApiChats(updateMessage.chats);
angular.forEach(updateMessage.updates, function (update) {
processUpdate(update, processOpts);
});
break;
default:
console.warn(dT(), 'Unknown update message', updateMessage);
}
}
function getDifference () {
if (!syncLoading) {
syncLoading = true;
pendingSeqUpdates = {};
pendingPtsUpdates = [];
}
if (syncPending) {
clearTimeout(syncPending.timeout);
syncPending = false;
}
MtpApiManager.invokeApi('updates.getDifference', {pts: curState.pts, date: curState.date, qts: -1}).then(function (differenceResult) {
if (differenceResult._ == 'updates.differenceEmpty') {
console.log(dT(), 'apply empty diff', differenceResult.seq);
curState.date = differenceResult.date;
curState.seq = differenceResult.seq;
syncLoading = false;
$rootScope.$broadcast('stateSynchronized');
return false;
}
AppUsersManager.saveApiUsers(differenceResult.users);
AppChatsManager.saveApiChats(differenceResult.chats);
// Should be first because of updateMessageID
// console.log(dT(), 'applying', differenceResult.other_updates.length, 'other updates');
angular.forEach(differenceResult.other_updates, function(update){
saveUpdate(update);
});
// console.log(dT(), 'applying', differenceResult.new_messages.length, 'new messages');
angular.forEach(differenceResult.new_messages, function (apiMessage) {
saveUpdate({
_: 'updateNewMessage',
message: apiMessage,
pts: curState.pts,
pts_count: 0
});
});
var nextState = differenceResult.intermediate_state || differenceResult.state;
curState.seq = nextState.seq;
curState.pts = nextState.pts;
curState.date = nextState.date;
console.log(dT(), 'apply diff', curState.seq, curState.pts);
if (differenceResult._ == 'updates.differenceSlice') {
getDifference();
} else {
// console.log(dT(), 'finished get diff');
$rootScope.$broadcast('stateSynchronized');
syncLoading = false;
}
});
}
function processUpdate (update, options) {
if (syncLoading) {
return false;
}
if (update._ == 'updateNewMessage') {
var message = update.message;
if (message.from_id && !AppUsersManager.hasUser(message.from_id) ||
message.fwd_from_id && !AppUsersManager.hasUser(message.fwd_from_id) ||
message.to_id.user_id && !AppUsersManager.hasUser(message.to_id.user_id) ||
message.to_id.chat_id && !AppChatsManager.hasChat(message.to_id.chat_id)) {
console.warn(dT(), 'Short update not enough data', message);
forceGetDifference();
return false;
}
}
var popPts, popSeq;
if (update.pts) {
var newPts = curState.pts + (update.pts_count || 0);
if (newPts < update.pts) {
console.log(dT(), 'Pts hole', curState, update);
pendingPtsUpdates.push(update);
if (!syncPending) {
syncPending = {
timeout: setTimeout(function () {
getDifference();
}, 5000)
};
}
syncPending.ptsAwaiting = true;
return false;
}
if (update.pts > curState.pts) {
curState.pts = update.pts;
popPts = true;
}
}
else if (options.seq > 0) {
var seq = options.seq;
var seqStart = options.seqStart || seq;
if (seqStart != curState.seq + 1) {
if (seqStart > curState.seq) {
console.warn(dT(), 'Seq hole', curState, syncPending && syncPending.seqAwaiting);
if (pendingSeqUpdates[seqStart] === undefined) {
pendingSeqUpdates[seqStart] = {seq: seq, date: options.date, updates: []};
}
pendingSeqUpdates[seqStart].updates.push(update);
if (!syncPending) {
syncPending = {
timeout: setTimeout(function () {
getDifference();
}, 5000)
};
}
if (!syncPending.seqAwaiting ||
syncPending.seqAwaiting < seqStart) {
syncPending.seqAwaiting = seqStart;
}
return false;
}
}
if (curState.seq != seq) {
curState.seq = seq;
if (options.date && curState.date < options.date) {
curState.date = options.date;
}
popSeq = true;
}
}
saveUpdate (update);
if (popPts) {
popPendingPtsUpdate();
}
else if (popSeq) {
popPendingSeqUpdate();
}
}
function saveUpdate (update) {
$rootScope.$broadcast('apiUpdate', update);
}
function attach () {
MtpNetworkerFactory.setUpdatesProcessor(processUpdateMessage);
MtpApiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then(function (stateResult) {
curState.seq = stateResult.seq;
curState.pts = stateResult.pts;
curState.date = stateResult.date;
setTimeout(function () {
syncLoading = false;
}, 1000);
// curState.seq = 1;
// curState.pts = stateResult.pts - 5000;
// curState.date = 1;
// getDifference();
})
}
return {
processUpdateMessage: processUpdateMessage,
attach: attach
}
})
.service('RichTextProcessor', function ($sce, $sanitize) {
var emojiMap = {},
emojiData = Config.Emoji,
emojiIconSize = 18,
emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1,
emojiCode;
var emojiRegExp = "\\u0023\\u20E3|\\u00a9|\\u00ae|\\u203c|\\u2049|\\u2139|[\\u2194-\\u2199]|\\u21a9|\\u21aa|\\u231a|\\u231b|\\u23e9|[\\u23ea-\\u23ec]|\\u23f0|\\u24c2|\\u25aa|\\u25ab|\\u25b6|\\u2611|\\u2614|\\u26fd|\\u2705|\\u2709|[\\u2795-\\u2797]|\\u27a1|\\u27b0|\\u27bf|\\u2934|\\u2935|[\\u2b05-\\u2b07]|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u3030|\\u303d|\\u3297|\\u3299|[\\uE000-\\uF8FF\\u270A-\\u2764\\u2122\\u25C0\\u25FB-\\u25FE\\u2615\\u263a\\u2648-\\u2653\\u2660-\\u2668\\u267B\\u267F\\u2693\\u261d\\u26A0-\\u26FA\\u2708\\u2702\\u2601\\u260E]|[\\u2600\\u26C4\\u26BE\\u23F3\\u2764]|\\uD83D[\\uDC00-\\uDFFF]|\\uD83C[\\uDDE8-\\uDDFA\uDDEC]\\uD83C[\\uDDEA-\\uDDFA\uDDE7]|[0-9]\\u20e3|\\uD83C[\\uDC00-\\uDFFF]";
for (emojiCode in emojiData) {
emojiMap[emojiData[emojiCode][0]] = emojiCode;
}
var alphaCharsRegExp = "a-z" +
"\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u00ff" + // Latin-1
"\\u0100-\\u024f" + // Latin Extended A and B
"\\u0253\\u0254\\u0256\\u0257\\u0259\\u025b\\u0263\\u0268\\u026f\\u0272\\u0289\\u028b" + // IPA Extensions
"\\u02bb" + // Hawaiian
"\\u0300-\\u036f" + // Combining diacritics
"\\u1e00-\\u1eff" + // Latin Extended Additional (mostly for Vietnamese)
"\\u0400-\\u04ff\\u0500-\\u0527" + // Cyrillic
"\\u2de0-\\u2dff\\ua640-\\ua69f" + // Cyrillic Extended A/B
"\\u0591-\\u05bf\\u05c1-\\u05c2\\u05c4-\\u05c5\\u05c7" +
"\\u05d0-\\u05ea\\u05f0-\\u05f4" + // Hebrew
"\\ufb1d-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40-\\ufb41" +
"\\ufb43-\\ufb44\\ufb46-\\ufb4f" + // Hebrew Pres. Forms
"\\u0610-\\u061a\\u0620-\\u065f\\u066e-\\u06d3\\u06d5-\\u06dc" +
"\\u06de-\\u06e8\\u06ea-\\u06ef\\u06fa-\\u06fc\\u06ff" + // Arabic
"\\u0750-\\u077f\\u08a0\\u08a2-\\u08ac\\u08e4-\\u08fe" + // Arabic Supplement and Extended A
"\\ufb50-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb" + // Pres. Forms A
"\\ufe70-\\ufe74\\ufe76-\\ufefc" + // Pres. Forms B
"\\u200c" + // Zero-Width Non-Joiner
"\\u0e01-\\u0e3a\\u0e40-\\u0e4e" + // Thai
"\\u1100-\\u11ff\\u3130-\\u3185\\uA960-\\uA97F\\uAC00-\\uD7AF\\uD7B0-\\uD7FF" + // Hangul (Korean)
"\\u3003\\u3005\\u303b" + // Kanji/Han iteration marks
"\\uff21-\\uff3a\\uff41-\\uff5a" + // full width Alphabet
"\\uff66-\\uff9f" + // half width Katakana
"\\uffa1-\\uffdc"; // half width Hangul (Korean)
var alphaNumericRegExp = "0-9\_" + alphaCharsRegExp;
// Based on Regular Expression for URL validation by Diego Perini
var urlRegExp = "((?:https?|ftp)://|mailto:)?" +
// user:pass authentication
"(?:\\S{1,64}(?::\\S{0,64})?@)?" +
"(?:" +
// sindresorhus/ip-regexp
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}" +
"|" +
// host name
"[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "0-9\-]{0,64}" +
// domain name
"(?:\\.[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "0-9\-]{0,64}){0,10}" +
// TLD identifier
"(?:\\.(xn--[0-9a-z]{2,16}|[" + alphaCharsRegExp + "]{2,24}))" +
")" +
// port number
"(?::\\d{2,5})?" +
// resource path
"(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>\"'])?)?";
var usernameRegExp = "[a-zA-Z\\d_]{5,32}";
var botCommandRegExp = "\\/([a-zA-Z\\d_]{1,32})(?:@(" + usernameRegExp + "))?(\\b|$)"
var fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|\\s)(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i');
var emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var youtubeRegExp = /^(?:https?:\/\/)?(?:www\.)?youtu(?:|\.be|be\.com|\.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/;
var vimeoRegExp = /^(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/;
var instagramRegExp = /^https?:\/\/(?:instagr\.am\/p\/|instagram\.com\/p\/)([a-zA-Z0-9\-\_]+)/i;
var vineRegExp = /^https?:\/\/vine\.co\/v\/([a-zA-Z0-9\-\_]+)/i;
var twitterRegExp = /^https?:\/\/twitter\.com\/.+?\/status\/\d+/i;
var facebookRegExp = /^https?:\/\/(?:www\.|m\.)?facebook\.com\/(?:.+?\/posts\/\d+|(?:story\.php|permalink\.php)\?story_fbid=(\d+)(?:&substory_index=\d+)?&id=(\d+))/i;
var gplusRegExp = /^https?:\/\/plus\.google\.com\/\d+\/posts\/[a-zA-Z0-9\-\_]+/i;
var soundcloudRegExp = /^https?:\/\/(?:soundcloud\.com|snd\.sc)\/([a-zA-Z0-9%\-\_]+)\/([a-zA-Z0-9%\-\_]+)/i;
var spotifyRegExp = /(https?:\/\/(open\.spotify\.com|play\.spotify\.com|spoti\.fi)\/(.+)|spotify:(.+))/i;
var siteHashtags = {
Telegram: '#/im?q=%23{1}',
Twitter: 'https://twitter.com/hashtag/{1}',
Instagram: 'https://instagram.com/explore/tags/{1}/',
'Google Plus': 'https://plus.google.com/explore/{1}'
};
var siteMentions = {
Telegram: '#/im?p=%40{1}',
Twitter: 'https://twitter.com/{1}',
Instagram: 'https://instagram.com/{1}/',
GitHub: 'https://github.com/{1}'
};
return {
wrapRichText: wrapRichText,
wrapPlainText: wrapPlainText
};
function getEmojiSpritesheetCoords(emojiCode) {
var i, row, column, totalColumns;
for (var cat = 0; cat < Config.EmojiCategories.length; cat++) {
totalColumns = Config.EmojiCategorySpritesheetDimens[cat][1];
i = Config.EmojiCategories[cat].indexOf(emojiCode);
if (i > -1) {
row = Math.floor(i / totalColumns);
column = (i % totalColumns);
return { category: cat, row: row, column: column };
}
}
console.error('emoji not found in spritesheet', emojiCode);
return null;
}
function wrapRichText(text, options) {
if (!text || !text.length) {
return '';
}
options = options || {};
var match,
raw = text,
html = [],
url,
contextSite = options.contextSite || 'Telegram',
contextExternal = contextSite != 'Telegram',
emojiFound = false,
emojiTitle,
emojiCoords;
// var start = tsNow();
while ((match = raw.match(fullRegExp))) {
html.push(encodeEntities(raw.substr(0, match.index)));
if (match[3]) { // telegram.me links
var contextUrl = !options.noLinks && siteMentions[contextSite];
if (contextUrl) {
var attr = '';
if (options.highlightUsername &&
options.highlightUsername.toLowerCase() == match[3].toLowerCase()) {
attr = 'class="im_message_mymention"';
}
html.push(
match[1],
'<a ',
attr,
contextExternal ? ' target="_blank" ' : '',
' href="',
contextUrl.replace('{1}', encodeURIComponent(match[3])),
'">',
encodeEntities(match[2] + match[3]),
'</a>'
);
} else {
html.push(
match[1],
encodeEntities(match[2] + match[3])
);
}
}
else if (match[4]) { // URL & e-mail
if (!options.noLinks) {
if (emailRegExp.test(match[4])) {
html.push(
'<a href="',
encodeEntities('mailto:' + match[4]),
'" target="_blank">',
encodeEntities(match[4]),
'</a>'
);
} else {
var url = false,
protocol = match[5],
tld = match[6],
excluded = '';
if (tld) {
if (!protocol && (tld.substr(0, 4) === 'xn--' || Config.TLD.indexOf(tld.toLowerCase()) !== -1)) {
protocol = 'http://';
}
if (protocol) {
var balanced = checkBrackets(match[4]);
if (balanced.length !== match[4].length) {
excluded = match[4].substring(balanced.length);
match[4] = balanced;
}
url = (match[5] ? '' : protocol) + match[4];
}
var tgMeMatch;
if (tld == 'me' &&
(tgMeMatch = url.match(/^https?:\/\/telegram\.me\/(.+)/))) {
var path = tgMeMatch[1].split('/');
switch (path[0]) {
case 'joinchat':
url = 'tg://join?invite=' + path[1];
break;
case 'addstickers':
url = 'tg://addstickers?set=' + path[1];
break;
default:
var domainQuery = path[0].split('?');
url = 'tg://resolve?domain=' + domainQuery[0] + (domainQuery[1] ? '&' + domainQuery[1] : '');
}
}
} else { // IP address
url = (match[5] ? '' : 'http://') + match[4];
}
if (url) {
html.push(
'<a href="',
encodeEntities(url),
'" target="_blank">',
encodeEntities(match[4]),
'</a>',
excluded
);
if (options.extractUrlEmbed &&
!options.extractedUrlEmbed) {
options.extractedUrlEmbed = findExternalEmbed(url);
}
} else {
html.push(encodeEntities(match[0]));
}
}
} else {
html.push(encodeEntities(match[0]));
}
}
else if (match[7]) { // New line
if (!options.noLinebreaks) {
html.push('<br/>');
} else {
html.push(' ');
}
}
else if (match[8]) {
if ((emojiCode = emojiMap[match[8]]) &&
(emojiCoords = getEmojiSpritesheetCoords(emojiCode))) {
emojiTitle = encodeEntities(emojiData[emojiCode][1][0]);
emojiFound = true;
html.push(
'<span class="emoji emoji-',
emojiCoords.category,
'-',
(emojiIconSize * emojiCoords.column),
'-',
(emojiIconSize * emojiCoords.row),
'" ',
'title="',emojiTitle, '">',
':', emojiTitle, ':</span>'
);
} else {
html.push(encodeEntities(match[8]));
}
}
else if (match[10]) {
var contextUrl = !options.noLinks && siteHashtags[contextSite] || options.contextHashtag;
if (contextUrl) {
html.push(
encodeEntities(match[9]),
'<a ',
contextExternal ? ' target="_blank" ' : '',
'href="',
contextUrl.replace('{1}', encodeURIComponent(match[10].substr(1)))
,
'">',
encodeEntities(match[10]),
'</a>'
);
} else {
html.push(
encodeEntities(match[9]),
encodeEntities(match[10])
);
}
}
else if (match[12]) { // Bot commands
if (!options.noLinks &&
!options.noCommands &&
!contextExternal) {
var bot = match[13] || options.fromBot;
html.push(
encodeEntities(match[11]),
'<a href="',
encodeEntities('tg://bot_command?command=' + encodeURIComponent(match[12]) + (bot ? '&bot=' + encodeURIComponent(bot) : '')),
'">',
encodeEntities('/' + match[12] + (match[13] ? '@' + match[13] : '')),
'</a>',
encodeEntities(match[14])
);
} else {
html.push(
encodeEntities(match[0])
);
}
}
raw = raw.substr(match.index + match[0].length);
}
html.push(encodeEntities(raw));
// var timeDiff = tsNow() - start;
// if (timeDiff > 1) {
// console.log(dT(), 'wrap text', text.length, timeDiff);
// }
text = $sanitize(html.join(''));
// console.log(3, text, html);
if (emojiFound) {
text = text.replace(/\ufe0f|&#65039;|&#65533;|&#8205;/g, '', text);
text = text.replace(/<span class="emoji emoji-(\d)-(\d+)-(\d+)"(.+?)<\/span>/g,
'<span class="emoji emoji-spritesheet-$1" style="background-position: -$2px -$3px;" $4</span>');
}
return $sce.trustAs('html', text);
}
function checkBrackets(url) {
var urlLength = url.length,
urlOpenBrackets = url.split('(').length - 1,
urlCloseBrackets = url.split(')').length - 1;
while (urlCloseBrackets > urlOpenBrackets &&
url.charAt(urlLength - 1) === ')') {
url = url.substr(0, urlLength - 1);
urlCloseBrackets--;
urlLength--;
}
if (urlOpenBrackets > urlCloseBrackets) {
url = url.replace(/\)+$/, '');
}
return url;
}
function findExternalEmbed(url) {
var embedUrlMatches,
result;
if (embedUrlMatches = url.match(youtubeRegExp)) {
return ['youtube', embedUrlMatches[1]];
}
if (embedUrlMatches = url.match(vimeoRegExp)) {
return ['vimeo', embedUrlMatches[1]];
}
else if (embedUrlMatches = url.match(instagramRegExp)) {
return ['instagram', embedUrlMatches[1]];
}
else if (embedUrlMatches = url.match(vineRegExp)) {
return ['vine', embedUrlMatches[1]];
}
else if (embedUrlMatches = url.match(soundcloudRegExp)) {
var badFolders = 'explore,upload,pages,terms-of-use,mobile,jobs,imprint'.split(',');
var badSubfolders = 'sets'.split(',');
if (badFolders.indexOf(embedUrlMatches[1]) == -1 &&
badSubfolders.indexOf(embedUrlMatches[2]) == -1) {
return ['soundcloud', embedUrlMatches[0]];
}
}
else if (embedUrlMatches = url.match(spotifyRegExp)) {
return ['spotify', embedUrlMatches[3].replace('/', ':')];
}
if (!Config.Modes.chrome_packed) { // Need external JS
if (embedUrlMatches = url.match(twitterRegExp)) {
return ['twitter', embedUrlMatches[0]];
}
else if (embedUrlMatches = url.match(facebookRegExp)) {
if (embedUrlMatches[2]!= undefined){
return ['facebook', "https://www.facebook.com/"+embedUrlMatches[2]+"/posts/"+embedUrlMatches[1]];
}
return ['facebook', embedUrlMatches[0]];
}
// Sorry, GPlus widget has no `xfbml.render` like callback and is too wide.
// else if (embedUrlMatches = url.match(gplusRegExp)) {
// return ['gplus', embedUrlMatches[0]];
// }
}
return false;
}
function wrapPlainText (text, options) {
if (emojiSupported) {
return text;
}
if (!text || !text.length) {
return '';
}
options = options || {};
text = text.replace(/\ufe0f/g, '', text);
var match,
raw = text,
text = [],
emojiTitle;
while ((match = raw.match(fullRegExp))) {
text.push(raw.substr(0, match.index));
if (match[8]) {
if ((emojiCode = emojiMap[match[8]]) &&
(emojiTitle = emojiData[emojiCode][1][0])) {
text.push(':' + emojiTitle + ':');
} else {
text.push(match[0]);
}
} else {
text.push(match[0]);
}
raw = raw.substr(match.index + match[0].length);
}
text.push(raw);
return text.join('');
}
})
.service('StatusManager', function ($timeout, $rootScope, MtpApiManager, AppUsersManager, IdleManager) {
var toPromise;
var lastOnlineUpdated = 0;
var started = false;
var myID = 0;
var myOtherDeviceActive = false;
MtpApiManager.getUserID().then(function (id) {
myID = id;
});
$rootScope.$on('apiUpdate', function (e, update) {
if (update._ == 'updateUserStatus' && update.user_id == myID) {
myOtherDeviceActive = tsNow() + (update.status._ == 'userStatusOnline' ? 300000 : 0);
}
});
return {
start: start,
isOtherDeviceActive: isOtherDeviceActive
};
function start() {
if (!started) {
started = true;
$rootScope.$watch('idle.isIDLE', checkIDLE);
$rootScope.$watch('offline', checkIDLE);
}
}
function sendUpdateStatusReq(offline) {
var date = tsNow();
if (offline && !lastOnlineUpdated ||
!offline && (date - lastOnlineUpdated) < 50000 ||
$rootScope.offline) {
return;
}
lastOnlineUpdated = offline ? 0 : date;
AppUsersManager.setUserStatus(myID, offline);
return MtpApiManager.invokeApi('account.updateStatus', {
offline: offline
}, {noErrorBox: true});
}
function checkIDLE() {
toPromise && $timeout.cancel(toPromise);
if ($rootScope.idle.isIDLE) {
toPromise = $timeout(function () {
sendUpdateStatusReq(true);
}, 5000);
} else {
sendUpdateStatusReq(false);
toPromise = $timeout(checkIDLE, 60000);
}
}
function isOtherDeviceActive() {
if (!myOtherDeviceActive) {
return false;
}
if (tsNow() > myOtherDeviceActive) {
myOtherDeviceActive = false;
return false;
}
return true;
}
})
.service('NotificationsManager', function ($rootScope, $window, $interval, $q, _, MtpApiManager, AppPeersManager, IdleManager, Storage, AppRuntimeManager) {
navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate;
var notificationsMsSiteMode = false;
try {
if (window.external && window.external.msIsSiteMode()) {
notificationsMsSiteMode = true;
}
} catch (e) {};
var notificationsUiSupport = notificationsMsSiteMode ||
('Notification' in window) ||
('mozNotification' in navigator);
var notificationsShown = {};
var notificationIndex = 0;
var notificationsCount = 0;
var soundsPlayed = {};
var vibrateSupport = !!navigator.vibrate;
var nextSoundAt = false;
var prevSoundVolume = false;
var peerSettings = {};
var faviconEl = $('link[rel="icon"]:first')[0];
var langNotificationsPluralize = _.pluralize('page_title_pluralize_notifications');
var titleBackup = document.title,
titleChanged = false,
titlePromise;
var prevFavicon;
var settings = {};
$rootScope.$watch('idle.isIDLE', function (newVal) {
if (!newVal) {
notificationsClear();
}
if (!Config.Navigator.mobile) {
$interval.cancel(titlePromise);
if (!newVal) {
titleChanged = false;
document.title = titleBackup;
setFavicon();
} else {
titleBackup = document.title;
titlePromise = $interval(function () {
var time = tsNow();
if (!notificationsCount || time % 2000 > 1000) {
if (titleChanged) {
titleChanged = false;
document.title = titleBackup;
setFavicon();
}
} else {
titleChanged = true;
document.title = langNotificationsPluralize(notificationsCount);
setFavicon('favicon_unread.ico');
}
}, 1000);
}
}
});
$rootScope.$on('apiUpdate', function (e, update) {
// console.log('on apiUpdate', update);
switch (update._) {
case 'updateNotifySettings':
if (update.peer._ == 'notifyPeer') {
var peerID = AppPeersManager.getPeerID(update.peer.peer);
savePeerSettings(peerID, update.notify_settings);
}
break;
}
});
var registeredDevice = false;
if (window.navigator.mozSetMessageHandler) {
window.navigator.mozSetMessageHandler('push', function(e) {
console.log(dT(), 'received push', e);
$rootScope.$broadcast('push_received');
});
window.navigator.mozSetMessageHandler('push-register', function(e) {
console.log(dT(), 'received push', e);
registeredDevice = false;
registerDevice();
});
}
return {
start: start,
notify: notify,
cancel: notificationCancel,
clear: notificationsClear,
soundReset: notificationSoundReset,
getPeerSettings: getPeerSettings,
getPeerMuted: getPeerMuted,
savePeerSettings: savePeerSettings,
updatePeerSettings: updatePeerSettings,
updateNotifySettings: updateNotifySettings,
getNotifySettings: getNotifySettings,
getVibrateSupport: getVibrateSupport,
testSound: playSound
};
function updateNotifySettings () {
Storage.get('notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview').then(function (updSettings) {
settings.nodesktop = updSettings[0];
settings.volume = updSettings[1] === false
? 0.5
: updSettings[1];
settings.novibrate = updSettings[2];
settings.nopreview = updSettings[3];
});
}
function getNotifySettings () {
return settings;
}
function getPeerSettings (peerID) {
if (peerSettings[peerID] !== undefined) {
return peerSettings[peerID];
}
return peerSettings[peerID] = MtpApiManager.invokeApi('account.getNotifySettings', {
peer: {
_: 'inputNotifyPeer',
peer: AppPeersManager.getInputPeerByID(peerID)
}
});
}
function setFavicon (href) {
href = href || 'favicon.ico';
if (prevFavicon === href) {
return
}
var link = document.createElement('link');
link.rel = 'shortcut icon';
link.type = 'image/x-icon';
link.href = href;
faviconEl.parentNode.replaceChild(link, faviconEl);
faviconEl = link;
prevFavicon = href
}
function savePeerSettings (peerID, settings) {
// console.trace(dT(), 'peer settings', peerID, settings);
peerSettings[peerID] = $q.when(settings);
}
function updatePeerSettings (peerID, settings) {
savePeerSettings(peerID, settings);
var inputSettings = angular.copy(settings);
inputSettings._ = 'inputPeerNotifySettings';
return MtpApiManager.invokeApi('account.updateNotifySettings', {
peer: {
_: 'inputNotifyPeer',
peer: AppPeersManager.getInputPeerByID(peerID)
},
settings: inputSettings
});
}
function getPeerMuted (peerID) {
return getPeerSettings(peerID).then(function (peerNotifySettings) {
return peerNotifySettings._ == 'peerNotifySettings' &&
peerNotifySettings.mute_until * 1000 > tsNow();
});
}
function start () {
updateNotifySettings();
$rootScope.$on('settings_changed', updateNotifySettings);
registerDevice();
if (!notificationsUiSupport) {
return false;
}
if ('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') {
$($window).on('click', requestPermission);
}
try {
if ('onbeforeunload' in window) {
$($window).on('beforeunload', notificationsClear);
}
} catch (e) {}
}
function requestPermission() {
Notification.requestPermission();
$($window).off('click', requestPermission);
}
function notify (data) {
// console.log('notify', $rootScope.idle.isIDLE, notificationsUiSupport);
// FFOS Notification blob src bug workaround
if (Config.Navigator.ffos) {
data.image = 'https://raw.githubusercontent.com/zhukov/webogram/master/app/img/icons/icon60.png';
}
else if (!data.image) {
data.image = 'img/icons/icon60.png';
}
notificationsCount++;
var now = tsNow();
if (settings.volume > 0 &&
(
!data.tag ||
!soundsPlayed[data.tag] ||
now > soundsPlayed[data.tag] + 60000
)
) {
playSound(settings.volume);
soundsPlayed[data.tag] = now;
}
if (!notificationsUiSupport ||
'Notification' in window && Notification.permission !== 'granted') {
return false;
}
if (settings.nodesktop) {
if (vibrateSupport && !settings.novibrate) {
navigator.vibrate([200, 100, 200]);
return;
}
return;
}
var idx = ++notificationIndex,
key = data.key || 'k' + idx,
notification;
if ('Notification' in window) {
notification = new Notification(data.title, {
icon: data.image || '',
body: data.message || '',
tag: data.tag || ''
});
}
else if ('mozNotification' in navigator) {
notification = navigator.mozNotification.createNotification(data.title, data.message || '', data.image || '');
}
else if (notificationsMsSiteMode) {
window.external.msSiteModeClearIconOverlay();
window.external.msSiteModeSetIconOverlay('img/icons/icon16.png', data.title);
window.external.msSiteModeActivate();
notification = {
index: idx
};
}
else {
return;
}
notification.onclick = function () {
notification.close();
AppRuntimeManager.focus();
notificationsClear();
if (data.onclick) {
data.onclick();
}
};
notification.onclose = function () {
delete notificationsShown[key];
notificationsClear();
};
if (notification.show) {
notification.show();
}
notificationsShown[key] = notification;
};
function playSound (volume) {
var now = tsNow();
if (nextSoundAt && now < nextSoundAt && prevSoundVolume == volume) {
return;
}
nextSoundAt = now + 1000;
prevSoundVolume = volume;
var filename = 'img/sound_a.mp3';
var obj = $('#notify_sound').html('<audio autoplay="autoplay">' +
'<source src="' + filename + '" type="audio/mpeg" />' +
'<embed hidden="true" autostart="true" loop="false" volume="' + (volume * 100) +'" src="' + filename +'" />' +
'</audio>');
obj.find('audio')[0].volume = volume;
}
function notificationCancel (key) {
var notification = notificationsShown[key];
if (notification) {
if (notificationsCount > 0) {
notificationsCount--;
}
try {
if (notification.close) {
notification.close();
}
else if (notificationsMsSiteMode &&
notification.index == notificationIndex) {
window.external.msSiteModeClearIconOverlay();
}
} catch (e) {}
delete notificationsCount[key];
}
}
function notificationSoundReset (tag) {
delete soundsPlayed[tag];
}
function notificationsClear() {
if (notificationsMsSiteMode) {
window.external.msSiteModeClearIconOverlay();
} else {
angular.forEach(notificationsShown, function (notification) {
try {
if (notification.close) {
notification.close()
}
} catch (e) {}
});
}
notificationsShown = {};
notificationsCount = 0;
}
var registerDevicePeriod = 1000,
registerDeviceTO;
function registerDevice () {
if (registeredDevice) {
return false;
}
if (navigator.push && Config.Navigator.ffos && Config.Modes.packed) {
var req = navigator.push.register();
req.onsuccess = function(e) {
clearTimeout(registerDeviceTO);
console.log(dT(), 'Push registered', req.result);
registeredDevice = req.result;
MtpApiManager.invokeApi('account.registerDevice', {
token_type: 4,
token: registeredDevice,
device_model: navigator.userAgent || 'Unknown UserAgent',
system_version: navigator.platform || 'Unknown Platform',
app_version: Config.App.version,
app_sandbox: false,
lang_code: navigator.language || 'en'
});
}
req.onerror = function(e) {
console.error('Push register error', e, e.toString());
registerDeviceTO = setTimeout(registerDevice, registerDevicePeriod);
registerDevicePeriod = Math.min(30000, registerDevicePeriod * 1.5);
}
}
}
function unregisterDevice () {
if (!registeredDevice) {
return false;
}
MtpApiManager.invokeApi('account.unregisterDevice', {
token_type: 4,
token: registeredDevice
}).then(function () {
registeredDevice = false;
})
}
function getVibrateSupport () {
return vibrateSupport;
}
})
.service('PasswordManager', function ($timeout, $q, $rootScope, MtpApiManager, CryptoWorker, MtpSecureRandom) {
return {
check: check,
getState: getState,
requestRecovery: requestRecovery,
recover: recover,
updateSettings: updateSettings
};
function getState (options) {
return MtpApiManager.invokeApi('account.getPassword', {}, options).then(function (result) {
return result;
});
}
function updateSettings (state, settings) {
var currentHashPromise;
var newHashPromise;
var params = {
new_settings: {
_: 'account.passwordInputSettings',
flags: 0,
hint: settings.hint || ''
}
};
if (typeof settings.cur_password === 'string' &&
settings.cur_password.length > 0) {
currentHashPromise = makePasswordHash(state.current_salt, settings.cur_password);
} else {
currentHashPromise = $q.when([]);
}
if (typeof settings.new_password === 'string' &&
settings.new_password.length > 0) {
var saltRandom = new Array(8);
var newSalt = bufferConcat(state.new_salt, saltRandom);
MtpSecureRandom.nextBytes(saltRandom);
newHashPromise = makePasswordHash(newSalt, settings.new_password);
params.new_settings.new_salt = newSalt;
params.new_settings.flags |= 1;
} else {
if (typeof settings.new_password === 'string') {
params.new_settings.flags |= 1;
params.new_settings.new_salt = [];
}
newHashPromise = $q.when([]);
}
if (typeof settings.email === 'string') {
params.new_settings.flags |= 2;
params.new_settings.email = settings.email || '';
}
return $q.all([currentHashPromise, newHashPromise]).then(function (hashes) {
params.current_password_hash = hashes[0];
params.new_settings.new_password_hash = hashes[1];
return MtpApiManager.invokeApi('account.updatePasswordSettings', params);
});
}
function check (state, password, options) {
return makePasswordHash(state.current_salt, password).then(function (passwordHash) {
return MtpApiManager.invokeApi('auth.checkPassword', {
password_hash: passwordHash
}, options);
});
}
function requestRecovery (state, options) {
return MtpApiManager.invokeApi('auth.requestPasswordRecovery', {}, options);
}
function recover (code, options) {
return MtpApiManager.invokeApi('auth.recoverPassword', {
code: code
}, options);
}
function makePasswordHash (salt, password) {
var passwordUTF8 = unescape(encodeURIComponent(password));
var buffer = new ArrayBuffer(passwordUTF8.length);
var byteView = new Uint8Array(buffer);
for (var i = 0, len = passwordUTF8.length; i < len; i++) {
byteView[i] = passwordUTF8.charCodeAt(i);
}
buffer = bufferConcat(bufferConcat(salt, byteView), salt);
return CryptoWorker.sha256Hash(buffer);
}
})
.service('ErrorService', function ($rootScope, $modal, $window) {
var shownBoxes = 0;
function show (params, options) {
if (shownBoxes >= 1) {
console.log('Skip error box, too many open', shownBoxes, params, options);
return false;
}
options = options || {};
var scope = $rootScope.$new();
angular.extend(scope, params);
shownBoxes++;
var modal = $modal.open({
templateUrl: templateUrl('error_modal'),
scope: scope,
windowClass: options.windowClass || 'error_modal_window'
});
modal.result['finally'](function () {
shownBoxes--;
});
return modal;
}
function alert (title, description) {
return show ({
title: title,
description: description
});
};
function confirm (params, options) {
options = options || {};
var scope = $rootScope.$new();
angular.extend(scope, params);
var modal = $modal.open({
templateUrl: templateUrl('confirm_modal'),
scope: scope,
windowClass: options.windowClass || 'confirm_modal_window'
});
return modal.result;
};
$window.safeConfirm = function (params, callback) {
if (typeof params === 'string') {
params = {message: params};
}
confirm(params).then(function (result) {
callback(result || true)
}, function () {
callback(false)
});
};
return {
show: show,
alert: alert,
confirm: confirm
}
})
.service('PeersSelectService', function ($rootScope, $modal) {
function selectPeer (options) {
var scope = $rootScope.$new();
scope.multiSelect = false;
scope.noMessages = true;
if (options) {
angular.extend(scope, options);
}
return $modal.open({
templateUrl: templateUrl('peer_select'),
controller: 'PeerSelectController',
scope: scope,
windowClass: 'peer_select_window mobile_modal',
backdrop: 'single'
}).result;
}
function selectPeers (options) {
if (Config.Mobile) {
return selectPeer(options).then(function (peerString) {
return [peerString];
});
}
var scope = $rootScope.$new();
scope.multiSelect = true;
scope.noMessages = true;
if (options) {
angular.extend(scope, options);
}
return $modal.open({
templateUrl: templateUrl('peer_select'),
controller: 'PeerSelectController',
scope: scope,
windowClass: 'peer_select_window mobile_modal',
backdrop: 'single'
}).result;
}
return {
selectPeer: selectPeer,
selectPeers: selectPeers
}
})
.service('ContactsSelectService', function ($rootScope, $modal) {
function select (multiSelect, options) {
options = options || {};
var scope = $rootScope.$new();
scope.multiSelect = multiSelect;
angular.extend(scope, options);
if (!scope.action && multiSelect) {
scope.action = 'select';
}
return $modal.open({
templateUrl: templateUrl('contacts_modal'),
controller: 'ContactsModalController',
scope: scope,
windowClass: 'contacts_modal_window mobile_modal',
backdrop: 'single'
}).result;
}
return {
selectContacts: function (options) {
return select (true, options);
},
selectContact: function (options) {
return select (false, options);
}
}
})
.service('ChangelogNotifyService', function (Storage, $rootScope, $modal) {
function checkUpdate () {
Storage.get('last_version').then(function (lastVersion) {
if (lastVersion != Config.App.version) {
if (lastVersion) {
showChangelog(lastVersion);
}
Storage.set({last_version: Config.App.version});
}
})
}
function showChangelog (lastVersion) {
var $scope = $rootScope.$new();
$scope.lastVersion = lastVersion;
$modal.open({
controller: 'ChangelogModalController',
templateUrl: templateUrl('changelog_modal'),
scope: $scope,
windowClass: 'changelog_modal_window mobile_modal'
});
}
return {
checkUpdate: checkUpdate,
showChangelog: showChangelog
}
})
.service('HttpsMigrateService', function (ErrorService, Storage) {
var started = false;
function check () {
Storage.get('https_dismiss').then(function (ts) {
if (!ts || tsNow() > ts + 43200000) {
ErrorService.confirm({
type: 'MIGRATE_TO_HTTPS'
}).then(function () {
var popup;
try {
popup = window.open('https://web.telegram.org', '_blank');
} catch (e) {}
if (!popup) {
location.href = 'https://web.telegram.org';
}
}, function () {
Storage.set({https_dismiss: tsNow()});
});
}
});
}
function start () {
if (started ||
location.protocol != 'http:' ||
Config.Modes.http ||
Config.App.domains.indexOf(location.hostname) == -1) {
return;
}
started = true;
setTimeout(check, 120000);
}
return {
start: start,
check: check
}
})
.service('LayoutSwitchService', function (ErrorService, Storage, AppRuntimeManager, $window) {
var started = false;
var confirmShown = false;
function switchLayout(mobile) {
ConfigStorage.noPrefix();
Storage.set({
layout_selected: mobile ? 'mobile' : 'desktop',
layout_width: $(window).width()
}).then(function () {
AppRuntimeManager.reload();
});
}
function layoutCheck (e) {
if (confirmShown) {
return;
}
var width = $(window).width();
var newMobile = width < 600;
if (!width ||
!e && (Config.Navigator.mobile ? width <= 800 : newMobile)) {
return;
}
if (newMobile != Config.Mobile) {
ConfigStorage.noPrefix();
Storage.get('layout_width').then(function (confirmedWidth) {
if (width == confirmedWidth) {
return false;
}
confirmShown = true;
ErrorService.confirm({
type: newMobile ? 'SWITCH_MOBILE_VERSION' : 'SWITCH_DESKTOP_VERSION'
}).then(function () {
switchLayout(newMobile);
}, function () {
ConfigStorage.noPrefix();
Storage.set({layout_width: width});
confirmShown = false;
});
});
}
}
function start () {
if (started || Config.Navigator.mobile) {
return;
}
started = true;
layoutCheck();
$($window).on('resize', layoutCheck);
}
return {
start: start,
switchLayout: switchLayout
}
})
.service('TelegramMeWebService', function (Storage) {
var disabled = Config.Modes.test ||
Config.App.domains.indexOf(location.hostname) == -1 ||
location.protocol != 'http:' && location.protocol != 'https:' ||
location.protocol == 'https:' && location.hostname != 'web.telegram.org';
function sendAsyncRequest (canRedirect) {
if (disabled) {
return false;
}
Storage.get('tgme_sync').then(function (curValue) {
var ts = tsNow(true);
if (canRedirect &&
curValue &&
curValue.canRedirect == canRedirect &&
curValue.ts + 86400 > ts) {
return false;
}
Storage.set({tgme_sync: {canRedirect: canRedirect, ts: ts}});
var script = $('<script>').appendTo('body')
.on('load error', function() {
script.remove();
})
.attr('src', '//telegram.me/_websync_?authed=' + (canRedirect ? '1' : '0'));
});
};
return {
setAuthorized: sendAsyncRequest
};
})
.service('LocationParamsService', function ($rootScope, $routeParams, AppPeersManager, AppUsersManager, AppMessagesManager, PeersSelectService, AppStickersManager) {
var tgAddrRegExp = /^(web\+)?tg:(\/\/)?(.+)/;
function checkLocationTgAddr () {
var tgaddr = $routeParams.tgaddr;
if (tgaddr) {
try {
tgaddr = decodeURIComponent(tgaddr);
} catch (e) {};
var matches = tgaddr.match(tgAddrRegExp);
if (matches) {
handleTgProtoAddr(matches[3]);
}
}
}
function handleTgProtoAddr (url, inner) {
var matches;
if (matches = url.match(/^resolve\?domain=(.+?)(?:&(start|startgroup)=(.+))?$/)) {
AppUsersManager.resolveUsername(matches[1]).then(function (userID) {
if (matches[2] == 'startgroup') {
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(userID, chatID, matches[3]).then(function () {
$rootScope.$broadcast('history_focus', {peerString: peerString});
});
});
return true;
}
$rootScope.$broadcast('history_focus', {
peerString: AppUsersManager.getUserString(userID),
startParam: matches[3]
});
});
return true;
}
if (matches = url.match(/^join\?invite=(.+)$/)) {
AppMessagesManager.openChatInviteLink(matches[1]);
return true;
}
if (matches = url.match(/^addstickers\?set=(.+)$/)) {
AppStickersManager.openStickersetLink(matches[1]);
return true;
}
if (inner &&
(matches = url.match(/^bot_command\?command=(.+?)(?:&bot=(.+))?$/))) {
var peerID = $rootScope.selectedPeerID;
var text = '/' + matches[1];
if (peerID < 0 && matches[2]) {
text += '@' + matches[2];
}
AppMessagesManager.sendText(peerID, text);
$rootScope.$broadcast('history_focus', {
peerString: AppPeersManager.getPeerString(peerID)
});
return true;
}
return false;
}
var started = false;
function start () {
if (started) {
return;
}
started = true;
if ('registerProtocolHandler' in navigator) {
try {
navigator.registerProtocolHandler('tg', '#im?tgaddr=%s', 'Telegram Web');
} catch (e) {}
try {
navigator.registerProtocolHandler('web+tg', '#im?tgaddr=%s', 'Telegram Web');
} catch (e) {}
}
$(document).on('click', function (event) {
var target = event.target;
if (target &&
target.tagName == 'A' &&
!target.onclick &&
!target.onmousedown) {
var href = $(target).attr('href') || target.href || '';
var match = href.match(tgAddrRegExp);
if (match) {
if (handleTgProtoAddr(match[3], true)) {
return cancelEvent(event);
}
}
}
});
$rootScope.$on('$routeUpdate', checkLocationTgAddr);
checkLocationTgAddr();
};
return {
start: start
};
})