]> git.mxchange.org Git - friendica-addons.git/blobdiff - jappixmini/jappix/js/presence.js
jappixmini: include jappix source
[friendica-addons.git] / jappixmini / jappix / js / presence.js
diff --git a/jappixmini/jappix/js/presence.js b/jappixmini/jappix/js/presence.js
new file mode 100644 (file)
index 0000000..0834209
--- /dev/null
@@ -0,0 +1,1075 @@
+/*
+
+Jappix - An open social platform
+These are the presence JS scripts for Jappix
+
+-------------------------------------------------
+
+License: AGPL
+Author: Vanaryon
+Last revision: 23/09/11
+
+*/
+
+// Sends the user first presence
+var FIRST_PRESENCE_SENT = false;
+
+function firstPresence(checksum) {
+       logThis('First presence sent.', 3);
+       
+       // Jappix is now ready: change the title
+       pageTitle('talk');
+       
+       // Anonymous check
+       var is_anonymous = isAnonymous();
+       
+       // Update our marker
+       FIRST_PRESENCE_SENT = true;
+       
+       // Try to use the last status message
+       var status = getDB('options', 'presence-status');
+       
+       if(!status)
+               status = '';
+       
+       // We tell the world that we are online
+       if(!is_anonymous)
+               sendPresence('', '', '', status, checksum);
+       
+       // Any status to apply?
+       if(status)
+               $('#presence-status').val(status);
+       
+       // Enable the presence picker
+       $('#presence-status').removeAttr('disabled');
+       $('#my-infos .f-presence a.picker').removeClass('disabled');
+       
+       // We set the last activity stamp
+       PRESENCE_LAST_ACTIVITY = getTimeStamp();
+       
+       // We store our presence
+       setDB('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)
+               getStorage(NS_BOOKMARKS);
+               
+               // We open a new chat if a XMPP link was submitted
+               if((parent.location.hash != '#OK') && LINK_VARS['x']) {
+                       // A link is submitted in the URL
+                       xmppLink(LINK_VARS['x']);
+                       
+                       // Set a OK status
+                       parent.location.hash = 'OK';
+               }
+       }
+}
+
+// Handles incoming presence packets
+function handlePresence(presence) {
+       // We define everything needed here
+       var from = fullXID(getStanzaFrom(presence));
+       var hash = hex_md5(from);
+       var node = presence.getNode();
+       var xid = bareXID(from);
+       var xidHash = hex_md5(xid);
+       
+       // We get the type content
+       var type = presence.getType();
+       if(!type)
+               type = '';
+       
+       // 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).find('x[xmlns=' + NS_VCARD_P + ']:first photo');
+       var checksum = photo.text();
+       var hasPhoto = photo.size();
+       
+       if(hasPhoto && (type != 'error'))
+               hasPhoto = 'true';
+       else
+               hasPhoto = 'false';
+       
+       // We get the CAPS content
+       var caps = $(node).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 == getXID()) && (hasPhoto == 'true') && (checksum != getDB('checksum', 1)))
+               getAvatar(getXID(), 'force', 'true', 'forget');
+       
+       // This presence comes from a groupchat
+       if(isPrivate(xid)) {
+               var x_muc = $(node).find('x[xmlns=' + NS_MUC_USER + ']:first');
+               var item = x_muc.find('item');
+               var affiliation = item.attr('affiliation');
+               var role = item.attr('role');
+               var reason = item.find('reason').text();
+               var iXID = item.attr('jid');
+               var iNick = item.attr('nick');
+               var nick = thisResource(from);
+               var messageTime = getCompleteTime();
+               var notInitial = true;
+               
+               // Read the status code
+               var status_code = new Array();
+               
+               x_muc.find('status').each(function() {
+                       status_code.push(parseInt($(this).attr('code')));
+               });
+               
+               // If this is an initial presence (when user join the room)
+               if(exists('#' + xidHash + '[data-initial=true]'))
+                       notInitial = false;
+               
+               // If one user is quitting
+               if(type && (type == 'unavailable')) {
+                       displayMucPresence(from, xidHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, notInitial);
+                       
+                       removeDB('presence', from);
+               }
+               
+               // If one user is joining
+               else {
+                       // Fixes M-Link first presence bug (missing ID!)
+                       if((nick == getMUCNick(xidHash)) && (presence.getID() == null) && !exists('#page-engine #' + xidHash + ' .list .' + hash)) {
+                               handleMUC(presence);
+                               
+                               logThis('Passed M-Link MUC first presence handling.', 2);
+                       }
+                       
+                       else {
+                               displayMucPresence(from, xidHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, notInitial);
+                               
+                               var xml = '<presence from="' + encodeQuotes(from) + '"><priority>' + priority.htmlEnc() + '</priority><show>' + show.htmlEnc() + '</show><type>' + type.htmlEnc() + '</type><status>' + status.htmlEnc() + '</status><avatar>' + hasPhoto.htmlEnc() + '</avatar><checksum>' + checksum.htmlEnc() + '</checksum><caps>' + caps.htmlEnc() + '</caps></presence>';
+                               
+                               setDB('presence', from, xml);
+                       }
+               }
+               
+               // Manage the presence
+               presenceFunnel(from, hash);
+       }
+       
+       // This presence comes from an user or a gateway
+       else {
+               // 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(exists('#buddy-list .buddy[data-xid=' + escape(xid) + ']'))
+                               acceptSubscribe(xid);
+                       
+                       // We do not know this entity, we'd be better ask the user
+                       else {
+                               // Get the nickname
+                               var nickname = $(node).find('nick[xmlns=' + NS_NICK + ']:first').text();
+                               
+                               // New notification
+                               newNotification('subscribe', xid, [xid, nickname], status);
+                       }
+               }
+               
+               // Unsubscribe stanza
+               else if(type == 'unsubscribe')
+                       sendRoster(xid, 'remove');
+               
+               // Other stanzas
+               else {
+                       // Unavailable/error presence
+                       if(type == 'unavailable')
+                               removeDB('presence', from);
+                       
+                       // Other presence (available, subscribe...)
+                       else {
+                               var xml = '<presence from="' + encodeQuotes(from) + '"><priority>' + priority.htmlEnc() + '</priority><show>' + show.htmlEnc() + '</show><type>' + type.htmlEnc() + '</type><status>' + status.htmlEnc() + '</status><avatar>' + hasPhoto.htmlEnc() + '</avatar><checksum>' + checksum.htmlEnc() + '</checksum><caps>' + caps.htmlEnc() + '</caps></presence>';
+                               
+                               setDB('presence', from, xml);
+                       }
+                       
+                       // We manage the presence
+                       presenceFunnel(xid, xidHash);
+                       
+                       // We display the presence in the current chat
+                       if(exists('#' + xidHash)) {
+                               var dStatus = filterStatus(xid, status, false);
+                               
+                               if(dStatus)
+                                       dStatus = ' (' + dStatus + ')';
+                               
+                               // Generate the presence-in-chat code
+                               var dName = getBuddyName(from).htmlEnc();
+                               var dBody = dName + ' (' + from + ') ' + _e("is now") + ' ' + humanShow(show, type) + dStatus;
+                               
+                               // Check whether it has been previously displayed
+                               var can_display = true;
+                               
+                               if($('#' + xidHash + ' .one-line.system-message:last').html() == dBody)
+                                       can_display = false;
+                               
+                               if(can_display)
+                                       displayMessage('chat', xid, xidHash, dName, dBody, getCompleteTime(), getTimeStamp(), 'system-message', false);
+                       }
+               }
+       }
+       
+       // For logger
+       if(!show) {
+               if(!type)
+                       show = 'available';
+               else
+                       show = 'unavailable';
+       }
+       
+       logThis('Presence received: ' + show + ', from ' + from);
+}
+
+// Displays a MUC presence
+function displayMucPresence(from, roomHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, initial) {
+       // Generate the values
+       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(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 = bareXID(iXID);
+               write += ' (<a onclick="return checkChatCreate(\'' + encodeOnclick(iXID) + '\', \'chat\');" href="xmpp:' + encodeOnclick(iXID) + '">' + iXID + '</a>) ';
+       }
+       
+       // User does not exists yet
+       if(!exists(thisUser) && (!type || (type == 'available'))) {
+               var myself = '';
+               
+               // Is it me?
+               if(nick == 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(
+                       '<div class="user ' + hash + myself + '" data-xid="' + encodeQuotes(from) + '" data-nick="' + escape(nick) + '"' + real_xid + ' data-role="' + encodeQuotes(role) + '" data-affiliation="' + encodeQuotes(affiliation) + '">' + 
+                               '<div class="name talk-images available">' + nick_html + '</div>' + 
+                               
+                               '<div class="avatar-container">' + 
+                                       '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + 
+                               '</div>' + 
+                       '</div>'
+               );
+               
+               // Click event
+               if(nick != getMUCNick(roomHash))
+                       $(thisUser).live('click', function() {
+                               checkChatCreate(from, 'private');
+                       });
+               
+               // We tell the user that someone entered the room
+               if(!initial) {
+                       notify = true;
+                       write += _e("joined the chat room");
+                       
+                       // Any status?
+                       if(status)
+                               write += ' (' + filterThisMessage(status, nick_html, true) + ')';
+                       else
+                               write += ' (' + _e("no status") + ')';
+               }
+               
+               // Enable the private chat input
+               thisPrivate.removeAttr('disabled');
+       }
+       
+       else if((type == 'unavailable') || (type == 'error')) {
+               // Is it me?
+               if(nick == 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(existArrayValue(status_code, 301) || existArrayValue(status_code, 307)) {
+                       $(thisUser).remove();
+                       notify = true;
+                       
+                       // Kicked?
+                       if(existArrayValue(status_code, 307))
+                               write += _e("has been kicked");
+                       
+                       // Banned?
+                       if(existArrayValue(status_code, 301))
+                               write += _e("has been banned");
+                       
+                       // Any reason?
+                       if(reason)
+                               write += ' (' + filterThisMessage(reason, nick_html, true) + ')';
+                       else
+                               write += ' (' + _e("no reason") + ')';
+               }
+               
+               // Nickname change?
+               else if(existArrayValue(status_code, 303) && iNick) {
+                       notify = true;
+                       write += printf(_e("changed his/her nickname to %s"), iNick.htmlEnc());
+                       
+                       // New values
+                       var new_xid = 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).die('click');
+                       
+                       // Change to the new nickname
+                       $(thisUser).attr('data-nick', 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).live('click', function() {
+                               checkChatCreate(new_xid, 'private');
+                       });
+               }
+               
+               // We tell the user that someone left the room
+               else if(!initial) {
+                       $(thisUser).remove();
+                       notify = true;
+                       write += _e("left the chat room");
+                       
+                       // Any status?
+                       if(status)
+                               write += ' (' + filterThisMessage(status, nick_html, true) + ')';
+                       else
+                               write += ' (' + _e("no status") + ')';
+               }
+               
+               // Disable the private chat input
+               thisPrivate.attr('disabled', true);
+       }
+       
+       // Must notify something
+       if(notify)
+               displayMessage('groupchat', from, roomHash, nick_html, write, messageTime, 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() {
+               if($(this).find('.user').size())
+                       $(this).show();
+               else
+                       $(this).hide();
+       });
+}
+
+// Filters a given status
+function filterStatus(xid, status, cut) {
+       var dStatus = '';
+       
+       if(!status)
+               status = '';
+       
+       else {
+               if(cut)
+                       dStatus = truncate(status, 50);
+               else
+                       dStatus = status;
+               
+               dStatus = filterThisMessage(dStatus, getBuddyName(xid).htmlEnc(), true);
+       }
+       
+       return dStatus;
+}
+
+// Displays a user's presence
+function displayPresence(value, type, show, status, hash, xid, avatar, checksum, caps) {
+       // Display the presence in the roster
+       var path = '#buddy-list .' + hash;
+       var buddy = $('#buddy-list .content .' + hash);
+       var dStatus = filterStatus(xid, status, false);
+       var tStatus = encodeQuotes(status);
+       var biStatus;
+       
+       // The buddy presence behind his name
+       $(path + ' .name .buddy-presence').replaceWith('<p class="buddy-presence talk-images ' + type + '">' + value + '</p>');
+       
+       // The buddy presence in the buddy infos
+       if(dStatus)
+               biStatus = dStatus;
+       else
+               biStatus = value;
+       
+       $(path + ' .bi-status').replaceWith('<p class="bi-status talk-images ' + type + '" title="' + tStatus + '">' + biStatus + '</p>');
+       
+       // 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_FILTERED)
+                       buddy.hide();
+               
+               // All the buddies are shown?
+               if(BLIST_ALL)
+                       buddy.show();
+               
+               // Chat stuffs
+               if(exists('#' + hash)) {
+                       // Remove the chatstate stuffs
+                       resetChatState(hash);
+                       $('#' + hash + ' .chatstate').remove();
+                       $('#' + hash + ' .message-area').removeAttr('data-chatstates');
+                       
+                       // Get the buddy avatar (only if a chat is opened)
+                       getAvatar(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_FILTERED)
+                       buddy.show();
+               
+               // Get the online buddy avatar if not a gateway
+               getAvatar(xid, 'cache', avatar, checksum);
+       }
+       
+       // Display the presence in the chat
+       if(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('<p class="bc-infos" title="' + tStatus + '"><span class="' + type + ' show talk-images">' + value + '</span>' + dStatus + '</p>');
+               
+               // Process the new status position
+               adaptChatPresence(hash);
+               
+               // Get the disco#infos for this user
+               var highest = getHighestResource(xid);
+               
+               if(highest)
+                       getDiscoInfos(highest, caps);
+               else
+                       displayDiscoInfos(xid, '');
+       }
+       
+       // Display the presence in the switcher
+       if(exists('#page-switch .' + hash))
+               $('#page-switch .' + hash + ' .icon').removeClass('available unavailable error away busy').addClass(type);
+       
+       // Update roster groups
+       if(!SEARCH_FILTERED)
+               updateGroups();
+       else
+               funnelFilterBuddySearch();
+}
+
+// Process the chat presence position
+function adaptChatPresence(hash) {
+       // Get values
+       var pep_numb = $('#' + hash + ' .bc-pep').find('a').size();
+       
+       // Process the right position
+       var presence_right = 12;
+       
+       if(pep_numb)
+               presence_right = (pep_numb * 20) + 18;
+       
+       // Apply the right position
+       $('#' + hash + ' p.bc-infos').css('right', presence_right);
+}
+
+// Convert the presence "show" element into a human-readable output
+function humanShow(show, type) {
+       if(type == 'unavailable')
+               show = _e("Unavailable");
+       
+       else if(type == 'error')
+               show = _e("Error");
+       
+       else {
+               switch(show) {
+                       case 'chat':
+                               show = _e("Talkative");
+                               break;
+                       
+                       case 'away':
+                               show = _e("Away");
+                               break;
+                       
+                       case 'xa':
+                               show = _e("Not available");
+                               break;
+                       
+                       case 'dnd':
+                               show = _e("Busy");
+                               break;
+                       
+                       default:
+                               show = _e("Available");
+                               break;
+               }
+       }
+       
+       return show;
+}
+
+// Makes the presence data go in the right way
+function presenceIA(type, show, status, hash, xid, avatar, checksum, caps) {
+       // Is there a status defined?
+       if(!status)
+               status = humanShow(show, type);
+       
+       // Then we can handle the events
+       if(type == 'error')
+               displayPresence(_e("Error"), 'error', show, status, hash, xid, avatar, checksum, caps);
+       
+       else if(type == 'unavailable')
+               displayPresence(_e("Unavailable"), 'unavailable', show, status, hash, xid, avatar, checksum, caps);
+       
+       else {
+               switch(show) {
+                       case 'chat':
+                               displayPresence(_e("Talkative"), 'available', show, status, hash, xid, avatar, checksum, caps);
+                               break;
+                       
+                       case 'away':
+                               displayPresence(_e("Away"), 'away', show, status, hash, xid, avatar, checksum, caps);
+                               break;
+                       
+                       case 'xa':
+                               displayPresence(_e("Not available"), 'busy', show, status, hash, xid, avatar, checksum, caps);
+                               break;
+                       
+                       case 'dnd':
+                               displayPresence(_e("Busy"), 'busy', show, status, hash, xid, avatar, checksum, caps);
+                               break;
+                       
+                       default:
+                               displayPresence(_e("Available"), 'available', show, status, hash, xid, avatar, checksum, caps);
+                               break;
+               }
+       }
+}
+
+// Gets the highest resource priority for an user
+function highestPriority(xid) {
+       var maximum = null;
+       var selector, priority, type, highest;
+       
+       // This is a groupchat presence
+       if(xid.indexOf('/') != -1)
+               highest = XMLFromString(getDB('presence', xid));
+       
+       // This is a "normal" presence: get the highest priority resource
+       else {
+               for(var i = 0; i < sessionStorage.length; i++) {
+                       // Get the pointer values
+                       var current = sessionStorage.key(i);
+                       
+                       // If the pointer is on a stored presence
+                       if(explodeThis('_', current, 0) == 'presence') {
+                               // Get the current XID
+                               var now = bareXID(explodeThis('_', current, 1));
+                               
+                               // If the current XID equals the asked XID
+                               if(now == xid) {
+                                       var xml = XMLFromString(sessionStorage.getItem(current));
+                                       var priority = parseInt($(xml).find('priority').text());
+                                       
+                                       // Higher priority
+                                       if((priority >= maximum) || (maximum == null)) {
+                                               maximum = priority;
+                                               highest = xml;
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       // The user might be offline if no highest
+       if(!highest)
+               highest = XMLFromString('<presence><type>unavailable</type></presence>');
+       
+       return highest;
+}
+
+// Gets the resource from a XID which has the highest priority
+function getHighestResource(xid) {
+       var xml = $(highestPriority(xid));
+       var highest = xml.find('presence').attr('from');
+       var type = xml.find('type').text();
+       
+       // If the use is online, we can return its highest resource
+       if(!type || (type == 'available') || (type == 'null'))
+               return highest;
+       else
+               return false;
+}
+
+// Makes something easy to process for the presence IA
+function presenceFunnel(xid, hash) {
+       // Get the highest priority presence value
+       var xml = $(highestPriority(xid));
+       var type = xml.find('type').text();
+       var show = xml.find('show').text();
+       var status = xml.find('status').text();
+       var avatar = xml.find('avatar').text();
+       var checksum = xml.find('checksum').text();
+       var caps = xml.find('caps').text();
+       
+       // Display the presence with that stored value
+       if(!type && !show)
+               presenceIA('', 'available', status, hash, xid, avatar, checksum, caps);
+       else
+               presenceIA(type, show, status, hash, xid, avatar, checksum, caps);
+}
+
+// Sends a defined presence packet
+function sendPresence(to, type, show, status, checksum, limit_history, password, handle) {
+       // Get some stuffs
+       var priority = getDB('priority', 1);
+       
+       if(!priority)
+               priority = '1';
+       if(!checksum)
+               checksum = getDB('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://www.jappix.com/', 'ver': myCaps()});
+       
+       // Nickname
+       var nickname = getName();
+       
+       if(nickname)
+               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));
+       }
+       
+       // 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': getPresenceLast()
+               }));
+       }
+       
+       // Else, set a new last activity stamp
+       else
+               PRESENCE_LAST_ACTIVITY = getTimeStamp();
+       
+       // Send the presence packet
+       if(handle)
+               con.send(presence, handle);
+       else
+               con.send(presence);
+       
+       if(!type)
+               type = 'available';
+       
+       logThis('Presence sent: ' + type, 3);
+}
+
+// Performs all the actions to get the presence data
+function presenceSend(checksum, autoidle) {
+       // We get the values of the inputs
+       var show = getUserShow();
+       var status = getUserStatus();
+       
+       // Send the presence
+       if(!isAnonymous())
+               sendPresence('', '', show, status, checksum);
+       
+       // We set the good icon
+       presenceIcon(show);
+       
+       // We store our presence
+       if(!autoidle)
+               setDB('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(RESUME)
+                       getMUC(room, nick);
+               
+               // Not disabled?
+               else if(!$(this).find('.message-area').attr('disabled'))
+                       sendPresence(room + '/' + nick, '', show, status, '', true);
+       });
+}
+
+// Changes the presence icon
+function presenceIcon(value) {
+       $('#my-infos .f-presence a.picker').attr('data-value', value);
+}
+
+// Sends a subscribe stanza
+function sendSubscribe(to, type) {
+       var status = '';
+       
+       // Subscribe request?
+       if(type == 'subscribe')
+               status = printf(_e("Hi, I am %s, I would like to add you as my friend."), getName());
+       
+       sendPresence(to, type, '', status);
+}
+
+// Accepts the subscription from another entity
+function acceptSubscribe(xid, name) {
+       // We update our chat
+       $('#' + hex_md5(xid) + ' .tools-add').hide();
+       
+       // We send a subsribed presence (to confirm)
+       sendSubscribe(xid, 'subscribed');
+       
+       // We send a subscription request (subscribe both sides)
+       sendSubscribe(xid, 'subscribe');
+       
+       // Specify the buddy name (if any)
+       if(name)
+               sendRoster(xid, '', name)
+}
+
+// Sends automatic away presence
+var AUTO_IDLE = false;
+
+function autoIdle() {
+       // Not connected?
+       if(!isConnected())
+               return;
+       
+       // Stop if an xa presence was set manually
+       var last_presence = getUserShow();
+       
+       if(!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(AUTO_IDLE && (last_presence == 'away')) {
+               idle_presence = 'xa';
+               activity_limit = 1200;
+       }
+       
+       // We must set the user to auto-away (10 minutes)
+       else {
+               idle_presence = 'away';
+               activity_limit = 600;
+       }
+       
+       // The user is really inactive and has set another presence than extended away
+       if(((!AUTO_IDLE && (last_presence != 'away')) || (AUTO_IDLE && (last_presence == 'away'))) && (getLastActivity() >= activity_limit)) {
+               // Then tell we use an auto presence
+               AUTO_IDLE = true;
+               
+               // Get the old status message
+               var status = getDB('options', 'presence-status');
+               
+               if(!status)
+                       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
+               presenceSend('', true);
+               
+               logThis('Auto-idle presence sent: ' + idle_presence, 3);
+       }
+}
+
+// Restores the old presence on a document bind
+function eventIdle() {
+       // If we were idle, restore our old presence
+       if(AUTO_IDLE) {
+               // Get the values
+               var show = getDB('presence-show', 1);
+               var status = getDB('options', 'presence-status');
+               
+               // Change the presence input
+               $('#my-infos .f-presence a.picker').attr('data-value', show);
+               $('#presence-status').val(status);
+               $('#presence-status').placeholder();
+               
+               // Then restore the old presence
+               presenceSend('', true);
+               
+               if(!show)
+                       show = 'available';
+               
+               logThis('Presence restored: ' + show, 3);
+       }
+       
+       // Apply some values
+       AUTO_IDLE = false;
+       LAST_ACTIVITY = getTimeStamp();
+}
+
+// Lives the auto idle functions
+function liveIdle() {
+       // Apply the autoIdle function every minute
+       AUTO_IDLE = false;
+       $('#my-infos .f-presence').everyTime('30s', autoIdle);
+       
+       // On body bind (click & key event)
+       $('body').live('mousedown', eventIdle)
+                .live('mousemove', eventIdle)
+                .live('keydown', eventIdle);
+}
+
+// Kills the auto idle functions
+function dieIdle() {
+       // Remove the event detector
+       $('body').die('mousedown', eventIdle)
+                .die('mousemove', eventIdle)
+                .die('keydown', eventIdle);
+}
+
+// Gets the user presence show
+function getUserShow() {
+       return $('#my-infos .f-presence a.picker').attr('data-value');
+}
+
+// Gets the user presence status
+function getUserStatus() {
+       return $('#presence-status').val();
+}
+
+// Plugin launcher
+function launchPresence() {
+       // Click event for user presence show
+       $('#my-infos .f-presence a.picker').click(function() {
+               // Disabled?
+               if($(this).hasClass('disabled'))
+                       return false;
+               
+               // Initialize some vars
+               var path = '#my-infos .f-presence div.bubble';
+               var show_id = ['xa', 'away', 'available'];
+               var show_lang = [_e("Not available"), _e("Away"), _e("Available")];
+               var show_val = getUserShow();
+               
+               // Yet displayed?
+               var can_append = true;
+               
+               if(exists(path))
+                       can_append = false;
+               
+               // Add this bubble!
+               showBubble(path);
+               
+               if(!can_append)
+                       return false;
+               
+               // Generate the HTML code
+               var html = '<div class="bubble removable">';
+               
+               for(i in show_id) {
+                       // Yet in use: no need to display it!
+                       if(show_id[i] == show_val)
+                               continue;
+                       
+                       html += '<a href="#" class="talk-images" data-value="' + show_id[i] + '" title="' + show_lang[i] + '"></a>';
+               }
+               
+               html += '</div>';
+               
+               // 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
+                       closeBubbles();
+                       
+                       // Focus on the status input
+                       $(document).oneTime(10, function() {
+                               $('#presence-status').focus();
+                       });
+                       
+                       return false;
+               });
+               
+               return false;
+       });
+       
+       // Submit events for user presence status
+       $('#presence-status').placeholder()
+       
+       .keyup(function(e) {
+               if(e.keyCode == 13) {
+                       $(this).blur();
+                       
+                       return false;
+               }
+       })
+       
+       .blur(function() {
+               // Read the parameters
+               var show = getUserShow();
+               var status = getUserStatus();
+               
+               // Read the old parameters
+               var old_show = getDB('presence-show', 1);
+               var old_status = getDB('options', 'presence-status');
+               
+               // Must send the presence?
+               if((show != old_show) || (status != old_status)) {
+                       // Update the local stored status
+                       setDB('options', 'presence-status', status);
+                       
+                       // Update the server stored status
+                       if(status != old_status)
+                               storeOptions();
+                       
+                       // Send the presence
+                       presenceSend();
+               }
+       })
+       
+       // Input focus handler
+       .focus(function() {
+               closeBubbles();
+       });
+}