Browse Source

Basic bot support

master
Igor Zhukov 10 years ago
parent
commit
b902f62ec1
  1. 17
      app/js/controllers.js
  2. 4
      app/js/directives.js
  3. 17
      app/js/filters.js
  4. 7
      app/js/locales/en-us.json
  5. 255
      app/js/services.js
  6. 16
      app/less/app.less
  7. 2
      app/partials/desktop/chat_modal.html
  8. 8
      app/partials/desktop/message.html
  9. 7
      app/partials/desktop/user_modal.html

17
app/js/controllers.js

@ -2502,7 +2502,7 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2502,7 +2502,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
})
.controller('UserModalController', function ($scope, $location, $rootScope, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) {
.controller('UserModalController', function ($scope, $location, $rootScope, AppProfileManager, $modal, AppUsersManager, MtpApiManager, NotificationsManager, AppPhotosManager, AppMessagesManager, AppPeersManager, PeersSelectService, ErrorService) {
var peerString = AppUsersManager.getUserString($scope.userID);
@ -2512,19 +2512,10 @@ angular.module('myApp.controllers', ['myApp.i18n']) @@ -2512,19 +2512,10 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.settings = {notifications: true};
AppUsersManager.getUserFull($scope.userID).then(function (userFullResult) {
if ($scope.override && $scope.override.phone_number) {
userFullResult.user.phone = $scope.override.phone_number;
if ($scope.override.first_name || $scope.override.last_name) {
userFullResult.user.first_name = $scope.override.first_name;
userFullResult.user.last_name = $scope.override.last_name;
}
AppUsersManager.saveApiUser(userFullResult.user);
}
AppPhotosManager.savePhoto(userFullResult.profile_photo);
$scope.blocked = userFullResult.blocked;
AppProfileManager.getProfile($scope.userID, $scope.override).then(function (userFull) {
$scope.blocked = userFull.blocked;
$scope.bot_info = userFull.bot_info;
NotificationsManager.savePeerSettings($scope.userID, userFullResult.notify_settings);
NotificationsManager.getPeerMuted($scope.userID).then(function (muted) {
$scope.settings.notifications = !muted;

4
app/js/directives.js

@ -2413,8 +2413,8 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -2413,8 +2413,8 @@ angular.module('myApp.directives', ['myApp.filters'])
update = function () {
var user = AppUsersManager.getUser(userID);
element
.html(statusFilter(user))
.toggleClass('status_online', user.status && user.status._ == 'userStatusOnline');
.html(statusFilter(user, attrs.botChatPrivacy))
.toggleClass('status_online', user.status && user.status._ == 'userStatusOnline' || false);
};
$scope.$watch(attrs.myUserStatus, function (newUserID) {

17
app/js/filters.js

@ -31,8 +31,11 @@ angular.module('myApp.filters', ['myApp.i18n']) @@ -31,8 +31,11 @@ angular.module('myApp.filters', ['myApp.i18n'])
.filter('userStatus', function($filter, _) {
var relativeTimeFilter = $filter('relativeTime');
return function (user) {
var statusType = user && user.status && user.status._ || 'userStatusEmpty';
return function (user, botChatPrivacy) {
var statusType = user && user.status && user.status._;
if (!statusType) {
statusType = user.pFlags.bot ? 'userStatusBot' : 'userStatusEmpty';
}
switch (statusType) {
case 'userStatusOnline':
return _('user_status_online');
@ -49,6 +52,16 @@ angular.module('myApp.filters', ['myApp.i18n']) @@ -49,6 +52,16 @@ angular.module('myApp.filters', ['myApp.i18n'])
case 'userStatusLastMonth':
return _('user_status_last_month');
case 'userStatusBot':
if (botChatPrivacy) {
if (user.pFlags.botNoPrivacy) {
return _('user_status_bot_noprivacy');
} else {
return _('user_status_bot_privacy');
}
}
return _('user_status_bot');
case 'userStatusEmpty':
default:
return _('user_status_long_ago');

7
app/js/locales/en-us.json

@ -117,6 +117,7 @@ @@ -117,6 +117,7 @@
"user_modal_delete_chat": "Delete chat",
"user_modal_info": "Info",
"user_modal_phone": "Phone",
"user_modal_about": "About",
"user_modal_username": "Username",
"user_modal_settings": "Settings",
"user_modal_notifications": "Notifications",
@ -144,6 +145,9 @@ @@ -144,6 +145,9 @@
"user_status_last_week": "last seen within a week",
"user_status_last_month": "last seen within a month",
"user_status_long_ago": "last seen a long time ago",
"user_status_bot": "bot",
"user_status_bot_noprivacy": "has access to messages",
"user_status_bot_privacy": "has no access to messages",
"chat_title_deleted": "DELETED",
"format_size_progress_mulitple": "{done} of {total} {parts}",
"format_size_progress": "{done} of {total}",
@ -274,7 +278,8 @@ @@ -274,7 +278,8 @@
"message_service_kicked_user": "removed {user}",
"message_service_left_group": "left group",
"message_service_joined_by_link": "joined group via invite link",
"message_service_unsupported_action": "Unsupported action {action}",
"message_service_unsupported_action": "unsupported action {action}",
"message_service_bot_intro_header": "What can this bot do?",
"error_modal_warning_title": "Warning",
"error_modal_bad_request_title": "Error",

255
app/js/services.js

@ -142,8 +142,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -142,8 +142,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
var lastWord = nameWords.pop();
apiUser.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1));
apiUser.sortStatus = getUserStatusForSort(apiUser.status);
if (apiUser.pFlags.bot) {
apiUser.sortStatus = -1;
} else {
apiUser.sortStatus = getUserStatusForSort(apiUser.status);
}
var result = users[userID];
if (result === undefined) {
@ -185,19 +188,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -185,19 +188,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
return users[id] || {id: id, deleted: true, num: 1};
}
function getUserFull (id) {
return MtpApiManager.invokeApi('users.getFullUser', {
id: getUserInput(id)
}).then(function (userFullResult) {
saveApiUser(userFullResult.user, true);
return userFullResult;
});
}
function getSelf() {
return getUser(myID);
}
function isBot(id) {
return users[id] && users[id].pFlags.bot;
}
function hasUser(id) {
return angular.isObject(users[id]);
}
@ -254,6 +252,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -254,6 +252,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
function forceUserOnline (id) {
if (isBot(id)) {
return;
}
var user = getUser(id);
if (user &&
user.status &&
@ -394,6 +395,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -394,6 +395,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
};
function setUserStatus (userID, offline) {
if (isBot(userID)) {
return;
}
var user = users[userID];
if (user) {
var status = offline ? {
@ -460,7 +464,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -460,7 +464,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
getUserString: getUserString,
getUserSearchText: getUserSearchText,
hasUser: hasUser,
getUserFull: getUserFull,
isBot: isBot,
importContact: importContact,
importContacts: importContacts,
deleteContacts: deleteContacts,
@ -833,6 +837,59 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -833,6 +837,59 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
})
.service('AppProfileManager', function (AppBotsManager, AppUsersManager, 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] = {
version: botInfo.version,
shareText: botInfo.share_text,
description: botInfo.description,
rAbout: RichTextProcessor.wrapRichText(botInfo.share_text, {noLinks: true, 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);
NotificationsManager.savePeerSettings(id, userFull.notify_settings);
userFull.bot_info = saveBotInfo(userFull.bot_info);
return userFull;
});
}
return {
getProfile: getProfile
}
})
.service('AppBotsManager', function (AppUsersManager, AppChatsManager) {
return {
@ -863,7 +920,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -863,7 +920,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
};
})
.service('AppMessagesManager', function ($q, $rootScope, $location, $filter, $timeout, $sce, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, AppWebPagesManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, PeersSelectService, Storage, FileManager, TelegramMeWebService, ErrorService, StatusManager, _) {
.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 = {};
@ -1043,7 +1100,39 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1043,7 +1100,39 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
AppChatsManager.saveApiChats(historyResult.chats);
saveMessages(historyResult.messages);
return historyResult;
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;
});
});
}
@ -1080,6 +1169,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1080,6 +1169,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
});
};
function wrapHistoryResult (peerID, result) {
return $q.when(result);
}
function getHistory (inputPeer, maxID, limit, backLimit, prerendered) {
var peerID = AppPeersManager.getPeerID(inputPeer),
historyStorage = historiesStorage[peerID],
@ -1136,7 +1229,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1136,7 +1229,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
if (!maxID && historyStorage.pending.length) {
history = historyStorage.pending.slice().concat(history);
}
return $q.when({
return wrapHistoryResult(peerID, {
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
@ -1166,12 +1259,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1166,12 +1259,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
history = historyStorage.pending.slice().concat(history);
}
return {
return wrapHistoryResult(peerID, {
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
unreadSkip: unreadSkip
};
});
})
}
@ -1190,12 +1283,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1190,12 +1283,12 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
history = historyStorage.pending.slice().concat(history);
}
return {
return wrapHistoryResult(peerID, {
count: historyStorage.count,
history: history,
unreadOffset: unreadOffset,
unreadSkip: unreadSkip
};
});
});
}
@ -1523,7 +1616,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1523,7 +1616,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
MtpApiManager.getUserID().then(function (fromID) {
if (peerID != fromID) {
flags |= 3;
flags |= 2;
if (!AppUsersManager.isBot(peerID)) {
flags |= 1;
}
}
if (replyToMsgID) {
flags |= 8;
@ -1651,7 +1747,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1651,7 +1747,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
MtpApiManager.getUserID().then(function (fromID) {
if (peerID != fromID) {
flags |= 3;
flags |= 2;
if (!AppUsersManager.isBot(peerID)) {
flags |= 1;
}
}
if (replyToMsgID) {
flags |= 8;
@ -1823,12 +1922,20 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -1823,12 +1922,20 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
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: peerID == fromID ? 0 : 3,
flags: flags,
date: tsNow(true) + serverTimeOffset,
message: '',
media: media,
@ -2144,6 +2251,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2144,6 +2251,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
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);
break;
}
}
@ -4223,13 +4334,13 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4223,13 +4334,13 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1,
emojiCode;
var emojiRegex = '\\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]';
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 regexAlphaChars = "a-z" +
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
@ -4255,41 +4366,44 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4255,41 +4366,44 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
"\\uff66-\\uff9f" + // half width Katakana
"\\uffa1-\\uffdc"; // half width Hangul (Korean)
var regexAlphaNumericChars = "0-9\_" + regexAlphaChars;
var alphaNumericRegExp = "0-9\_" + alphaCharsRegExp;
// Based on Regular Expression for URL validation by Diego Perini
var urlRegex = "((?:https?|ftp)://|mailto:)?" +
var urlRegExp = "((?:https?|ftp)://|mailto:)?" +
// user:pass authentication
"(?:\\S{1,64}(?::\\S{0,64})?@)?" +
"(?:" +
// sindresorhus/ip-regex
// 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
"[" + regexAlphaChars + "0-9][" + regexAlphaChars + "0-9\-]{0,64}" +
"[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "0-9\-]{0,64}" +
// domain name
"(?:\\.[" + regexAlphaChars + "0-9][" + regexAlphaChars + "0-9\-]{0,64}){0,10}" +
"(?:\\.[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "0-9\-]{0,64}){0,10}" +
// TLD identifier
"(?:\\.(xn--[0-9a-z]{2,16}|[" + regexAlphaChars + "]{2,24}))" +
"(?:\\.(xn--[0-9a-z]{2,16}|[" + alphaCharsRegExp + "]{2,24}))" +
")" +
// port number
"(?::\\d{2,5})?" +
// resource path
"(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>\"'])?)?";
var regExp = new RegExp('(^|\\s)(@)([a-zA-Z\\d_]{5,32})|(' + urlRegex + ')|(\\n)|(' + emojiRegex + ')|(^|\\s)(#[' + regexAlphaNumericChars + ']{2,64})', 'i');
var usernameRegExp = "[a-zA-Z\\d_]{5,32}";
var botCommandRegExp = "\\/([a-zA-Z\\d_]{1,32})(?:@(" + usernameRegExp + "))?(\\s|$)"
var fullRegExp = new RegExp('(^|\\s)(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|\\s)(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i');
var emailRegex = /^(([^<>()[\]\\.,;:\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 youtubeRegex = /^(?:https?:\/\/)?(?:www\.)?youtu(?:|\.be|be\.com|\.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/;
var vimeoRegex = /^(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/;
var instagramRegex = /^https?:\/\/(?:instagr\.am\/p\/|instagram\.com\/p\/)([a-zA-Z0-9\-\_]+)/i;
var vineRegex = /^https?:\/\/vine\.co\/v\/([a-zA-Z0-9\-\_]+)/i;
var twitterRegex = /^https?:\/\/twitter\.com\/.+?\/status\/\d+/i;
var facebookRegex = /^https?:\/\/(?:www\.|m\.)?facebook\.com\/(?:.+?\/posts\/\d+|(?:story\.php|permalink\.php)\?story_fbid=(\d+)(?:&substory_index=\d+)?&id=(\d+))/i;
var gplusRegex = /^https?:\/\/plus\.google\.com\/\d+\/posts\/[a-zA-Z0-9\-\_]+/i;
var soundcloudRegex = /^https?:\/\/(?:soundcloud\.com|snd\.sc)\/([a-zA-Z0-9%\-\_]+)\/([a-zA-Z0-9%\-\_]+)/i;
var spotifyRegex = /(https?:\/\/(open\.spotify\.com|play\.spotify\.com|spoti\.fi)\/(.+)|spotify:(.+))/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}',
@ -4344,7 +4458,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4344,7 +4458,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
// var start = tsNow();
while ((match = raw.match(regExp))) {
while ((match = raw.match(fullRegExp))) {
html.push(encodeEntities(raw.substr(0, match.index)));
if (match[3]) { // telegram.me links
@ -4375,7 +4489,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4375,7 +4489,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}
else if (match[4]) { // URL & e-mail
if (!options.noLinks) {
if (emailRegex.test(match[4])) {
if (emailRegExp.test(match[4])) {
html.push(
'<a href="',
encodeEntities('mailto:' + match[4]),
@ -4495,6 +4609,23 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4495,6 +4609,23 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
);
}
}
else if (match[12]) { // Bot commands
if (!options.noLinks || contextExternal) {
html.push(
encodeEntities(match[11]),
'<a href="',
encodeEntities('tg://bot_command?command=' + encodeURIComponent(match[12]) + (match[13] ? '&bot=' + encodeURIComponent(match[13]) : '')),
'">',
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);
}
@ -4539,19 +4670,19 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4539,19 +4670,19 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
var embedUrlMatches,
result;
if (embedUrlMatches = url.match(youtubeRegex)) {
if (embedUrlMatches = url.match(youtubeRegExp)) {
return ['youtube', embedUrlMatches[1]];
}
if (embedUrlMatches = url.match(vimeoRegex)) {
if (embedUrlMatches = url.match(vimeoRegExp)) {
return ['vimeo', embedUrlMatches[1]];
}
else if (embedUrlMatches = url.match(instagramRegex)) {
else if (embedUrlMatches = url.match(instagramRegExp)) {
return ['instagram', embedUrlMatches[1]];
}
else if (embedUrlMatches = url.match(vineRegex)) {
else if (embedUrlMatches = url.match(vineRegExp)) {
return ['vine', embedUrlMatches[1]];
}
else if (embedUrlMatches = url.match(soundcloudRegex)) {
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 &&
@ -4559,22 +4690,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4559,22 +4690,22 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
return ['soundcloud', embedUrlMatches[0]];
}
}
else if (embedUrlMatches = url.match(spotifyRegex)) {
else if (embedUrlMatches = url.match(spotifyRegExp)) {
return ['spotify', embedUrlMatches[3].replace('/', ':')];
}
if (!Config.Modes.chrome_packed) { // Need external JS
if (embedUrlMatches = url.match(twitterRegex)) {
if (embedUrlMatches = url.match(twitterRegExp)) {
return ['twitter', embedUrlMatches[0]];
}
else if (embedUrlMatches = url.match(facebookRegex)) {
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(gplusRegex)) {
// else if (embedUrlMatches = url.match(gplusRegExp)) {
// return ['gplus', embedUrlMatches[0]];
// }
}
@ -4599,7 +4730,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -4599,7 +4730,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
text = [],
emojiTitle;
while ((match = raw.match(regExp))) {
while ((match = raw.match(fullRegExp))) {
text.push(raw.substr(0, match.index));
if (match[8]) {
@ -5501,7 +5632,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -5501,7 +5632,7 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
.service('LocationParamsService', function ($rootScope, $routeParams, AppUsersManager, AppMessagesManager, AppStickersManager) {
var tgAddrRegEx = /^(web\+)?tg:(\/\/)?(.+)/;
var tgAddrRegExp = /^(web\+)?tg:(\/\/)?(.+)/;
function checkLocationTgAddr () {
var tgaddr = $routeParams.tgaddr;
@ -5509,14 +5640,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -5509,14 +5640,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
try {
tgaddr = decodeURIComponent(tgaddr);
} catch (e) {};
var matches = tgaddr.match(tgAddrRegEx);
var matches = tgaddr.match(tgAddrRegExp);
if (matches) {
handleTgProtoAddr(matches[3]);
}
}
}
function handleTgProtoAddr (url) {
function handleTgProtoAddr (url, inner) {
var matches;
if (matches = url.match(/^resolve\?domain=(.+)$/)) {
@ -5538,6 +5669,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -5538,6 +5669,14 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
return true;
}
if (inner &&
(matches = url.match(/^bot_command\?command=(.+?)(?:&bot=(.+))?/))) {
$rootScope.$broadcast('bot_command', {
command: matches[1],
bot: matches[2] || ''
});
}
return false;
}
@ -5564,9 +5703,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -5564,9 +5703,9 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
!target.onclick &&
!target.onmousedown) {
var href = $(target).attr('href') || target.href || '';
var match = href.match(tgAddrRegEx);
var match = href.match(tgAddrRegExp);
if (match) {
if (handleTgProtoAddr(match[3])) {
if (handleTgProtoAddr(match[3], true)) {
return cancelEvent(event);
}
}

16
app/less/app.less

@ -1912,6 +1912,22 @@ img.im_message_document_thumb { @@ -1912,6 +1912,22 @@ img.im_message_document_thumb {
.im_service_message_wrap {
text-align: center;
}
.im_bot_intro_message_wrap {
max-width: 300px;
padding: 4px 10px;
margin: 10px auto;
color: #000;
line-height: 1.4;
text-align: left;
font-size: 13px;
}
.im_bot_intro_message_header {
font-weight: bold;
text-align: center;
}
.im_bot_intro_message {
margin-top: 10px;
}
.im_service_message {
display: inline-block;
min-width: 10px;

2
app/partials/desktop/chat_modal.html

@ -100,7 +100,7 @@ @@ -100,7 +100,7 @@
<div class="md_modal_list_peer_name">
<a class="md_modal_list_peer_name" my-user-link="participant.user_id"></a>
</div>
<div class="md_modal_list_peer_description" my-user-status="::participant.user_id"></div>
<div class="md_modal_list_peer_description" my-user-status="::participant.user_id" bot-chat-privacy="true"></div>
</div>
</div>

8
app/partials/desktop/message.html

@ -3,8 +3,12 @@ @@ -3,8 +3,12 @@
<div class="im_message_wrap clearfix" ng-switch="::historyMessage._ == 'messageService'">
<div class="im_service_message_wrap" ng-switch-when="true">
<div class="im_service_message">
<div class="im_service_message_wrap" ng-switch-when="true" ng-switch="historyMessage.action._ == 'messageActionBotIntro'">
<div ng-switch-when="true" class="im_bot_intro_message_wrap">
<div class="im_bot_intro_message_header" my-i18n="message_service_bot_intro_header"></div>
<div class="im_bot_intro_message" ng-bind-html="::historyMessage.action.rDescription"></div>
</div>
<div ng-switch-default class="im_service_message">
<a class="im_message_author" my-user-link="historyMessage.from_id" short="!historyMessage.to_id.chat_id" color="historyMessage.to_id.chat_id > 0" no-watch="true"></a>
<span class="im_message_service" my-service-message></span>
</div>

7
app/partials/desktop/user_modal.html

@ -36,6 +36,13 @@ @@ -36,6 +36,13 @@
<div class="md_modal_iconed_section_wrap md_modal_iconed_section_number" ng-if="user.phone || user.username">
<i class="md_modal_section_icon md_modal_section_icon_phone"></i>
<div class="md_modal_section_param_wrap" ng-if="user.pFlags.bot &amp;&amp; bot_info.rAbout">
<div class="md_modal_section_param_value">
<span ng-bind="bot_info.rAbout"></span>
</div>
<div class="md_modal_section_param_name" my-i18n="user_modal_about"></div>
</div>
<div class="md_modal_section_param_wrap" ng-if="user.phone">
<div class="md_modal_section_param_value" ng-bind="user.phone | phoneNumber"></div>
<div class="md_modal_section_param_name" my-i18n="user_modal_phone"></div>

Loading…
Cancel
Save