2014-01-30 22:42:19 +01:00
|
|
|
var people = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2016-12-15 22:18:59 +01:00
|
|
|
var people_dict;
|
|
|
|
var people_by_name_dict;
|
|
|
|
var people_by_user_id_dict;
|
2017-10-26 18:26:28 +02:00
|
|
|
var active_user_dict;
|
2016-12-15 22:18:59 +01:00
|
|
|
var cross_realm_dict;
|
|
|
|
var pm_recipient_count_dict;
|
2017-01-19 23:04:52 +01:00
|
|
|
var my_user_id;
|
2016-12-15 22:18:59 +01:00
|
|
|
|
|
|
|
// We have an init() function so that our automated tests
|
|
|
|
// can easily clear data.
|
|
|
|
exports.init = function () {
|
|
|
|
// The following three Dicts point to the same objects
|
2017-01-31 20:44:51 +01:00
|
|
|
// (all people we've seen), but people_dict can have duplicate
|
|
|
|
// keys related to email changes. We want to deprecate
|
|
|
|
// people_dict over time and always do lookups by user_id.
|
2016-12-15 22:18:59 +01:00
|
|
|
people_dict = new Dict({fold_case: true});
|
|
|
|
people_by_name_dict = new Dict({fold_case: true});
|
|
|
|
people_by_user_id_dict = new Dict();
|
2017-01-31 20:44:51 +01:00
|
|
|
|
2017-10-26 18:26:28 +02:00
|
|
|
// The next dictionary includes all active users (human/user)
|
|
|
|
// in our realm, but it excludes non-active users and
|
|
|
|
// cross-realm bots.
|
|
|
|
active_user_dict = new Dict();
|
2017-01-31 21:35:45 +01:00
|
|
|
cross_realm_dict = new Dict(); // keyed by user_id
|
2017-01-31 21:02:15 +01:00
|
|
|
pm_recipient_count_dict = new Dict();
|
2016-12-15 22:18:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// WE INITIALIZE DATA STRUCTURES HERE!
|
|
|
|
exports.init();
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2016-10-31 15:56:57 +01:00
|
|
|
exports.get_person_from_user_id = function (user_id) {
|
2017-01-24 23:10:01 +01:00
|
|
|
if (!people_by_user_id_dict.has(user_id)) {
|
|
|
|
blueslip.error('Unknown user_id in get_person_from_user_id: ' + user_id);
|
|
|
|
return undefined;
|
|
|
|
}
|
2016-10-31 15:56:57 +01:00
|
|
|
return people_by_user_id_dict.get(user_id);
|
|
|
|
};
|
|
|
|
|
2017-01-31 20:44:51 +01:00
|
|
|
exports.get_by_email = function (email) {
|
|
|
|
var person = people_dict.get(email);
|
|
|
|
|
|
|
|
if (!person) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (person.email.toLowerCase() !== email.toLowerCase()) {
|
|
|
|
blueslip.warn(
|
|
|
|
'Obsolete email passed to get_by_email: ' + email +
|
|
|
|
' new email = ' + person.email
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return person;
|
|
|
|
};
|
|
|
|
|
2017-03-30 18:47:38 +02:00
|
|
|
exports.get_realm_count = function () {
|
|
|
|
// This returns the number of active people in our realm. It should
|
|
|
|
// exclude bots and deactivated users.
|
2017-10-26 18:26:28 +02:00
|
|
|
return active_user_dict.num_items();
|
2017-03-30 18:47:38 +02:00
|
|
|
};
|
|
|
|
|
2017-02-07 17:59:11 +01:00
|
|
|
exports.id_matches_email_operand = function (user_id, email) {
|
|
|
|
var person = exports.get_by_email(email);
|
|
|
|
|
|
|
|
if (!person) {
|
|
|
|
// The user may type bad data into the search bar, so
|
|
|
|
// we don't complain too loud here.
|
|
|
|
blueslip.debug('User email operand unknown: ' + email);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (person.user_id === user_id);
|
|
|
|
};
|
|
|
|
|
2017-01-31 20:44:51 +01:00
|
|
|
exports.update_email = function (user_id, new_email) {
|
|
|
|
var person = people_by_user_id_dict.get(user_id);
|
|
|
|
person.email = new_email;
|
|
|
|
people_dict.set(new_email, person);
|
|
|
|
|
|
|
|
// For legacy reasons we don't delete the old email
|
|
|
|
// keys in our dictionaries, so that reverse lookups
|
|
|
|
// still work correctly.
|
2014-01-30 22:42:19 +01:00
|
|
|
};
|
|
|
|
|
2016-10-30 15:22:24 +01:00
|
|
|
exports.get_user_id = function (email) {
|
2017-01-31 20:44:51 +01:00
|
|
|
var person = people.get_by_email(email);
|
2016-10-30 15:22:24 +01:00
|
|
|
if (person === undefined) {
|
2017-02-03 17:41:16 +01:00
|
|
|
var error_msg = 'Unknown email for get_user_id: ' + email;
|
2017-03-04 01:36:02 +01:00
|
|
|
blueslip.error(error_msg);
|
2016-10-30 15:22:24 +01:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
var user_id = person.user_id;
|
|
|
|
if (!user_id) {
|
2017-10-07 17:57:16 +02:00
|
|
|
blueslip.error('No user_id found for ' + email);
|
2016-10-30 15:22:24 +01:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return user_id;
|
|
|
|
};
|
|
|
|
|
2017-03-26 19:32:54 +02:00
|
|
|
exports.is_known_user_id = function (user_id) {
|
|
|
|
/*
|
|
|
|
For certain low-stakes operations, such as emoji reactions,
|
|
|
|
we may get a user_id that we don't know about, because the
|
|
|
|
user may have been deactivated. (We eventually want to track
|
|
|
|
deactivated users on the client, but until then, this is an
|
|
|
|
expedient thing we can check.)
|
|
|
|
*/
|
|
|
|
return people_by_user_id_dict.has(user_id);
|
|
|
|
};
|
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
function sort_numerically(user_ids) {
|
|
|
|
user_ids = _.map(user_ids, function (user_id) {
|
|
|
|
return parseInt(user_id, 10);
|
|
|
|
});
|
|
|
|
|
|
|
|
user_ids.sort(function (a, b) {
|
|
|
|
return a - b;
|
|
|
|
});
|
|
|
|
|
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
|
2017-02-05 00:22:16 +01:00
|
|
|
exports.huddle_string = function (message) {
|
|
|
|
if (message.type !== 'private') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var user_ids = _.map(message.display_recipient, function (recip) {
|
|
|
|
return recip.id;
|
|
|
|
});
|
|
|
|
|
|
|
|
function is_huddle_recip(user_id) {
|
|
|
|
return user_id &&
|
|
|
|
people_by_user_id_dict.has(user_id) &&
|
|
|
|
(!exports.is_my_user_id(user_id));
|
|
|
|
}
|
|
|
|
|
|
|
|
user_ids = _.filter(user_ids, is_huddle_recip);
|
|
|
|
|
|
|
|
if (user_ids.length <= 1) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-01 15:54:47 +02:00
|
|
|
|
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-05 00:22:16 +01:00
|
|
|
|
|
|
|
return user_ids.join(',');
|
|
|
|
};
|
|
|
|
|
2016-11-15 22:55:37 +01:00
|
|
|
exports.user_ids_string_to_emails_string = function (user_ids_string) {
|
|
|
|
var user_ids = user_ids_string.split(',');
|
|
|
|
var emails = _.map(user_ids, function (user_id) {
|
|
|
|
var person = people_by_user_id_dict.get(user_id);
|
|
|
|
if (person) {
|
|
|
|
return person.email;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!_.all(emails)) {
|
|
|
|
blueslip.error('Unknown user ids: ' + user_ids_string);
|
|
|
|
return;
|
|
|
|
}
|
2016-11-19 00:33:32 +01:00
|
|
|
|
|
|
|
emails = _.map(emails, function (email) {
|
|
|
|
return email.toLowerCase();
|
|
|
|
});
|
|
|
|
|
|
|
|
emails.sort();
|
|
|
|
|
2016-11-15 22:55:37 +01:00
|
|
|
return emails.join(',');
|
|
|
|
};
|
|
|
|
|
2017-02-25 00:30:51 +01:00
|
|
|
exports.reply_to_to_user_ids_string = function (emails_string) {
|
|
|
|
// This is basically emails_strings_to_user_ids_string
|
|
|
|
// without blueslip warnings, since it can be called with
|
|
|
|
// invalid data.
|
|
|
|
var emails = emails_string.split(',');
|
|
|
|
|
|
|
|
var user_ids = _.map(emails, function (email) {
|
|
|
|
var person = people.get_by_email(email);
|
|
|
|
if (person) {
|
|
|
|
return person.user_id;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!_.all(user_ids)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-25 00:30:51 +01:00
|
|
|
|
|
|
|
return user_ids.join(',');
|
|
|
|
};
|
|
|
|
|
2017-06-02 05:46:09 +02:00
|
|
|
exports.get_user_time_preferences = function (user_id) {
|
2017-04-02 21:01:58 +02:00
|
|
|
var user_timezone = people.get_person_from_user_id(user_id).timezone;
|
|
|
|
if (user_timezone) {
|
|
|
|
if (page_params.twenty_four_hour_time) {
|
2017-06-02 05:46:09 +02:00
|
|
|
return {
|
|
|
|
timezone: user_timezone,
|
|
|
|
format: "HH:mm",
|
|
|
|
};
|
2017-04-02 21:01:58 +02:00
|
|
|
}
|
2017-06-02 05:46:09 +02:00
|
|
|
return {
|
|
|
|
timezone: user_timezone,
|
|
|
|
format: "hh:mm A",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.get_user_time = function (user_id) {
|
|
|
|
var user_pref = people.get_user_time_preferences(user_id);
|
|
|
|
if (user_pref) {
|
|
|
|
return moment().tz(user_pref.timezone).format(user_pref.format);
|
2017-04-02 21:01:58 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-15 22:55:37 +01:00
|
|
|
exports.emails_strings_to_user_ids_string = function (emails_string) {
|
|
|
|
var emails = emails_string.split(',');
|
2017-02-24 16:58:33 +01:00
|
|
|
return exports.email_list_to_user_ids_string(emails);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.email_list_to_user_ids_string = function (emails) {
|
2016-11-15 22:55:37 +01:00
|
|
|
var user_ids = _.map(emails, function (email) {
|
2017-01-31 20:44:51 +01:00
|
|
|
var person = people.get_by_email(email);
|
2016-11-15 22:55:37 +01:00
|
|
|
if (person) {
|
|
|
|
return person.user_id;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!_.all(user_ids)) {
|
2017-02-24 16:58:33 +01:00
|
|
|
blueslip.warn('Unknown emails: ' + emails);
|
2016-11-15 22:55:37 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-11-19 00:33:32 +01:00
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2016-11-19 00:33:32 +01:00
|
|
|
|
2016-11-15 22:55:37 +01:00
|
|
|
return user_ids.join(',');
|
|
|
|
};
|
|
|
|
|
2017-01-25 16:23:22 +01:00
|
|
|
exports.get_full_name = function (user_id) {
|
|
|
|
return people_by_user_id_dict.get(user_id).full_name;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.get_recipients = function (user_ids_string) {
|
|
|
|
// See message_store.get_pm_full_names() for a similar function.
|
|
|
|
|
|
|
|
var user_ids = user_ids_string.split(',');
|
|
|
|
var other_ids = _.reject(user_ids, exports.is_my_user_id);
|
|
|
|
|
|
|
|
if (other_ids.length === 0) {
|
|
|
|
// private message with oneself
|
|
|
|
return exports.my_full_name();
|
|
|
|
}
|
|
|
|
|
|
|
|
var names = _.map(other_ids, exports.get_full_name).sort();
|
|
|
|
return names.join(', ');
|
|
|
|
};
|
|
|
|
|
2017-02-14 01:15:26 +01:00
|
|
|
exports.pm_reply_user_string = function (message) {
|
|
|
|
var user_ids = people.pm_with_user_ids(message);
|
|
|
|
|
|
|
|
if (!user_ids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return user_ids.join(',');
|
|
|
|
};
|
|
|
|
|
2017-02-09 03:18:40 +01:00
|
|
|
exports.pm_reply_to = function (message) {
|
2017-02-14 00:46:51 +01:00
|
|
|
var user_ids = people.pm_with_user_ids(message);
|
2017-02-09 03:18:40 +01:00
|
|
|
|
2017-02-14 00:46:51 +01:00
|
|
|
if (!user_ids) {
|
2017-02-09 03:18:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var emails = _.map(user_ids, function (user_id) {
|
|
|
|
var person = people_by_user_id_dict.get(user_id);
|
|
|
|
if (!person) {
|
|
|
|
blueslip.error('Unknown user id in message: ' + user_id);
|
|
|
|
return '?';
|
|
|
|
}
|
|
|
|
return person.email;
|
|
|
|
});
|
|
|
|
|
|
|
|
emails.sort();
|
|
|
|
|
|
|
|
var reply_to = emails.join(',');
|
|
|
|
|
|
|
|
return reply_to;
|
|
|
|
};
|
|
|
|
|
2017-08-01 15:35:07 +02:00
|
|
|
function sorted_other_user_ids(user_ids) {
|
|
|
|
// This excludes your own user id unless you're the only user
|
|
|
|
// (i.e. you sent a message to yourself).
|
2017-02-06 20:48:01 +01:00
|
|
|
|
|
|
|
var other_user_ids = _.filter(user_ids, function (user_id) {
|
|
|
|
return !people.is_my_user_id(user_id);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (other_user_ids.length >= 1) {
|
|
|
|
user_ids = other_user_ids;
|
|
|
|
} else {
|
|
|
|
user_ids = [my_user_id];
|
|
|
|
}
|
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-06 20:48:01 +01:00
|
|
|
|
|
|
|
return user_ids;
|
2017-08-01 15:35:07 +02:00
|
|
|
}
|
|
|
|
|
2017-08-01 15:51:56 +02:00
|
|
|
exports.pm_lookup_key = function (user_ids_string) {
|
|
|
|
/*
|
|
|
|
The server will sometimes include our own user id
|
|
|
|
in keys for PMs, but we only want our user id if
|
|
|
|
we sent a message to ourself.
|
|
|
|
*/
|
|
|
|
var user_ids = user_ids_string.split(',');
|
|
|
|
user_ids = sorted_other_user_ids(user_ids);
|
|
|
|
return user_ids.join(',');
|
|
|
|
};
|
|
|
|
|
2017-08-01 15:35:07 +02:00
|
|
|
exports.pm_with_user_ids = function (message) {
|
|
|
|
if (message.type !== 'private') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.display_recipient.length === 0) {
|
|
|
|
blueslip.error('Empty recipient list in message');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var user_ids = _.map(message.display_recipient, function (elem) {
|
|
|
|
return elem.user_id || elem.id;
|
|
|
|
});
|
|
|
|
|
|
|
|
return sorted_other_user_ids(user_ids);
|
2017-02-06 20:48:01 +01:00
|
|
|
};
|
|
|
|
|
2017-06-04 01:51:53 +02:00
|
|
|
exports.group_pm_with_user_ids = function (message) {
|
|
|
|
if (message.type !== 'private') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.display_recipient.length === 0) {
|
|
|
|
blueslip.error('Empty recipient list in message');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var user_ids = _.map(message.display_recipient, function (elem) {
|
|
|
|
return elem.user_id || elem.id;
|
|
|
|
});
|
|
|
|
var is_user_present = _.some(user_ids, function (user_id) {
|
|
|
|
return people.is_my_user_id(user_id);
|
|
|
|
});
|
|
|
|
if (is_user_present) {
|
|
|
|
user_ids.sort();
|
|
|
|
if (user_ids.length > 2) {
|
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2017-02-06 20:48:01 +01:00
|
|
|
exports.pm_with_url = function (message) {
|
|
|
|
var user_ids = exports.pm_with_user_ids(message);
|
|
|
|
|
|
|
|
if (!user_ids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var suffix;
|
|
|
|
|
|
|
|
if (user_ids.length > 1) {
|
|
|
|
suffix = 'group';
|
|
|
|
} else {
|
|
|
|
var person = exports.get_person_from_user_id(user_ids[0]);
|
|
|
|
if (person && person.email) {
|
|
|
|
suffix = person.email.split('@')[0].toLowerCase();
|
|
|
|
} else {
|
|
|
|
blueslip.error('Unknown people in message');
|
|
|
|
suffix = 'unk';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var slug = user_ids.join(',') + '-' + suffix;
|
|
|
|
var uri = "#narrow/pm-with/" + slug;
|
|
|
|
return uri;
|
|
|
|
};
|
|
|
|
|
2017-02-10 03:18:57 +01:00
|
|
|
exports.update_email_in_reply_to = function (reply_to, user_id, new_email) {
|
|
|
|
// We try to replace an old email with a new email in a reply_to,
|
|
|
|
// but we try to avoid changing the reply_to if we don't have to,
|
|
|
|
// and we don't warn on any errors.
|
|
|
|
var emails = reply_to.split(',');
|
|
|
|
|
|
|
|
var persons = _.map(emails, function (email) {
|
|
|
|
return people_dict.get(email.trim());
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!_.all(persons)) {
|
|
|
|
return reply_to;
|
|
|
|
}
|
|
|
|
|
|
|
|
var needs_patch = _.any(persons, function (person) {
|
|
|
|
return person.user_id === user_id;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!needs_patch) {
|
|
|
|
return reply_to;
|
|
|
|
}
|
|
|
|
|
|
|
|
emails = _.map(persons, function (person) {
|
|
|
|
if (person.user_id === user_id) {
|
|
|
|
return new_email;
|
|
|
|
}
|
|
|
|
return person.email;
|
|
|
|
});
|
|
|
|
|
|
|
|
return emails.join(',');
|
|
|
|
};
|
2017-02-08 22:10:06 +01:00
|
|
|
|
|
|
|
exports.pm_with_operand_ids = function (operand) {
|
|
|
|
var emails = operand.split(',');
|
|
|
|
emails = _.map(emails, function (email) { return email.trim(); });
|
|
|
|
var persons = _.map(emails, function (email) {
|
|
|
|
return people_dict.get(email);
|
|
|
|
});
|
|
|
|
|
2017-06-19 09:32:56 +02:00
|
|
|
// If your email is included in a PM group with other people, just ignore it
|
|
|
|
if (persons.length > 1) {
|
|
|
|
persons = _.without(persons, people_by_user_id_dict.get(my_user_id));
|
|
|
|
}
|
|
|
|
|
2017-02-08 22:10:06 +01:00
|
|
|
if (!_.all(persons)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var user_ids = _.map(persons, function (person) {
|
|
|
|
return person.user_id;
|
|
|
|
});
|
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-08 22:10:06 +01:00
|
|
|
|
|
|
|
return user_ids;
|
|
|
|
};
|
|
|
|
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
exports.emails_to_slug = function (emails_string) {
|
|
|
|
var slug = exports.emails_strings_to_user_ids_string(emails_string);
|
|
|
|
|
|
|
|
if (!slug) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
slug += '-';
|
|
|
|
|
|
|
|
var emails = emails_string.split(',');
|
|
|
|
|
|
|
|
if (emails.length === 1) {
|
|
|
|
slug += emails[0].split('@')[0].toLowerCase();
|
|
|
|
} else {
|
|
|
|
slug += 'group';
|
|
|
|
}
|
|
|
|
|
|
|
|
return slug;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.slug_to_emails = function (slug) {
|
|
|
|
var m = /^([\d,]+)-/.exec(slug);
|
|
|
|
if (m) {
|
|
|
|
var user_ids = m[1];
|
|
|
|
return exports.user_ids_string_to_emails_string(user_ids);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-02-17 00:44:21 +01:00
|
|
|
exports.format_small_avatar_url = function (raw_url) {
|
2017-01-21 21:45:52 +01:00
|
|
|
var url = raw_url + "&s=50";
|
|
|
|
return url;
|
|
|
|
};
|
|
|
|
|
2017-01-11 16:45:06 +01:00
|
|
|
exports.sender_is_bot = function (message) {
|
|
|
|
if (message.sender_id) {
|
|
|
|
var person = exports.get_person_from_user_id(message.sender_id);
|
|
|
|
return person.is_bot;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2017-01-21 20:29:39 +01:00
|
|
|
exports.small_avatar_url = function (message) {
|
|
|
|
// Try to call this function in all places where we need 25px
|
|
|
|
// avatar images, so that the browser can help
|
|
|
|
// us avoid unnecessary network trips. (For user-uploaded avatars,
|
|
|
|
// the s=25 parameter is essentially ignored, but it's harmless.)
|
|
|
|
//
|
|
|
|
// We actually request these at s=50, so that we look better
|
|
|
|
// on retina displays.
|
2017-01-21 21:34:27 +01:00
|
|
|
|
|
|
|
var url = "";
|
|
|
|
var person;
|
|
|
|
|
|
|
|
if (message.sender_id) {
|
|
|
|
// We should always have message.sender_id, except for in the
|
|
|
|
// tutorial, where it's ok to fall back to the url in the fake
|
|
|
|
// messages.
|
|
|
|
person = exports.get_person_from_user_id(message.sender_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The first time we encounter a sender in a message, we may
|
|
|
|
// not have person.avatar_url set, but if we do, then use that.
|
|
|
|
if (person && person.avatar_url) {
|
|
|
|
url = person.avatar_url;
|
|
|
|
} else if (message.avatar_url) {
|
|
|
|
// Here we fall back to using the avatar_url from the message
|
|
|
|
// itself.
|
|
|
|
url = message.avatar_url;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (url) {
|
2017-02-17 00:44:21 +01:00
|
|
|
url = exports.format_small_avatar_url(url);
|
2017-01-21 20:29:39 +01:00
|
|
|
}
|
2017-01-21 21:34:27 +01:00
|
|
|
|
|
|
|
return url;
|
2017-01-21 20:29:39 +01:00
|
|
|
};
|
|
|
|
|
2017-10-26 18:45:08 +02:00
|
|
|
exports.is_valid_email_for_compose = function (email) {
|
|
|
|
if (people.is_cross_realm_email(email)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
var person = people.get_by_email(email);
|
|
|
|
if (!person) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return active_user_dict.has(person.user_id);
|
|
|
|
};
|
|
|
|
|
2017-10-26 18:52:42 +02:00
|
|
|
exports.get_active_user_for_email = function (email) {
|
2017-01-31 20:44:51 +01:00
|
|
|
var person = people.get_by_email(email);
|
2017-01-31 23:45:10 +01:00
|
|
|
if (!person) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2017-10-26 18:26:28 +02:00
|
|
|
return active_user_dict.get(person.user_id);
|
2014-01-30 22:42:19 +01:00
|
|
|
};
|
|
|
|
|
2017-10-26 21:00:30 +02:00
|
|
|
exports.is_active_user_for_popover = function (user_id) {
|
|
|
|
// For popover menus, we include cross-realm bots as active
|
|
|
|
// users.
|
|
|
|
|
|
|
|
if (cross_realm_dict.get(user_id)) {
|
2017-10-25 00:59:51 +02:00
|
|
|
return true;
|
|
|
|
}
|
2017-10-26 21:00:30 +02:00
|
|
|
if (active_user_dict.has(user_id)) {
|
|
|
|
return true;
|
2017-10-25 00:59:51 +02:00
|
|
|
}
|
2017-10-26 21:00:30 +02:00
|
|
|
|
|
|
|
// TODO: We can report errors here once we start loading
|
|
|
|
// deactivated users at page-load time. For now just warn.
|
|
|
|
if (!people_by_user_id_dict.has(user_id)) {
|
|
|
|
blueslip.warn("Unexpectedly invalid user_id in user popover query: " + user_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2017-10-14 00:33:36 +02:00
|
|
|
};
|
|
|
|
|
2016-11-01 19:36:50 +01:00
|
|
|
exports.get_all_persons = function () {
|
2017-01-31 20:44:51 +01:00
|
|
|
return people_by_user_id_dict.values();
|
2016-11-01 19:36:50 +01:00
|
|
|
};
|
|
|
|
|
2016-11-01 19:45:53 +01:00
|
|
|
exports.get_realm_persons = function () {
|
2017-10-26 18:26:28 +02:00
|
|
|
return active_user_dict.values();
|
2016-11-01 19:45:53 +01:00
|
|
|
};
|
|
|
|
|
2017-10-26 18:17:43 +02:00
|
|
|
exports.get_active_user_ids = function () {
|
|
|
|
// This includes active users and active bots.
|
2017-10-26 18:26:28 +02:00
|
|
|
return active_user_dict.keys();
|
2017-10-07 20:10:10 +02:00
|
|
|
};
|
|
|
|
|
2016-11-02 23:48:47 +01:00
|
|
|
exports.is_cross_realm_email = function (email) {
|
2017-01-31 20:44:51 +01:00
|
|
|
var person = people.get_by_email(email);
|
2017-01-31 21:35:45 +01:00
|
|
|
if (!person) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return cross_realm_dict.has(person.user_id);
|
2016-11-02 23:48:47 +01:00
|
|
|
};
|
|
|
|
|
2016-11-03 21:59:18 +01:00
|
|
|
exports.get_recipient_count = function (person) {
|
|
|
|
// We can have fake person objects like the "all"
|
|
|
|
// pseudo-person in at-mentions. They will have
|
|
|
|
// the pm_recipient_count on the object itself.
|
|
|
|
if (person.pm_recipient_count) {
|
|
|
|
return person.pm_recipient_count;
|
|
|
|
}
|
|
|
|
|
2017-01-31 21:02:15 +01:00
|
|
|
var user_id = person.user_id || person.id;
|
|
|
|
var count = pm_recipient_count_dict.get(user_id);
|
2016-11-03 21:59:18 +01:00
|
|
|
|
|
|
|
return count || 0;
|
|
|
|
};
|
|
|
|
|
2017-01-31 21:02:15 +01:00
|
|
|
exports.incr_recipient_count = function (user_id) {
|
|
|
|
var old_count = pm_recipient_count_dict.get(user_id) || 0;
|
|
|
|
pm_recipient_count_dict.set(user_id, old_count + 1);
|
2016-11-03 21:59:18 +01:00
|
|
|
};
|
|
|
|
|
2017-06-21 03:33:30 +02:00
|
|
|
// Diacritic removal from:
|
|
|
|
// https://stackoverflow.com/questions/18236208/perform-a-find-match-with-javascript-ignoring-special-language-characters-acce
|
|
|
|
function remove_diacritics(s) {
|
|
|
|
if (/^[a-z]+$/.test(s)) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
.replace(/[áàãâä]/g,"a")
|
|
|
|
.replace(/[éèëê]/g,"e")
|
|
|
|
.replace(/[íìïî]/g,"i")
|
|
|
|
.replace(/[óòöôõ]/g,"o")
|
|
|
|
.replace(/[úùüû]/g, "u")
|
|
|
|
.replace(/[ç]/g, "c")
|
|
|
|
.replace(/[ñ]/g, "n");
|
|
|
|
}
|
|
|
|
|
2017-06-23 08:33:48 +02:00
|
|
|
exports.person_matches_query = function (user, query) {
|
|
|
|
var email = user.email.toLowerCase();
|
|
|
|
var names = user.full_name.toLowerCase().split(' ');
|
2016-10-18 19:21:38 +02:00
|
|
|
|
2017-06-23 08:33:48 +02:00
|
|
|
var termlets = query.toLowerCase().split(/\s+/);
|
|
|
|
termlets = _.map(termlets, function (termlet) {
|
|
|
|
return termlet.trim();
|
|
|
|
});
|
2016-11-14 22:34:46 +01:00
|
|
|
|
2017-06-23 08:33:48 +02:00
|
|
|
if (email.indexOf(query.trim()) === 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return _.all(termlets, function (termlet) {
|
|
|
|
var is_ascii = /^[a-z]+$/.test(termlet);
|
|
|
|
return _.any(names, function (name) {
|
|
|
|
if (is_ascii) {
|
|
|
|
// Only ignore diacritics if the query is plain ascii
|
|
|
|
name = remove_diacritics(name);
|
|
|
|
}
|
|
|
|
if (name.indexOf(termlet) === 0) {
|
|
|
|
return true;
|
|
|
|
}
|
2016-11-14 22:22:02 +01:00
|
|
|
});
|
2017-06-23 08:33:48 +02:00
|
|
|
});
|
|
|
|
};
|
2016-11-14 22:22:02 +01:00
|
|
|
|
2017-06-23 08:33:48 +02:00
|
|
|
exports.filter_people_by_search_terms = function (users, search_terms) {
|
|
|
|
var filtered_users = new Dict();
|
2016-11-14 22:22:02 +01:00
|
|
|
|
2016-10-18 19:21:38 +02:00
|
|
|
// Loop through users and populate filtered_users only
|
|
|
|
// if they include search_terms
|
|
|
|
_.each(users, function (user) {
|
|
|
|
var person = exports.get_by_email(user.email);
|
|
|
|
// Get person object (and ignore errors)
|
|
|
|
if (!person || !person.full_name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return user emails that include search terms
|
2017-06-23 08:33:48 +02:00
|
|
|
var match = _.any(search_terms, function (search_term) {
|
|
|
|
return exports.person_matches_query(user, search_term);
|
2016-10-18 19:21:38 +02:00
|
|
|
});
|
2016-11-14 21:37:36 +01:00
|
|
|
|
2016-11-14 22:22:02 +01:00
|
|
|
if (match) {
|
2017-01-26 13:00:27 +01:00
|
|
|
filtered_users.set(person.user_id, true);
|
2016-11-14 21:37:36 +01:00
|
|
|
}
|
2016-10-18 19:21:38 +02:00
|
|
|
});
|
|
|
|
return filtered_users;
|
|
|
|
};
|
|
|
|
|
2017-10-26 18:52:42 +02:00
|
|
|
exports.get_by_name = function (name) {
|
2014-01-30 22:42:19 +01:00
|
|
|
return people_by_name_dict.get(name);
|
|
|
|
};
|
|
|
|
|
|
|
|
function people_cmp(person1, person2) {
|
|
|
|
var name_cmp = util.strcmp(person1.full_name, person2.full_name);
|
|
|
|
if (name_cmp < 0) {
|
|
|
|
return -1;
|
|
|
|
} else if (name_cmp > 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return util.strcmp(person1.email, person2.email);
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.get_rest_of_realm = function get_rest_of_realm() {
|
|
|
|
var people_minus_you = [];
|
2017-10-26 18:26:28 +02:00
|
|
|
active_user_dict.each(function (person) {
|
2017-01-19 20:18:03 +01:00
|
|
|
if (!exports.is_current_user(person.email)) {
|
2016-12-03 03:08:47 +01:00
|
|
|
people_minus_you.push({email: person.email,
|
2017-01-07 07:58:08 +01:00
|
|
|
user_id: person.user_id,
|
2016-12-03 03:08:47 +01:00
|
|
|
full_name: person.full_name});
|
2014-01-30 22:42:19 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return people_minus_you.sort(people_cmp);
|
|
|
|
};
|
|
|
|
|
2014-02-01 15:17:17 +01:00
|
|
|
exports.add = function add(person) {
|
2016-10-31 15:56:57 +01:00
|
|
|
if (person.user_id) {
|
|
|
|
people_by_user_id_dict.set(person.user_id, person);
|
|
|
|
} else {
|
|
|
|
// We eventually want to lock this down completely
|
|
|
|
// and report an error and not update other the data
|
|
|
|
// structures here, but we have a lot of edge cases
|
|
|
|
// with cross-realm bots, zephyr users, etc., deactivated
|
|
|
|
// users, where we are probably fine for now not to
|
|
|
|
// find them via user_id lookups.
|
|
|
|
blueslip.warn('No user_id provided for ' + person.email);
|
|
|
|
}
|
|
|
|
|
2014-01-30 22:42:19 +01:00
|
|
|
people_dict.set(person.email, person);
|
|
|
|
people_by_name_dict.set(person.full_name, person);
|
|
|
|
};
|
|
|
|
|
2017-01-31 23:45:10 +01:00
|
|
|
exports.add_in_realm = function (person) {
|
2017-10-26 18:26:28 +02:00
|
|
|
active_user_dict.set(person.user_id, person);
|
2014-01-30 22:42:19 +01:00
|
|
|
exports.add(person);
|
|
|
|
};
|
|
|
|
|
2016-12-15 22:44:42 +01:00
|
|
|
exports.deactivate = function (person) {
|
|
|
|
// We don't fully remove a person from all of our data
|
|
|
|
// structures, because deactivated users can be part
|
|
|
|
// of somebody's PM list.
|
2017-10-26 18:26:28 +02:00
|
|
|
active_user_dict.del(person.user_id);
|
2014-01-30 22:42:19 +01:00
|
|
|
};
|
|
|
|
|
2017-11-06 17:03:01 +01:00
|
|
|
exports.report_late_add = function (user_id, email) {
|
|
|
|
// This function is extracted to make unit testing easier,
|
|
|
|
// plus we may fine-tune our reporting here for different
|
|
|
|
// types of realms.
|
|
|
|
var msg = 'Added user late: user_id=' + user_id + ' email=' + email;
|
|
|
|
|
2017-11-06 18:33:08 +01:00
|
|
|
blueslip.error(msg);
|
2017-11-06 17:03:01 +01:00
|
|
|
};
|
|
|
|
|
2016-12-15 23:33:36 +01:00
|
|
|
exports.extract_people_from_message = function (message) {
|
|
|
|
var involved_people;
|
|
|
|
|
|
|
|
switch (message.type) {
|
|
|
|
case 'stream':
|
|
|
|
involved_people = [{full_name: message.sender_full_name,
|
|
|
|
user_id: message.sender_id,
|
|
|
|
email: message.sender_email}];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'private':
|
|
|
|
involved_people = message.display_recipient;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add new people involved in this message to the people list
|
|
|
|
_.each(involved_people, function (person) {
|
2017-11-06 16:47:19 +01:00
|
|
|
if (person.unknown_local_echo_user) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var user_id = person.user_id || person.id;
|
|
|
|
|
|
|
|
if (people_by_user_id_dict.has(user_id)) {
|
|
|
|
return;
|
2017-11-06 15:48:44 +01:00
|
|
|
}
|
2017-11-06 16:47:19 +01:00
|
|
|
|
2017-11-06 17:03:01 +01:00
|
|
|
exports.report_late_add(user_id, person.email);
|
|
|
|
|
2017-11-06 16:47:19 +01:00
|
|
|
exports.add({
|
|
|
|
email: person.email,
|
|
|
|
user_id: user_id,
|
|
|
|
full_name: person.full_name,
|
|
|
|
is_admin: person.is_realm_admin || false,
|
|
|
|
is_bot: person.is_bot || false,
|
|
|
|
});
|
2017-11-06 15:48:44 +01:00
|
|
|
});
|
|
|
|
};
|
2016-12-15 23:33:36 +01:00
|
|
|
|
2017-11-06 15:48:44 +01:00
|
|
|
exports.maybe_incr_recipient_count = function (message) {
|
|
|
|
if (message.type !== 'private') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!message.sent_by_me) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Track the number of PMs we've sent to this person to improve autocomplete
|
|
|
|
_.each(message.display_recipient, function (person) {
|
|
|
|
|
|
|
|
if (person.unknown_local_echo_user) {
|
|
|
|
return;
|
2016-12-15 23:33:36 +01:00
|
|
|
}
|
2017-11-06 15:48:44 +01:00
|
|
|
|
|
|
|
var user_id = person.user_id || person.id;
|
|
|
|
exports.incr_recipient_count(user_id);
|
2016-12-15 23:33:36 +01:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-01-21 00:02:28 +01:00
|
|
|
exports.set_full_name = function (person_obj, new_full_name) {
|
|
|
|
if (people_by_name_dict.has(person_obj.full_name)) {
|
|
|
|
people_by_name_dict.del(person_obj.full_name);
|
|
|
|
}
|
|
|
|
people_by_name_dict.set(new_full_name, person_obj);
|
|
|
|
person_obj.full_name = new_full_name;
|
|
|
|
};
|
|
|
|
|
2017-01-19 20:18:03 +01:00
|
|
|
exports.is_current_user = function (email) {
|
|
|
|
if (email === null || email === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-19 23:04:52 +01:00
|
|
|
|
|
|
|
return email.toLowerCase() === exports.my_current_email().toLowerCase();
|
2017-01-19 20:18:03 +01:00
|
|
|
};
|
|
|
|
|
2017-01-20 23:16:28 +01:00
|
|
|
exports.initialize_current_user = function (user_id) {
|
|
|
|
my_user_id = user_id;
|
2017-01-19 23:04:52 +01:00
|
|
|
};
|
|
|
|
|
2017-01-20 23:49:20 +01:00
|
|
|
exports.my_full_name = function () {
|
|
|
|
return people_by_user_id_dict.get(my_user_id).full_name;
|
|
|
|
};
|
|
|
|
|
2017-01-19 23:04:52 +01:00
|
|
|
exports.my_current_email = function () {
|
|
|
|
return people_by_user_id_dict.get(my_user_id).email;
|
|
|
|
};
|
2017-01-19 20:18:03 +01:00
|
|
|
|
2017-01-19 23:52:09 +01:00
|
|
|
exports.my_current_user_id = function () {
|
|
|
|
return my_user_id;
|
|
|
|
};
|
|
|
|
|
2017-01-20 17:43:45 +01:00
|
|
|
exports.is_my_user_id = function (user_id) {
|
|
|
|
if (!user_id) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return user_id.toString() === my_user_id.toString();
|
|
|
|
};
|
|
|
|
|
2017-05-24 04:15:52 +02:00
|
|
|
exports.initialize = function () {
|
2017-04-24 21:59:07 +02:00
|
|
|
_.each(page_params.realm_users, function (person) {
|
2016-11-01 21:01:42 +01:00
|
|
|
exports.add_in_realm(person);
|
2014-01-30 22:42:19 +01:00
|
|
|
});
|
|
|
|
|
2017-11-06 14:56:06 +01:00
|
|
|
_.each(page_params.realm_non_active_users, function (person) {
|
|
|
|
exports.add(person);
|
|
|
|
});
|
|
|
|
|
2016-11-02 23:48:47 +01:00
|
|
|
_.each(page_params.cross_realm_bots, function (person) {
|
|
|
|
if (!people_dict.has(person.email)) {
|
|
|
|
exports.add(person);
|
|
|
|
}
|
2017-01-31 21:35:45 +01:00
|
|
|
cross_realm_dict.set(person.user_id, person);
|
2016-11-02 23:48:47 +01:00
|
|
|
});
|
2016-11-01 20:55:18 +01:00
|
|
|
|
2017-01-20 23:16:28 +01:00
|
|
|
exports.initialize_current_user(page_params.user_id);
|
2017-01-19 23:04:52 +01:00
|
|
|
|
2017-04-24 21:59:07 +02:00
|
|
|
delete page_params.realm_users; // We are the only consumer of this.
|
2017-11-06 14:56:06 +01:00
|
|
|
delete page_params.realm_non_active_users;
|
2016-11-02 23:48:47 +01:00
|
|
|
delete page_params.cross_realm_bots;
|
2017-05-24 04:15:52 +02:00
|
|
|
};
|
2014-01-30 22:42:19 +01:00
|
|
|
|
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = people;
|
|
|
|
}
|