Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Dutchosintguy/f065a174a3c866b351fa657272b09aec to your computer and use it in GitHub Desktop.
Save Dutchosintguy/f065a174a3c866b351fa657272b09aec to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Telegram Script
// @author thefabledowl@gmail.com
// @version 0.3
// @description Greasemonkey script to extract users/history
// @author You
// @downloadUrl https://gist.github.com/fabledowl/4d6f84b211a2918fb9ee9556550df5b0/raw/telegramScript.user.js
// @updateUrl https://gist.github.com/fabledowl/4d6f84b211a2918fb9ee9556550df5b0/raw/telegramScript.user.js
// @include https://web.telegram.org/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
if (!window.name.includes('NG_ENABLE_DEBUG_INFO!')) {
window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
}
function waitForKeyElements (selectorTxt, actionFunction, bWaitOnce, iframeSelector) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents ()
.find (selectorTxt);
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each ( function () {
var jThis = $(this);
var alreadyFound = jThis.data ('alreadyFound') || false;
if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction (jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data ('alreadyFound', true);
}
} );
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
var runAngular = function() {
try {
if (angular.element(document.body).injector().get('$rootScope')) {
initializeScript();
return;
}
} catch(e) {
window.setTimeout(runAngular, 1);
}
};
runAngular();
// Your code here...
function initializeScript() {
var injector = angular.element(document).injector();
var MtpApiManager = injector.get('MtpApiManager');
var AppPeersManager = injector.get('AppPeersManager');
var AppChatsManager = injector.get('AppChatsManager');
var AppUsersManager = injector.get('AppUsersManager');
var rootScope = injector.get('$rootScope');
function randomInt(val) {
let max = val * 1.25;
let min = val * 0.75;
return Math.floor(Math.random() * (max - min + 1) + min);
}
function removeExtraChars(str) {
return str.replace(/\n/g, '').replace(/\r/g, '').replace(/\t/g, '');
}
function downloadFile(filename, content) {
var d = new Date();
var day = d.getUTCDate();
var month = d.getUTCMonth() + 1;
var year = d.getUTCFullYear();
filename += '_' + day + '-' + month + '-' + year;
var link = document.createElement('a');
link.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
link.setAttribute('download', filename + '.txt');
$('body').append(link);
link.click();
link.remove();
}
function to(promise) {
return promise.then(data=> {
return [null, data];
}).catch(err => [err]);
}
function getItemByID(id, elements) {
var index = elements.filter(function (element) {
return element.id === id;
}) [0];
return elements.indexOf(index);
}
async function stall(stallTime) {
await new Promise(resolve => setTimeout(resolve, stallTime));
}
function t_getHistory(id, offset, offset_id) {
return MtpApiManager.invokeApi('messages.getHistory', {
peer: AppPeersManager.getInputPeerByID(-id),
offset_id: offset_id,
add_offset: offset,
limit: 20
}, {
noErrorBox: true
});
}
function t_getChat(id) {
return MtpApiManager.invokeApi('messages.getFullChat', {
chat_id: id
}, {
noErrorBox: true
});
}
function t_getParticipants(id, offset) {
return MtpApiManager.invokeApi('channels.getParticipants', {
channel: AppChatsManager.getChannelInput(id),
filter: {
_: 'channelParticipantsRecent'
},
limit: 200,
offset: offset
}, {
noErrorBox: true
});
}
function t_getChannel(id) {
return MtpApiManager.invokeApi('channels.getFullChannel', {
channel: AppChatsManager.getChannelInput(id)
}, {
noErrorBox: true
});
}
function t_groupOrChannel(channel) {
if (channel._.includes('Chat')) {
return 'Group';
} else if (channel.pFlags.megagroup) {
return 'Group';
}
return 'Channel';
}
async function getChannelParticipants(id, userCount) {
let data, err;
let users = [];
let offset = 0;
while (true) {
updateProgressBar('users', 0, userCount, offset);
[err, data] = await to(t_getParticipants(id, offset));
if (err) {
if (err.code == 400 && err.type == 'CHANNEL_PRIVATE') {
db_deleteItem(item);
}
return false;
}
users.push(data);
offset += 200;
if (offset > userCount)
break;
await stall(randomInt(1200));
}
updateProgressBar('users', 0, userCount, userCount);
return users;
}
async function getUsers(id) {
let err, data, users;
if (AppChatsManager.isChannel(id)) {
[err, data] = await to(t_getChannel(id));
if (err) {
return false;
}
return {users: await getChannelParticipants(id, data.full_chat.participants_count), channel: data};
} else {
[err, data] = await to(t_getChat(id));
if (err) {
if (err.code == 400 && err.type == 'CHANNEL_PRIVATE') {
db_deleteItem(item);
}
return false;
}
updateProgressBar('users', 0, 100, 100);
return {users: [{users: data.users}], channel: data};
}
return null;
}
function parseUsers(users_array, channel_id) {
var users = [];
for (var i = 0; i < users_array.length; i++) {
var participants;
var data = users_array[i];
if (data.participants) {
participants = data.participants.map(function (participant) {
var user = data.users[getItemByID(participant.user_id, data.users)];
Object.assign(user, participant);
return user;
});
} else {
participants = data.users || data;
}
for (var j = 0; j < participants.length; j++) {
participants[j].dateJoined = 'N/A';
if (participants[j].date) {
participants[j].dateJoined = new Date(participants[j].date * 1000).toUTCString();
}
participants[j].full_name = '';
if (participants[j].first_name) {
participants[j].full_name += participants[j].first_name;
}
if (participants[j].last_name) {
participants[j].full_name += ' ' + participants[j].last_name;
}
participants[j].username = (participants[j].username ? participants[j].username : '');
if (participants[j]._.includes('Creator')) {
participants[j].type = 'Creator';
} else if (participants[j]._.includes('Editor')) {
participants[j].type = 'Admin';
} else {
participants[j].type = 'User';
}
if (participants[j].status) {
if (participants[j].status._ == 'userStatusOffline') {
participants[j].lastOnline = new Date(participants[j].status.was_online * 1000).toUTCString();
} else if (participants[j].status._ == 'userStatusOnline') {
participants[j].lastOnline = new Date().toUTCString();
} else if (participants[j].status._ == 'userStatusRecently') {
participants[j].lastOnline = 'Recently';
} else if (participants[j].status._ == 'userStatusLastWeek') {
participants[j].lastOnline = 'Last Week';
} else if (participants[j].status._ == 'userStatusLastMonth') {
participants[j].lastOnline = 'Last Month';
}
} else {
participants[j].lastOnline = 'Unknown';
}
participants[j].channel_id = channel_id;
participants[j].inviter_id = (participants[j].inviter_id ? participants[j].inviter_id : '');
}
Array.prototype.push.apply(users, participants);
}
return users;
}
function exportUsers(data) {
var channel = data.channel.full_chat;
let users = parseUsers(data.users, channel.id);
let type = "Channel";
if (channel._ == "chatFull" || data.channel.chats[0].pFlags.megagroup) {
type = "Group";
}
var fileContent = 'ID\tFirst Name\tLast Name\tFull Name\tUsername\tAccount Type\tLast Online\tDate Joined\tInvited By\tAccess Hash\tChannel ID\tChannel Name\tGroup/Channel\r\n';
for (var l = 0; l < users.length; l++) {
var user = users[l];
var id = user.id;
var firstName = removeExtraChars(user.first_name ? user.first_name : '')
var lastName = removeExtraChars(user.last_name ? user.last_name : '')
var fullName = removeExtraChars(user.full_name ? user.full_name : '')
var username = removeExtraChars(user.username ? user.username : '')
var channelName = removeExtraChars(data.channel.chats[0].title ? data.channel.chats[0].title : '')
var dataString = id + '\t' + firstName + '\t' + lastName + '\t' + fullName + '\t' + username + '\t' + user.type + '\t' + user.lastOnline + '\t' + user.dateJoined + '\t' + user.inviter_id + '\t' + user.access_hash + '\t' + user.channel_id + '\t' + channelName + '\t' + type;
fileContent += (l < users.length ? dataString + '\r\n' : dataString);
}
let filename = 'users_' + channel.id;
downloadFile(filename, fileContent);
}
async function manualUsers(id) {
let data = await getUsers(Math.abs(id), true);
$('div.progress-bar.users').parent().hide();
exportUsers(data);
}
async function getMessages(id, manual_date) {
let offsetID = 0;
let offset = 0;
let err, data;
var messageData = {messages: [], users: [], chats: []};
var manualLimit = Math.floor(Date.now() / 1000) - manual_date;
updateProgressBar('history', 0, manualLimit, 0);
while (true) {
[err, data] = await to(t_getHistory(id, offset, offsetID));
if (err) {
if (err.code == 400 && err.type == 'CHANNEL_PRIVATE') {
db_deleteItem(item);
}
return null;
}
// Filter out messages already seen
data.messages = data.messages.filter(function(message) {
return message.date > manual_date;
});
let messageContent = JSON.stringify(data.messages);
// Filter out users and chats no longer in the messages array
data.users = data.users.filter(function(user) {
return messageContent.includes(user.id);
});
data.chats = data.chats.filter(function(chat) {
return messageContent.includes(chat.id);
});
Array.prototype.push.apply(messageData.messages, data.messages);
Array.prototype.push.apply(messageData.users, data.users);
Array.prototype.push.apply(messageData.chats, data.chats);
if (data.messages.length > 0) {
updateProgressBar('history', 0, manualLimit, (manual_date + manualLimit) - data.messages[data.messages.length-1].date);
}
if (data.messages.length < 20) {
break;
}
offset += 20;
await stall(randomInt(1000)); // Random delay to prevent flood limit
}
updateProgressBar('history', 0, manualLimit, manualLimit);
return messageData
}
function parseMessages(messages, chats, users) {
var _messages = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
var item = {
type: message._,
id: message.id,
message: message.message || '',
date: new Date(message.date * 1000).toUTCString(),
to_id: message.to_id.channel_id || message.to_id.user_id || message.to_id.chat_id
};
if (message.to_id._.includes('Channel') || message.to_id._.includes('Chat')) {
let currChat = chats[getItemByID(message.to_id.channel_id || message.to_id.chat_id, chats)];
item.to_type = t_groupOrChannel(currChat);
item.to_name = currChat.title;
} else {
let currUser = users[getItemByID(message.to_id.user_id, users)];
item.to_type = 'User';
item.to_name = (currUser.first_name || '') + ' ' + (currUser.last_name || '');
}
if (message._ == "messageService") {
switch (message.action._) {
case "messageActionChatJoinedByLink":
item.message = message.from_id + ' joined the group via invite link';
break;
case "messageActionChatEditTitle":
item.message = 'Channel name was changed to ' + message.action.title;
break;
case "messageActionChatAddUser":
if (message.from_id == message.action.users[0]) {
item.message = 'ID ' + message.action.users[0] + ' joined the group';
} else {
item.message = 'ID ' + message.from_id + ' added ID ' + message.action.users[0] + ' to the group';
}
break;
case "messageActionChatEditPhoto":
item.message = 'Channel photo updated';
break;
case "messageActionChatDeleteUser":
item.message = 'ID ' + message.from_id + ' removed ID ' + message.action.user_id + ' from the group';
break;
case "messageActionChatDeletePhoto":
item.message = 'Channel photo deleted';
break;
default:
item.message = message.action._;
}
}
if (message.from_id) {
let user = users[getItemByID(message.from_id, users)];
item.from_type = 'User';
item.from_id = user.id;
item.from_name = (user.first_name || '') + ' ' + (user.last_name || '');
item.from_username = user.username || '';
} else {
let chat = chats[getItemByID(message.to_id.channel_id || message.to_id.chat_id, chats)];
item.from_type = t_groupOrChannel(chat);
item.from_id = chat.id;
item.from_name = chat.title || '';
item.from_username = chat.username || '';
}
if (message.fwd_from) {
if (message.fwd_from.channel_id || message.fwd_from.chat_id) {
let chat = chats[getItemByID(message.fwd_from.channel_id || message.to_id.chat_id, chats)];
item.fwd_from_id = chat.id;
item.fwd_from_date = new Date(message.fwd_from.date * 1000).toUTCString();
item.fwd_from_type = t_groupOrChannel(chat);
item.fwd_from_name = chat.title || '';
item.fwd_from_username = chat.username || '';
} else {
let user = users[getItemByID(message.fwd_from.from_id, users)];
item.fwd_from_type = 'User';
item.fwd_from_id = user.id;
item.fwd_from_date = new Date(message.fwd_from.date * 1000).toUTCString();
item.fwd_from_name = (user.first_name || '') + ' ' + (user.last_name || '');
item.fwd_from_username = user.username || '';
}
}
if (message.media) {
item.media_caption = message.media.caption || '';
switch (message.media._) {
case 'messageMediaWebPage':
item.media = 'Web Page:' + (message.media.webpage.url ? ' URL - ' + message.media.webpage.url : '') ;
break;
case 'messageMediaPhoto':
item.media = 'Photo';
break;
case 'messageMediaVideo':
item.media = 'Video';
break;
case 'messageMediaDocument':
item.media = 'Document: FILETYPE - ' + message.media.document.mime_type;
break;
case 'messageMediaAudio':
item.media = 'Audio';
break;
case 'messageMediaContact':
item.media = 'Contact: ID - ' + message.media.user_id + ' first name - ' + (message.media.first_name || '') + ' last name - ' + (message.media.last_name || '') + ' phone - ' + message.media.phone;
break;
}
}
_messages.push(item);
}
return _messages;
}
function exportMessages(data, id) {
let messages = parseMessages(data.messages, data.chats, data.users);
let channel = data.chats[0];
var fileContent =
'Message ID\t' +
'Message Type\t' +
'Message Date\t' +
'Message\t' +
'To Channel/User\t' +
'To Channel ID/User ID\t' +
'To Name\t' +
'From Channel/User\t' +
'From Channel ID/User ID\t' +
'From Name\t' +
'From Username\t' +
'Fwd From Channel/User\t' +
'Fwd From Channel ID/User ID\t' +
'Fwd From Date\t' +
'Fwd From Name\t' +
'Fwd From Username\t' +
'Media Type\t' +
'Media Caption\r\n';
for (var l = 0; l < messages.length; l++) {
var message = messages[l];
var dataString =
message.id + '\t' +
message.type + '\t' +
message.date + '\t' +
removeExtraChars(message.message ? message.message : '') + '\t' +
(message.to_type ? message.to_type : '') + '\t' +
(message.to_id ? message.to_id : '') + '\t' +
(message.to_name ? message.to_name : '') + '\t' +
(message.from_type ? message.from_type : '') + '\t' +
(message.from_id ? message.from_id : '') + '\t' +
removeExtraChars(message.from_name ? message.from_name : '') + '\t' +
removeExtraChars(message.from_username ? message.from_username : '') + '\t' +
(message.fwd_from_type ? message.fwd_from_type : '') + '\t' +
(message.fwd_from_id ? message.fwd_from_id : '') + '\t' +
(message.fwd_from_date ? message.fwd_from_date : '') + '\t' +
removeExtraChars(message.fwd_from_name ? message.fwd_from_name : '') + '\t' +
removeExtraChars(message.fwd_from_username ? message.fwd_from_username : '') + '\t' +
(message.media ? message.media : '') + '\t' +
removeExtraChars(message.media_caption ? message.media_caption : '')
fileContent += (l < messages.length ? dataString + '\r\n' : dataString);
}
let filename = 'messages_' + id;
downloadFile(filename, fileContent);
}
async function manualHistory(id) {
var dateNow = Date.now();
var now = Math.floor(dateNow / 1000);
var prevDate = now - 86400;
let data = await getMessages(id, prevDate);
$('div.progress-bar.history').parent().hide();
if (data.messages.length > 0)
exportMessages(data, id);
}
function addStyle(css) {
$('head').append($('<style>').text(css));
}
function parseDialog(jNode) {
var item = angular.element(jNode).scope().$parent;
if (item.user) {
let dialogName = $($(jNode).find('div.peer_modal_profile_name')[0]);
dialogName.after('<span style="font-weight: bold;">ID: ' + Math.abs(item.user.id) + '</span>');
} else if (item.chatFull) {
let dialogName = $($(jNode).find('div.peer_modal_profile_name')[0]);
dialogName.after('<span style="font-weight: bold;">ID: ' + Math.abs(item.chatFull.chat.id) + '</span>');
}
}
function parseHistoryItem(jNode) {
var historyMessage = angular.element(jNode).scope().historyMessage;
if (historyMessage._ == 'messageService') {
var msgAuthor = $($(jNode).find('a.im_message_author')[0]);
msgAuthor.after('<span style="margin-left: 5px; font-weight: bold;">ID: ' + Math.abs(historyMessage.fromID) + '</span>');
var invited = $($(jNode).find("[ng-switch-when='messageActionChatAddUser']")[0]);
if (invited) {
invited.after('<span style="margin-left: 5px; font-weight: bold;">ID: ' + Math.abs(historyMessage.action.user_id) + '</span>');
}
var removed = $($(jNode).find("[ng-switch-when='messageActionChatDeleteUser']")[0]);
if (removed) {
removed.after('<span style="margin-left: 5px; font-weight: bold;">ID: ' + Math.abs(historyMessage.action.user_id) + '</span>');
}
} else {
var authorSpan = $($(jNode).find('span.im_message_author_wrap')[0]);
authorSpan.append('<span style="margin-left: 5px; font-weight: bold;">ID: ' + Math.abs(historyMessage.fromID) + '</span>');
if (historyMessage.fwdFromID) {
var forwardLink = $($(jNode).find('a.im_message_fwd_author')[0]);
forwardLink.parent().append('<span style="margin-left: 5px; font-weight: bold;">ID: ' + Math.abs(historyMessage.fwdFromID) + '</span>');
}
}
}
function updateUI(id) {
var curDialog = $('div.im_page_wrap').scope().historyPeer.data;
if (!curDialog)
return;
var dialog_info = $('div.dialog-info');
var tools_info = $('div.tools-info');
dialog_info.empty();
let word;
if (id < 0) {
$('button.manual_history').css('display', 'block');
$('button.manual_history').attr('id', -id);
if (curDialog._ == "chat" || curDialog.pFlags.megagroup) {
word = 'Group';
$('button.dialog-info').text('Group Info');
} else {
word = 'Channel';
$('button.dialog-info').text('Channel Info');
$('button.manual_users').hide();
}
if (curDialog.pFlags.megagroup || curDialog.pFlags.creator || curDialog.pFlags.admin || curDialog._ == "chat") {
$('button.manual_users').attr('id', -id);
$('button.manual_users').css('display', 'block');
}
dialog_info.append('<p>ID: ' + Math.abs(id) + '</p>');
dialog_info.append('<p>' + word + ' Name: ' + curDialog.title + '</p>');
if (curDialog.username)
dialog_info.append('<p>Username: ' + curDialog.username + '</p>');
} else if (id > 0) {
$('button.manual_users').hide();
$('div.progress-bar.users').parent().hide();
$('button.manual_history').css('display', 'block');
$('button.manual_history').attr('id', -id);
$('button.dialog-info').text('User Info');
dialog_info.append('<p>ID: ' + id + '</p>');
dialog_info.append('<p>First Name: ' + (curDialog.first_name || '') + '</p>');
dialog_info.append('<p>Last Name: ' + (curDialog.last_name || '') + '</p>');
if (curDialog.status) {
if (curDialog.status._ == 'userStatusOffline') {
lastOnline = new Date(curDialog.status.was_online * 1000).toUTCString();
} else if (curDialog.status._ == 'userStatusOnline') {
lastOnline = new Date().toUTCString();
} else if (curDialog.status._ == 'userStatusRecently') {
lastOnline = 'Recently';
} else if (curDialog.status._ == 'userStatusLastWeek') {
lastOnline = 'Last Week';
} else if (curDialog.status._ == 'userStatusLastMonth') {
lastOnline = 'Last Month';
}
} else {
lastOnline = 'Unknown';
}
dialog_info.append('<p>Last Online: ' + lastOnline + '</p>');
} else {
$('button.manual_users').hide();
$('button.manual_history').hide();
dialog_info.append('<p>Select Channel/User</p>');
}
}
function updateProgressBar(bar, min, max, now) {
var val_now = Math.round(100 * now / max);
$('div.' + bar).attr('aria-valuenow', val_now);
$('div.' + bar).attr('aria-valuemin', 0);
$('div.' + bar).attr('aria-valuemax', 100);
$('div.' + bar).css('width', val_now + '%');
$('div.' + bar).text(val_now + '%');
}
function createUI() {
addStyle('button.accordion {background-color: rgb(86, 130, 163); cursor: default; text-shadow:rgba(163, 163, 163, 0.5) 0px 1px 3px; color: #fff; font-weight: bold; width: 100%; border: none; padding: 14px; text-align: left; outline: none; transition: 0.4s;}' +
'div.content-panel {background-color: white; overflow:hidden; transition: max-height 0.2s ease-out; padding: 5px;}' +
'div.content-panel p {margin: 0px; margin-left: 5px; font-weight: bold;}' +
'div.content-panel .tools {display:none; margin-bottom: 5px; margin-left: 5px; width: 100px;}' +
'div.content-panel .tools:first-child {display:none; margin-top: 5px;}' +
'div.progress {display: none; margin-bottom: 5px; margin-left: 5px; margin-right: 5px;}' +
'.custompanel-heading {background-color: rgb(86, 130, 163); height: 48px; color: #fff; font-size: 14px; padding-left: 20px; padding-top: 14px;}' +
'.custompanel-body {background-color: #fff; height: 100%; color: #fff; font-size: 14px; padding-left: 20px; padding-top: 14px; margin-bottom: 20px;}'
);
$('body').append('<div class="custompanel" style="position:fixed; left: 0px; top:0px; z-index:1; width:250px;">' +
'<button class="accordion dialog-info">Info</button>' +
'<div class="content-panel dialog-info"><p>Select a Group or Channel</p></div>' +
'<button class="accordion">Tools</button>' +
'<div class="content-panel tools-info">' +
'<button class="btn tools manual_history">Get History</button>' +
'<div class="progress"><div class="progress-bar progress-bar-striped active history" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div></div>' +
'<button class="btn tools manual_users">Get Users</button>' +
'<div class="progress"><div class="progress-bar progress-bar-striped active users" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div></div>' +
'</div>' +
'</div>'
);
$('button.manual_users').click(async function() {
$(this).prop('disabled', true);
updateProgressBar('users', 0, 100, 0);
$('div.progress-bar.users').parent().show();
await manualUsers($(this).attr('id'));
$(this).prop('disabled', false);
});
$('button.manual_history').click(async function() {
$(this).prop('disabled', true);
updateProgressBar('history', 0, 100, 0);
$('div.progress-bar.history').parent().show();
await manualHistory($(this).attr('id'));
$(this).prop('disabled', false);
});
}
createUI();
rootScope.$watch("selectedPeerID", function (newValue, oldValue) {
updateUI(newValue);
});
waitForKeyElements('div.im_history_message_wrap', parseHistoryItem);
waitForKeyElements('div.modal-dialog', parseDialog);
updateUI(rootScope.selectedPeerID);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment