/* 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 = '' + priority.htmlEnc() + '' + show.htmlEnc() + '' + type.htmlEnc() + '' + status.htmlEnc() + '' + hasPhoto.htmlEnc() + '' + checksum.htmlEnc() + '' + caps.htmlEnc() + ''; 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 = '' + priority.htmlEnc() + '' + show.htmlEnc() + '' + type.htmlEnc() + '' + status.htmlEnc() + '' + hasPhoto.htmlEnc() + '' + checksum.htmlEnc() + '' + caps.htmlEnc() + ''; 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 += ' (' + iXID + ') '; } // 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( '
' + '
' + nick_html + '
' + '
' + '' + '
' + '
' ); // 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('

' + 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_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('

' + value + '' + dStatus + '

'); // 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('unavailable'); 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(); } // Addon 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 = '
'; for(i in show_id) { // Yet in use: no need to display it! if(show_id[i] == 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 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(); }); }