/* Jappix - An open social platform These are the presence JS scripts for Jappix ------------------------------------------------- License: AGPL Author: Valérian Saliou */ // Bundle var Presence = (function () { /** * Alias of this * @private */ var self = {}; /* Variables */ self.first_sent = false; self.auto_idle = false; /** * Handles groupchat presence * @private * @param {string} from * @param {string} xid * @param {string} hash * @param {string} type * @param {string} show * @param {string} status * @param {string} xid_hash * @param {string} resource * @param {object} node_sel * @param {object} presence * @param {number} priority * @param {boolean} has_photo * @param {string} checksum * @param {string} caps * @return {undefined} */ self._handleGroupchat = function(from, xid, hash, type, show, status, xid_hash, resource, node_sel, presence, priority, has_photo, checksum, caps) { try { var resources_obj, xml; var x_muc = node_sel.find('x[xmlns="' + NS_MUC_USER + '"]:first'); var item_sel = x_muc.find('item'); var affiliation = item_sel.attr('affiliation'); var role = item_sel.attr('role'); var reason = item_sel.find('reason').text(); var iXID = item_sel.attr('jid'); var iNick = item_sel.attr('nick'); var nick = resource; var message_time = DateUtils.getCompleteTime(); var not_initial = !Common.exists('#' + xid_hash + '[data-initial="true"]'); // Read the status code var status_code = []; x_muc.find('status').each(function() { status_code.push(parseInt($(this).attr('code'))); }); if(type && (type == 'unavailable')) { // User quitting self.displayMUC( from, xid_hash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, message_time, nick, not_initial ); DataStore.removeDB(Connection.desktop_hash, 'presence-stanza', from); resources_obj = self.removeResource(xid, resource); } else { // User joining // Fixes M-Link first presence bug (missing ID!) if(nick == Name.getMUCNick(xid_hash) && presence.getID() === null && !Common.exists('#page-engine #' + xid_hash + ' .list .' + hash)) { Groupchat.handleMUC(presence); Console.warn('Passed M-Link MUC first presence handling.'); } else { self.displayMUC( from, xid_hash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, message_time, nick, not_initial ); xml = '' + '' + priority.htmlEnc() + '' + '' + show.htmlEnc() + '' + '' + type.htmlEnc() + '' + '' + status.htmlEnc() + '' + '' + has_photo.htmlEnc() + '' + '' + checksum.htmlEnc() + '' + '' + caps.htmlEnc() + '' + ''; DataStore.setDB(Connection.desktop_hash, 'presence-stanza', from, xml); resources_obj = self.addResource(xid, resource); } } // Manage the presence self.processPriority(from, resource, resources_obj); self.funnel(from, hash); } catch(e) { Console.error('Groupchat._handleGroupchat', e); } }; /** * Handles user presence * @private * @param {string} from * @param {string} xid * @param {string} type * @param {string} show * @param {string} status * @param {string} xid_hash * @param {string} resource * @param {object} node_sel * @param {number} priority * @param {boolean} has_photo * @param {string} checksum * @param {string} caps * @return {undefined} */ self._handleUser = function(from, xid, type, show, status, xid_hash, resource, node_sel, priority, has_photo, checksum, caps) { try { var resources_obj, xml; // Subscribed/Unsubscribed stanzas if((type == 'subscribed') || (type == 'unsubscribed')) { return; } // Subscribe stanza else if(type == 'subscribe') { // This is a buddy we can safely authorize, because we added him to our roster if(Common.exists('#roster .buddy[data-xid="' + escape(xid) + '"]')) { self.acceptSubscribe(xid); } // We do not know this entity, we'd be better ask the user else { // Get the nickname var nickname = node_sel.find('nick[xmlns="' + NS_NICK + '"]:first').text(); // New notification Notification.create('subscribe', xid, [xid, nickname], status); } } // Unsubscribe stanza else if(type == 'unsubscribe') { Roster.send(xid, 'remove'); } // Other stanzas else { // Unavailable/error presence if(type == 'unavailable') { DataStore.removeDB(Connection.desktop_hash, 'presence-stanza', from); resources_obj = self.removeResource(xid, resource); } else { xml = '' + '' + priority.htmlEnc() + '' + '' + show.htmlEnc() + '' + '' + type.htmlEnc() + '' + '' + status.htmlEnc() + '' + '' + has_photo.htmlEnc() + '' + '' + checksum.htmlEnc() + '' + '' + caps.htmlEnc() + '' + ''; DataStore.setDB(Connection.desktop_hash, 'presence-stanza', from, xml); resources_obj = self.addResource(xid, resource); } // We manage the presence self.processPriority(xid, resource, resources_obj); self.funnel(xid, xid_hash); // We display the presence in the current chat if(Common.exists('#' + xid_hash)) { var dStatus = self.filterStatus(xid, status, false); if(dStatus) { dStatus = ' (' + dStatus + ')'; } // Generate the presence-in-chat code var dName = Name.getBuddy(from).htmlEnc(); var dBody = dName + ' (' + from + ') ' + Common._e("is now") + ' ' + self.humanShow(show, type) + dStatus; // Check whether it has been previously displayed var can_display = ($('#' + xid_hash + ' .one-line.system-message:last').html() != dBody); if(can_display) { Message.display( 'chat', xid, xid_hash, dName, dBody, DateUtils.getCompleteTime(), DateUtils.getTimeStamp(), 'system-message', false ); } } } // Get disco#infos for this presence (related to Caps) Caps.getDiscoInfos(from, caps); } catch(e) { Console.error('Groupchat._handleUser', e); } }; /** * Attaches picker events * @private * @param {string} name * @param {object} element_text_sel * @param {function} send_fn * @return {boolean} */ self._eventsPicker = function(element_picker_sel) { try { // Disabled? if(element_picker_sel.hasClass('disabled')) { return false; } // Initialize some vars var path = '#my-infos .f-presence div.bubble'; var show_val = self.getUserShow(); var shows_obj = { 'xa': Common._e("Not available"), 'away': Common._e("Away"), 'available': Common._e("Available") }; var can_append = !Common.exists(path); // Add this bubble! Bubble.show(path); if(!can_append) { return false; } // Generate the HTML code var html = '
'; for(var cur_show_name in shows_obj) { // Yet in use: no need to display it! if(cur_show_name == show_val) { continue; } html += ''; } html += '
'; // Append the HTML code $('#my-infos .f-presence').append(html); // Click event $(path + ' a').click(function() { // Update the presence show marker $('#my-infos .f-presence a.picker').attr( 'data-value', $(this).attr('data-value') ); // Close the bubble Bubble.close(); // Focus on the status input $(document).oneTime(10, function() { $('#presence-status').focus(); }); return false; }); } catch(e) { Console.error('Groupchat._eventsPicker', e); } finally { return false; } }; /** * Sends the user first presence * @public * @param {string} checksum * @return {undefined} */ self.sendFirst = function(checksum) { try { Console.info('First presence sent.'); var presence_status_sel = $('#presence-status'); // Jappix is now ready: change the title Interface.title('talk'); // Anonymous check var is_anonymous = Utils.isAnonymous(); // Update our marker self.first_sent = true; // Try to use the last status message var status = DataStore.getDB(Connection.desktop_hash, 'options', 'presence-status') || ''; // We tell the world that we are online if(!is_anonymous) { self.send('', '', '', status, checksum); } // Any status to apply? if(status) { presence_status_sel.val(status); } // Enable the presence picker presence_status_sel.removeAttr('disabled'); $('#my-infos .f-presence a.picker').removeClass('disabled'); // We set the last activity stamp DateUtils.presence_last_activity = DateUtils.getTimeStamp(); // We store our presence DataStore.setDB(Connection.desktop_hash, 'presence-show', 1, 'available'); // Not anonymous if(!is_anonymous) { // We get the stored bookmarks (because of the photo hash and some other stuffs, we must get it later) Storage.get(NS_BOOKMARKS); // We open a new chat if a XMPP link was submitted if((parent.location.hash != '#OK') && XMPPLinks.links_var.x) { // A link is submitted in the URL XMPPLinks.go(XMPPLinks.links_var.x); // Set a OK status parent.location.hash = 'OK'; } } } catch(e) { Console.error('Presence.sendFirst', e); } }; /** * Handles incoming presence packets * @public * @param {object} presence * @return {undefined} */ self.handle = function(presence) { try { // We define everything needed here var from = Common.fullXID(Common.getStanzaFrom(presence)); var hash = hex_md5(from); var node_sel = $(presence.getNode()); var xid = Common.bareXID(from); var xid_hash = hex_md5(xid); var resource = Common.thisResource(from); // We get the type content var type = presence.getType() || ''; // We get the priority content var priority = presence.getPriority() + ''; if(!priority || (type == 'error')) { priority = '0'; } // We get the show content var show = presence.getShow(); if(!show || (type == 'error')) { show = ''; } // We get the status content var status = presence.getStatus(); if(!status || (type == 'error')) { status = ''; } // We get the photo content var photo = node_sel.find('x[xmlns="' + NS_VCARD_P + '"]:first photo'); var checksum = photo.text(); var has_photo = (photo.size() && (type != 'error')) ? 'true' : 'false'; // We get the CAPS content var caps = node_sel.find('c[xmlns="' + NS_CAPS + '"]:first').attr('ver'); if(!caps || (type == 'error')) { caps = ''; } // This presence comes from another resource of my account with a difference avatar checksum if(xid == Common.getXID() && has_photo == 'true' && checksum != DataStore.getDB(Connection.desktop_hash, 'checksum', 1)) { Avatar.get(Common.getXID(), 'force', 'true', 'forget'); } if(Utils.isPrivate(xid)) { // Groupchat presence self._handleGroupchat( from, xid, hash, type, show, status, xid_hash, resource, node_sel, presence, priority, has_photo, checksum, caps ); } else { // User or gateway presence self._handleUser( from, xid, type, show, status, xid_hash, resource, node_sel, priority, has_photo, checksum, caps ); } Console.log('Presence received (type: ' + (type || 'available') + ', show: ' + (show || 'none') + ') from ' + from); } catch(e) { Console.error('Presence.handle', e); } }; /** * Displays a MUC presence * @public * @param {string} from * @param {string} roomHash * @param {string} hash * @param {string} type * @param {string} show * @param {string} status * @param {string} affiliation * @param {string} role * @param {string} reason * @param {string} status_code * @param {string} iXID * @param {string} iNick * @param {string} message_time * @param {string} nick * @param {boolean} initial * @return {undefined} */ self.displayMUC = function(from, roomHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, message_time, nick, initial) { try { // Generate the values var room_xid = Common.bareXID(from); var thisUser = '#page-engine #' + roomHash + ' .list .' + hash; var thisPrivate = $('#' + hash + ' .message-area'); var nick_html = nick.htmlEnc(); var real_xid = ''; var write = nick_html + ' '; var notify = false; // Reset data? if(!role) { role = 'participant'; } if(!affiliation) { affiliation = 'none'; } // Must update the role? if(Common.exists(thisUser) && (($(thisUser).attr('data-role') != role) || ($(thisUser).attr('data-affiliation') != affiliation))) { $(thisUser).remove(); } // Any XID submitted? if(iXID) { real_xid = ' data-realxid="' + iXID + '"'; iXID = Common.bareXID(iXID); write += ' (' + iXID + ') '; } // User does not exists yet if(!Common.exists(thisUser) && (!type || (type == 'available'))) { var myself = ''; // Is it me? if(nick == Name.getMUCNick(roomHash)) { // Enable the room $('#' + roomHash + ' .message-area').removeAttr('disabled'); // Marker myself = ' myself'; } // Set the user in the MUC list $('#' + roomHash + ' .list .' + role + ' .title').after( '
' + '
' + '
' + nick_html + '
' + '
' + '' + '
' + '
' + '
' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '
' ); // Click event if(nick != Name.getMUCNick(roomHash)) { $(thisUser).hover(function() { if(iXID && Groupchat.affiliationMe(room_xid).code >= 2) { var user_actions_sel = $(this).find('.user-actions'); var user_actions_btn_sel = user_actions_sel.find('.action'); // Update buttons var i; var hide_btns = []; var user_affiliation = Groupchat.affiliationUser(room_xid, nick); if(user_affiliation.name == 'owner') { hide_btns.push( 'promote', 'demote', 'kick' ); } else if(user_affiliation.name === 'admin') { hide_btns.push( 'promote', 'kick' ); } else { hide_btns.push( 'demote' ); } if(Roster.isFriend(iXID)) { hide_btns.push( 'add' ); } // Go Go Go!! for(i in hide_btns) { user_actions_btn_sel.filter('.' + hide_btns[i]).hide(); } // Slide down? if(hide_btns.length < user_actions_btn_sel.size()) { user_actions_sel.stop(true).slideDown(250); } } }, function() { var user_actions_sel = $(this).find('.user-actions'); if(user_actions_sel.is(':visible')) { user_actions_sel.stop(true).slideUp(200, function() { user_actions_sel.find('.action').show(); }); } }); $(thisUser).find('.user-details').on('click', function() { Chat.checkCreate(from, 'private'); }); $(thisUser).find('.user-actions .action a').on('click', function() { var this_parent_sel = $(this).parent(); if(this_parent_sel.is('.promote')) { Groupchat.promoteModerator(room_xid, iXID); } else if(this_parent_sel.is('.demote')) { Groupchat.demoteModerator(room_xid, iXID); } else if(this_parent_sel.is('.add')) { this_parent_sel.hide(); Roster.addThisContact(iXID, nick); } else if(this_parent_sel.is('.kick')) { Groupchat.kickUser(room_xid, (iXID || from), nick); } return false; }); } // We tell the user that someone entered the room if(!initial && DataStore.getDB(Connection.desktop_hash, 'options', 'groupchatpresence') !== '0') { notify = true; write += Common._e("joined the chat room"); // Any status? if(status) { write += ' (' + Filter.message(status, nick_html, true) + ')'; } else { write += ' (' + Common._e("no status") + ')'; } } // Enable the private chat input thisPrivate.removeAttr('disabled'); } else if((type == 'unavailable') || (type == 'error')) { // Is it me? if(nick == Name.getMUCNick(roomHash)) { $(thisUser).remove(); // Disable the groupchat input $('#' + roomHash + ' .message-area').attr('disabled', true); // Remove all the groupchat users $('#' + roomHash + ' .list .user').remove(); } // Someone has been kicked or banned? if(Utils.existArrayValue(status_code, 301) || Utils.existArrayValue(status_code, 307)) { $(thisUser).remove(); notify = true; // Kicked? if(Utils.existArrayValue(status_code, 307)) { write += Common._e("has been kicked"); } // Banned? if(Utils.existArrayValue(status_code, 301)) { write += Common._e("has been banned"); } // Any reason? if(reason) { write += ' (' + Filter.message(reason, nick_html, true) + ')'; } else { write += ' (' + Common._e("no reason") + ')'; } } // Nickname change? else if(Utils.existArrayValue(status_code, 303) && iNick) { notify = true; write += Common.printf(Common._e("changed his/her nickname to %s"), iNick.htmlEnc()); // New values var new_xid = Common.cutResource(from) + '/' + iNick; var new_hash = hex_md5(new_xid); var new_class = 'user ' + new_hash; if($(thisUser).hasClass('myself')) { new_class += ' myself'; } // Die the click event $(thisUser).off('click'); // Change to the new nickname $(thisUser).attr('data-nick', escape(iNick)) .attr('data-xid', new_xid) .find('.name').text(iNick); // Change the user class $(thisUser).attr('class', new_class); // New click event $('#page-engine #' + roomHash + ' .list .' + new_hash).on('click', function() { Chat.checkCreate(new_xid, 'private'); }); } // We tell the user that someone left the room else if(!initial && DataStore.getDB(Connection.desktop_hash, 'options', 'groupchatpresence') !== '0') { $(thisUser).remove(); notify = true; write += Common._e("left the chat room"); // Any status? if(status) { write += ' (' + Filter.message(status, nick_html, true) + ')'; } else { write += ' (' + Common._e("no status") + ')'; } } // Disable the private chat input thisPrivate.attr('disabled', true); } // Must notify something if(notify) { Message.display('groupchat', from, roomHash, nick_html, write, message_time, DateUtils.getTimeStamp(), 'system-message', false); } // Set the good status show icon switch(show) { case 'chat': case 'away': case 'xa': case 'dnd': break; default: show = 'available'; break; } $(thisUser + ' .name').attr('class', 'name talk-images ' + show); // Set the good status text var uTitle = nick; // Any XID to add? if(iXID) { uTitle += ' (' + iXID + ')'; } // Any status to add? if(status) { uTitle += ' - ' + status; } $(thisUser).attr('title', uTitle); // Show or hide the role category, depending of its content $('#' + roomHash + ' .list .role').each(function() { var this_sel = $(this); if(this_sel.find('.user').size()) { this_sel.show(); } else { this_sel.hide(); } }); } catch(e) { Console.error('Presence.displayMUC', e); } }; /** * Filters a given status * @public * @param {string} xid * @param {string} status * @param {boolean} cut * @return {string} */ self.filterStatus = function(xid, status, cut) { try { var dStatus = ''; if(!status) { status = ''; } else { if(cut) { dStatus = Utils.truncate(status, 50); } else { dStatus = status; } dStatus = Filter.message(dStatus, Name.getBuddy(xid).htmlEnc(), true); } return dStatus; } catch(e) { Console.error('Presence.filterStatus', e); } }; /** * Displays a user's presence * @public * @param {string} value * @param {string} type * @param {string} show * @param {string} status * @param {string} hash * @param {string} xid * @param {string} avatar * @param {string} checksum * @param {string} caps * @return {undefined} */ self.display = function(value, type, show, status, hash, xid, avatar, checksum, caps) { try { // Display the presence in the roster var path = '#roster .' + hash; var buddy = $('#roster .content .' + hash); var dStatus = self.filterStatus(xid, status, false); var tStatus = Common.encodeQuotes(status); var biStatus; // The buddy presence behind his name $(path + ' .name .buddy-presence').replaceWith('

' + value + '

'); // The buddy presence in the buddy infos if(dStatus) { biStatus = dStatus; } else { biStatus = value; } $(path + ' .bi-status').replaceWith('

' + biStatus + '

'); // When the buddy disconnect himself, we hide him if((type == 'unavailable') || (type == 'error')) { // Set a special class to the buddy buddy.addClass('hidden-buddy'); // No filtering is launched? if(!Search.search_filtered) { buddy.hide(); } // All the buddies are shown? if(Roster.blist_all) { buddy.show(); } // Chat stuffs if(Common.exists('#' + hash)) { // Remove the chatstate stuffs ChatState.reset(hash); $('#' + hash + ' .chatstate').remove(); $('#' + hash + ' .message-area').removeAttr('data-chatstates'); // Get the buddy avatar (only if a chat is opened) Avatar.get(xid, 'cache', 'true', 'forget'); } } // If the buddy is online else { // When the buddy is online, we show it buddy.removeClass('hidden-buddy'); // No filtering is launched? if(!Search.search_filtered) { buddy.show(); } // Get the online buddy avatar if not a gateway Avatar.get(xid, 'cache', avatar, checksum); } // Display the presence in the chat if(Common.exists('#' + hash)) { // We generate a well formed status message if(dStatus) { // No need to write the same status two times if(dStatus == value) { dStatus = ''; } else { dStatus = ' (' + dStatus + ')'; } } // We show the presence value $('#' + hash + ' .bc-infos').replaceWith('

' + value + '' + dStatus + '

'); // Process the new status position self.adaptChat(hash); } // Display the presence in the switcher if(Common.exists('#page-switch .' + hash)) { $('#page-switch .' + hash + ' .icon').removeClass('available unavailable error away busy').addClass(type); } // Update roster groups if(!Search.search_filtered) { Roster.updateGroups(); } else { Search.funnelFilterBuddy(); } // Get the disco#infos for this user var highest = self.highestPriority(xid); if(highest) { Caps.getDiscoInfos(highest, caps); } else { Caps.displayDiscoInfos(xid, ''); } } catch(e) { Console.error('Presence.display', e); } }; /** * Process the chat presence position * @public * @param {string} hash * @return {undefined} */ self.adaptChat = function(hash) { try { // Get values var pep_numb = $('#' + hash + ' .bc-pep').find('a').size(); // Process the left/right position var presence_h = 12; if(pep_numb) { presence_h = (pep_numb * 20) + 18; } // Apply the left/right position var presence_h_tag = ($('html').attr('dir') == 'rtl') ? 'left' : 'right'; $('#' + hash + ' p.bc-infos').css(presence_h_tag, presence_h); } catch(e) { Console.error('Presence.adaptChat', e); } }; /** * Convert the presence "show" element into a human-readable output * @public * @param {string} show * @param {string} type * @return {undefined} */ self.humanShow = function(show, type) { try { if(type == 'unavailable') { show = Common._e("Unavailable"); } else if(type == 'error') { show = Common._e("Error"); } else { switch(show) { case 'chat': show = Common._e("Talkative"); break; case 'away': show = Common._e("Away"); break; case 'xa': show = Common._e("Not available"); break; case 'dnd': show = Common._e("Busy"); break; default: show = Common._e("Available"); break; } } return show; } catch(e) { Console.error('Presence.humanShow', e); } }; /** * Makes the presence data go in the right way * @public * @param {string} type * @param {string} show * @param {string} status * @param {string} hash * @param {string} xid * @param {string} avatar * @param {string} checksum * @param {string} caps * @return {undefined} */ self.IA = function(type, show, status, hash, xid, avatar, checksum, caps) { try { // Any status defined? status = status || self.humanShow(show, type); // Handle events if(type == 'error') { self.display(Common._e("Error"), 'error', show, status, hash, xid, avatar, checksum, caps); } else if(type == 'unavailable') { self.display(Common._e("Unavailable"), 'unavailable', show, status, hash, xid, avatar, checksum, caps); } else { switch(show) { case 'chat': self.display(Common._e("Talkative"), 'available', show, status, hash, xid, avatar, checksum, caps); break; case 'away': self.display(Common._e("Away"), 'away', show, status, hash, xid, avatar, checksum, caps); break; case 'xa': self.display(Common._e("Not available"), 'busy', show, status, hash, xid, avatar, checksum, caps); break; case 'dnd': self.display(Common._e("Busy"), 'busy', show, status, hash, xid, avatar, checksum, caps); break; default: self.display(Common._e("Available"), 'available', show, status, hash, xid, avatar, checksum, caps); break; } } } catch(e) { Console.error('Presence.IA', e); } }; /** * Flush the presence data for a given user * @public * @param {string} xid * @return {boolean} */ self.flush = function(xid) { try { var flushed_marker = false; var db_regex = new RegExp(('^' + Connection.desktop_hash + '_') + 'presence' + ('_(.+)')); for(var i = 0; i < DataStore.storageDB.length; i++) { // Get the pointer values var current = DataStore.storageDB.key(i); // If the pointer is on a stored presence if(current.match(db_regex)) { // Get the current XID var now_full = RegExp.$1; var now_bare = Common.bareXID(now_full); // If the current XID equals the asked XID if(now_bare == xid) { if(DataStore.removeDB(Connection.desktop_hash, 'presence-stanza', now_full)) { Console.info('Presence data flushed for: ' + now_full); flushed_marker = true; i--; } } } } return flushed_marker; } catch(e) { Console.error('Presence.flush', e); } }; /** * Process the highest resource priority for an user * @public * @param {string} xid * @param {string} resource * @param {object} resources_obj * @return {undefined} */ self.processPriority = function(xid, resource, resources_obj) { try { if(!xid) { Console.warn('No XID value'); return; } // Initialize vars var cur_resource, cur_from, cur_pr, cur_xml, cur_priority, from_highest; from_highest = null; max_priority = null; // Groupchat or gateway presence? (no priority here) if(xid.indexOf('/') !== -1 || Common.isGateway(xid)) { from_highest = xid; Console.log('Processed presence for groupchat user: ' + xid); } else { if(!self.highestPriority(xid)) { from_highest = xid; if(resource) { from_highest += '/' + resource; } Console.log('Processed initial presence for regular user: ' + xid + ' (highest priority for: ' + (from_highest || 'none') + ')'); } else { var fn_parse_resource = function(cur_resource) { // Read presence data cur_from = xid; if(cur_resource) { cur_from += '/' + cur_resource; } cur_pr = DataStore.getDB(Connection.desktop_hash, 'presence-stanza', cur_from); if(cur_pr) { // Parse presence data cur_xml = Common.XMLFromString(cur_pr); cur_priority = $(cur_xml).find('priority').text(); cur_priority = !isNaN(cur_priority) ? parseInt(cur_priority) : 0; // Higher priority? if((cur_priority >= max_priority) || (max_priority === null)) { max_priority = cur_priority; from_highest = cur_from; } } }; // Parse bare presences (used by gateway contacts, mostly) if(resources_obj.bare === 1) { fn_parse_resource(null); } // Parse resources for(cur_resource in resources_obj.list) { fn_parse_resource(cur_resource); } Console.log('Processed presence for regular user: ' + xid + ' (highest priority for: ' + (from_highest || 'none') + ')'); } } if(from_highest) { DataStore.setDB(Connection.desktop_hash, 'presence-priority', xid, from_highest); } else { DataStore.removeDB(Connection.desktop_hash, 'presence-priority', xid); } } catch(e) { Console.error('Presence.processPriority', e); } }; /** * Returns the highest presence priority XID for an user * @public * @param {string} xid * @return {string} */ self.highestPriority = function(xid) { try { return DataStore.getDB(Connection.desktop_hash, 'presence-priority', xid) || ''; } catch(e) { Console.error('Presence.highestPriority', e); } }; /** * Gets the presence stanza for given full XID * @public * @param {string} xid_full * @return {object} */ self.readStanza = function(xid_full) { try { var pr = DataStore.getDB(Connection.desktop_hash, 'presence-stanza', xid_full); if(!pr) { pr = 'unavailable'; } return Common.XMLFromString(pr); } catch(e) { Console.error('Presence.readStanza', e); } }; /** * Gets the resource from a XID which has the highest priority * @public * @param {string} xid * @return {object} */ self.highestPriorityStanza = function(xid) { try { return self.readStanza( self.highestPriority(xid) ); } catch(e) { Console.error('Presence.highestPriorityStanza', e); } }; /** * Lists presence resources for an user * @public * @param {string} xid * @return {object} */ self.resources = function(xid) { try { var resources_obj = { 'bare': 0, 'list': {} }; var resources_db = DataStore.getDB(Connection.desktop_hash, 'presence-resources', xid); if(resources_db) { resources_obj = $.evalJSON(resources_db); } return resources_obj; } catch(e) { Console.error('Presence.resources', e); } }; /** * Adds a given presence resource for an user * @public * @param {string} xid * @param {string} resource * @return {object} */ self.addResource = function(xid, resource) { var resources_obj = null; try { resources_obj = self.resources(xid); if(resource) { resources_obj.list[resource] = 1; } else { resources_obj.bare = 1; } DataStore.setDB(Connection.desktop_hash, 'presence-resources', xid, $.toJSON(resources_obj)); } catch(e) { Console.error('Presence.addResource', e); } finally { return resources_obj; } }; /** * Removes a given presence resource for an user * @public * @param {string} xid * @param {string} resource * @return {object} */ self.removeResource = function(xid, resource) { var resources_obj = null; try { resources_obj = self.resources(xid); if(resource) { delete resources_obj.list[resource]; } else { resources_obj.bare = 0; } DataStore.setDB(Connection.desktop_hash, 'presence-resources', xid, $.toJSON(resources_obj)); } catch(e) { Console.error('Presence.removeResource', e); } finally { return resources_obj; } }; /** * Makes something easy to process for the presence IA * @public * @param {string} xid * @param {string} hash * @return {undefined} */ self.funnel = function(xid, hash) { try { // Get the highest priority presence value var presence_node = $(self.highestPriorityStanza(xid)); var type = presence_node.find('type').text(); var show = presence_node.find('show').text(); var status = presence_node.find('status').text(); var avatar = presence_node.find('avatar').text(); var checksum = presence_node.find('checksum').text(); var caps = presence_node.find('caps').text(); // Display the presence with that stored value if(!type && !show) { self.IA('', 'available', status, hash, xid, avatar, checksum, caps); } else { self.IA(type, show, status, hash, xid, avatar, checksum, caps); } } catch(e) { Console.error('Presence.funnel', e); } }; /** * Sends a defined presence packet * @public * @param {string} to * @param {string} type * @param {string} show * @param {string} status * @param {string} checksum * @param {number} limit_history * @param {string} password * @param {function} handle * @return {undefined} */ self.send = function(to, type, show, status, checksum, limit_history, password, handle) { try { // Get some stuffs var priority = DataStore.getDB(Connection.desktop_hash, 'priority', 1) || '1'; checksum = checksum || DataStore.getDB(Connection.desktop_hash, 'checksum', 1); if(show == 'available') { show = ''; } if(type == 'available') { type = ''; } // New presence var presence = new JSJaCPresence(); // Avoid "null" or "none" if nothing stored if(!checksum || (checksum == 'none')) { checksum = ''; } // Presence headers if(to) presence.setTo(to); if(type) presence.setType(type); if(show) presence.setShow(show); if(status) presence.setStatus(status); presence.setPriority(priority); // CAPS (entity capabilities) presence.appendNode('c', { 'xmlns': NS_CAPS, 'hash': 'sha-1', 'node': 'https://jappix.org/', 'ver': Caps.mine() }); // Nickname var nickname = Name.get(); if(nickname && !limit_history) { presence.appendNode('nick', { 'xmlns': NS_NICK }, nickname); } // vcard-temp:x:update node var x = presence.appendNode('x', { 'xmlns': NS_VCARD_P }); x.appendChild(presence.buildNode('photo', { 'xmlns': NS_VCARD_P }, checksum)); // MUC X data if(limit_history || password) { var xMUC = presence.appendNode('x', { 'xmlns': NS_MUC }); // Max messages age (for MUC) if(limit_history) { xMUC.appendChild(presence.buildNode('history', { 'maxstanzas': 20, 'seconds': 86400, 'xmlns': NS_MUC })); } // Room password if(password) { xMUC.appendChild(presence.buildNode('password', { 'xmlns': NS_MUC }, password)); } } // Reachability details if(type != 'unavailable') { var reach_regex = new RegExp('[^+0-9]', 'g'); var reach_phone = DataStore.getDB(Connection.desktop_hash, 'profile', 'phone') || ''; reach_phone = reach_phone.replace(reach_regex, ''); if(reach_phone) { /* REF: http://www.xmpp.org/extensions/xep-0152.html */ var reach_node = presence.appendNode(presence.buildNode('reach', { 'xmlns': NS_URN_REACH })); reach_node.appendChild( presence.buildNode('addr', { 'uri': 'tel:' + reach_phone, 'xmlns': NS_URN_REACH }) ); } } // If away, send a last activity time if((show == 'away') || (show == 'xa')) { /* REF: http://xmpp.org/extensions/xep-0256.html */ presence.appendNode(presence.buildNode('query', { 'xmlns': NS_LAST, 'seconds': DateUtils.getPresenceLast() })); /* REF: http://xmpp.org/extensions/xep-0319.html */ presence.appendNode(presence.buildNode('idle', { 'xmlns': NS_URN_IDLE, 'since': DateUtils.getLastActivityDate() })); } else { DateUtils.presence_last_activity = DateUtils.getTimeStamp(); } // Send presence packet if(typeof handle == 'function') { con.send(presence, handle); } else { con.send(presence); } Console.info('Presence sent: ' + (type || 'available')); } catch(e) { Console.error('Presence.send', e); } }; /** * Performs all the actions to get the presence data * @public * @param {string} checksum * @param {boolean} autoidle * @return {undefined} */ self.sendActions = function(checksum, autoidle) { try { // We get the values of the inputs var show = self.getUserShow(); var status = self.getUserStatus(); // Send the presence if(!Utils.isAnonymous()) { self.send('', '', show, status, checksum); } // We set the good icon self.icon(show); // We store our presence if(!autoidle) DataStore.setDB(Connection.desktop_hash, 'presence-show', 1, show); // We send the presence to our active MUC $('.page-engine-chan[data-type="groupchat"]').each(function() { var tmp_nick = $(this).attr('data-nick'); if(!tmp_nick) { return; } var room = unescape($(this).attr('data-xid')); var nick = unescape(tmp_nick); // Must re-initialize? if(Connection.resume) { Groupchat.getMUC(room, nick); } // Not disabled? else if(!$(this).find('.message-area').attr('disabled')) { self.send(room + '/' + nick, '', show, status, '', true); } }); } catch(e) { Console.error('Presence.quickSend', e); } }; /** * Changes the presence icon * @public * @param {string} value * @return {undefined} */ self.icon = function(value) { try { $('#my-infos .f-presence a.picker').attr('data-value', value); } catch(e) { Console.error('Presence.icon', e); } }; /** * Sends a subscribe stanza * @public * @param {string} to * @param {string} type * @return {undefined} */ self.sendSubscribe = function(to, type) { try { var status = ''; // Subscribe request? if(type == 'subscribe') { status = Common.printf(Common._e("Hi, I am %s, I would like to add you as my friend."), Name.get()); } self.send(to, type, '', status); } catch(e) { Console.error('Presence.sendSubscribe', e); } }; /** * Accepts the subscription from another entity * @public * @param {string} xid * @param {string} name * @return {undefined} */ self.acceptSubscribe = function(xid, name) { try { // We update our chat $('#' + hex_md5(xid) + ' .tools-add').hide(); // We send a subsribed presence (to confirm) self.sendSubscribe(xid, 'subscribed'); // We send a subscription request (subscribe both sides) self.sendSubscribe(xid, 'subscribe'); // Specify the buddy name (if any) if(name) { Roster.send(xid, '', name); } } catch(e) { Console.error('Presence.acceptSubscribe', e); } }; /** * Sends automatic away presence * @public * @return {undefined} */ self.autoIdle = function() { try { // Not connected? if(!Common.isConnected()) { return; } // Stop if an xa presence was set manually var last_presence = self.getUserShow(); if(!self.auto_idle && ((last_presence == 'away') || (last_presence == 'xa'))) { return; } var idle_presence; var activity_limit; // Can we extend to auto extended away mode (20 minutes)? if(self.auto_idle && (last_presence == 'away')) { idle_presence = 'xa'; activity_limit = 1200; } else { idle_presence = 'away'; activity_limit = 600; } // The user is really inactive and has set another presence than extended away if(((!self.auto_idle && (last_presence != 'away')) || (self.auto_idle && (last_presence == 'away'))) && (DateUtils.getLastActivity() >= activity_limit)) { // Then tell we use an auto presence self.auto_idle = true; // Get the old status message var status = DataStore.getDB(Connection.desktop_hash, 'options', 'presence-status') || ''; // Change the presence input $('#my-infos .f-presence a.picker').attr('data-value', idle_presence); $('#presence-status').val(status); // Then send the xa presence self.sendActions('', true); Console.info('Auto-idle presence sent: ' + idle_presence); } } catch(e) { Console.error('Presence.autoIdle', e); } }; /** * Restores the old presence on a document bind * @public * @return {undefined} */ self.eventIdle = function() { try { // If we were idle, restore our old presence if(self.auto_idle) { var presence_status_sel = $('#presence-status'); // Get the values var show = DataStore.getDB(Connection.desktop_hash, 'presence-show', 1); var status = DataStore.getDB(Connection.desktop_hash, 'options', 'presence-status'); // Change the presence input $('#my-infos .f-presence a.picker').attr('data-value', show); presence_status_sel.val(status); presence_status_sel.placeholder(); // Then restore the old presence self.sendActions('', true); Console.info('Presence restored: ' + (show || 'available')); } // Apply some values self.auto_idle = false; DateUtils.last_activity = DateUtils.getTimeStamp(); } catch(e) { Console.error('Presence.eventIdle', e); } }; /** * Lives the auto idle functions * @public * @return {undefined} */ self.liveIdle = function() { try { // Apply the autoIdle function every minute self.auto_idle = false; $('#my-infos .f-presence').everyTime('30s', self.autoIdle); // On body bind (click & key event) $('body').on('mousedown', self.eventIdle) .on('mousemove', self.eventIdle) .on('keydown', self.eventIdle); } catch(e) { Console.error('Presence.liveIdle', e); } }; /** * Kills the auto idle functions * @public * @return {undefined} */ self.dieIdle = function() { try { // Remove the event detector $('body').off('mousedown', self.eventIdle) .off('mousemove', self.eventIdle) .off('keydown', self.eventIdle); } catch(e) { Console.error('Presence.dieIdle', e); } }; /** * Gets the user presence show * @public * @return {string} */ self.getUserShow = function() { try { return $('#my-infos .f-presence a.picker').attr('data-value'); } catch(e) { Console.error('Presence.getUserShow', e); } }; /** * Gets the user presence status * @public * @return {string} */ self.getUserStatus = function() { try { return $('#presence-status').val(); } catch(e) { Console.error('Presence.getUserStatus', e); } }; /** * Plugin launcher * @public * @return {undefined} */ self.instance = function() { try { // Click event for user presence show $('#my-infos .f-presence a.picker').click(function() { return self._eventsPicker( $(this) ); }); // Submit events for user presence status var presence_status_sel = $('#presence-status'); presence_status_sel.placeholder(); presence_status_sel.keyup(function(e) { if(e.keyCode == 13) { $(this).blur(); return false; } }); presence_status_sel.blur(function() { // Read the parameters var show = self.getUserShow(); var status = self.getUserStatus(); // Read the old parameters var old_show = DataStore.getDB(Connection.desktop_hash, 'presence-show', 1); var old_status = DataStore.getDB(Connection.desktop_hash, 'options', 'presence-status'); // Must send the presence? if((show != old_show) || (status != old_status)) { // Update the local stored status DataStore.setDB(Connection.desktop_hash, 'options', 'presence-status', status); // Update the server stored status if(status != old_status) { Options.store(); } // Send the presence self.sendActions(); } }); // Input focus handler presence_status_sel.focus(function() { Bubble.close(); }); } catch(e) { Console.error('Presence.instance', e); } }; /** * Return class scope */ return self; })();