/* Jappix - An open social platform These are the messages JS scripts for Jappix ------------------------------------------------- License: AGPL Authors: Vanaryon, Maranda Last revision: 01/09/11 */ // Handles the incoming message packets function handleMessage(message) { // Error packet? Stop! if(handleErrorReply(message)) return; // We get the message items var from = fullXID(getStanzaFrom(message)); var id = message.getID(); var type = message.getType(); var body = trim(message.getBody()); var node = message.getNode(); var subject = trim(message.getSubject()); // We generate some values var xid = bareXID(from); var resource = thisResource(from); var hash = hex_md5(xid); var xHTML = $(node).find('html body').size(); var GCUser = false; // This message comes from a groupchat user if(isPrivate(xid) && ((type == 'chat') || !type) && resource) { GCUser = true; xid = from; hash = hex_md5(xid); } // Get message date var time, stamp, d_stamp; // Read the delay var delay = readMessageDelay(node); // Any delay? if(delay) { time = relativeDate(delay); d_stamp = Date.jab2date(delay); } // No delay: get actual time else { time = getCompleteTime(); d_stamp = new Date(); } // Get the date stamp stamp = extractStamp(d_stamp); // Received message if(hasReceived(message)) return messageReceived(hash, id); // Chatstate message if(node && !delay && ((((type == 'chat') || !type) && !exists('#page-switch .' + hash + ' .unavailable')) || (type == 'groupchat'))) { /* REF: http://xmpp.org/extensions/xep-0085.html */ // Re-process the hash var chatstate_hash = hash; if(type == 'groupchat') chatstate_hash = hex_md5(from); // Do something depending of the received state if($(node).find('active').size()) { displayChatState('active', chatstate_hash, type); // Tell Jappix the entity supports chatstates $('#' + chatstate_hash + ' .message-area').attr('data-chatstates', 'true'); logThis('Active chatstate received from: ' + from); } else if($(node).find('composing').size()) displayChatState('composing', chatstate_hash, type); else if($(node).find('paused').size()) displayChatState('paused', chatstate_hash, type); else if($(node).find('inactive').size()) displayChatState('inactive', chatstate_hash, type); else if($(node).find('gone').size()) displayChatState('gone', chatstate_hash, type); } // Invite message if($(node).find('x[xmlns=' + NS_MUC_USER + '] invite').size()) { // We get the needed values var iFrom = $(node).find('x[xmlns=' + NS_MUC_USER + '] invite').attr('from'); var iRoom = $(node).find('x[xmlns=' + NS_XCONFERENCE + ']').attr('jid'); // Old invite method? if(!iRoom) iRoom = from; // We display the notification newNotification('invite_room', iFrom, [iRoom], body); logThis('Invite Request from: ' + iFrom + ' to join: ' + iRoom); return false; } // Request message if(message.getChild('confirm', NS_HTTP_AUTH)) { // Open a new notification newNotification('request', xid, [message], body); logThis('HTTP Request from: ' + xid); return false; } // OOB message if(message.getChild('x', NS_XOOB)) { handleOOB(from, id, 'x', node); logThis('Message OOB request from: ' + xid); return false; } // Roster Item Exchange message if(message.getChild('x', NS_ROSTERX)) { // Open a new notification newNotification('rosterx', xid, [message], body); logThis('Roster Item Exchange from: ' + xid); return false; } // Normal message if((type == 'normal') && body) { // Message date var messageDate = delay; // No message date? if(!messageDate) messageDate = getXMPPTime('utc'); // Message ID var messageID = hex_md5(xid + subject + messageDate); // We store the received message storeInboxMessage(xid, subject, body, 'unread', messageID, messageDate); // Display the inbox message if(exists('#inbox')) displayInboxMessage(xid, subject, body, 'unread', messageID, messageDate); // Check we have new messages (play a sound if any unread messages) if(checkInboxMessages()) soundPlay(2); // Send it to the server storeInbox(); return false; } // PubSub event if($(node).find('event').attr('xmlns') == NS_PUBSUB_EVENT) { // We get the needed values var iParse = $(node).find('event items'); var iNode = iParse.attr('node'); // Turn around the different result cases if(iNode) { switch(iNode) { // Mood case NS_MOOD: // Retrieve the values var iMood = iParse.find('mood'); var fValue = ''; var tText = ''; // There's something if(iMood.children().size()) { // Read the value fValue = node.getElementsByTagName('mood').item(0).childNodes.item(0).nodeName; // Read the text tText = iMood.find('text').text(); // Avoid errors if(!fValue) fValue = ''; } // Store the PEP event (and display it) storePEP(xid, 'mood', fValue, tText); break; // Activity case NS_ACTIVITY: // Retrieve the values var iActivity = iParse.find('activity'); var sValue = ''; var tText = ''; // There's something if(iActivity.children().size()) { // Read the value fValue = node.getElementsByTagName('activity').item(0).childNodes.item(0).nodeName; // Read the text tText = iActivity.find('text').text(); // Avoid errors if(!fValue) fValue = ''; } // Store the PEP event (and display it) storePEP(xid, 'activity', fValue, tText); break; // Tune case NS_TUNE: // Retrieve the values var iTune = iParse.find('tune'); var tArtist = iTune.find('artist').text(); var tSource = iTune.find('source').text(); var tTitle = iTune.find('title').text(); var tURI = iTune.find('uri').text(); // Store the PEP event (and display it) storePEP(xid, 'tune', tArtist, tTitle, tSource, tURI); break; // Geolocation case NS_GEOLOC: // Retrieve the values var iGeoloc = iParse.find('geoloc'); var tLat = iGeoloc.find('lat').text(); var tLon = iGeoloc.find('lon').text(); // Any extra-values? var tLocality = iGeoloc.find('locality').text(); var tRegion = iGeoloc.find('region').text(); var tCountry = iGeoloc.find('country').text(); var tHuman = humanPosition(tLocality, tRegion, tCountry); // Store the PEP event (and display it) storePEP(xid, 'geoloc', tLat, tLon, tHuman); break; // Microblog case NS_URN_MBLOG: displayMicroblog(message, xid, hash, 'mixed', 'push'); break; // Inbox case NS_URN_INBOX: // Do not handle friend's notifications if(xid == getXID()) handleNotifications(message); break; } } return false; } // If this is a room topic message if(subject && (type == 'groupchat')) { // Filter the vars var filter_subject = subject.replace(/\n+/g, ' '); var filteredSubject = filterThisMessage(filter_subject, resource, true); var filteredName = resource.htmlEnc(); // Display the new subject at the top $('#' + hash + ' .top .name .bc-infos .muc-topic').replaceWith('<span class="muc-topic" title="' + filter_subject + '">' + filteredSubject + '</span>'); // Display the new subject as a system message if(resource) { var topic_body = filteredName + ' ' + _e("changed the subject to:") + ' ' + filterThisMessage(subject, resource, true); displayMessage(type, from, hash, filteredName, topic_body, time, stamp, 'system-message', false); } } // If the message has a content if(xHTML || body) { var filteredMessage; var notXHTML = true; // IE bug fix if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9)) xHTML = 0; //If this is a xHTML message if(xHTML) { notXHTML = false; // Filter the xHTML message body = filterThisXHTML(node); } // Groupchat message if(type == 'groupchat') { /* REF: http://xmpp.org/extensions/xep-0045.html */ // We generate the message type and time var message_type = 'user-message'; // This is an old message if(delay && resource) message_type = 'old-message'; // This is a system message else if(!resource) message_type = 'system-message'; var nickQuote = ''; // If this is not an old message if(message_type == 'user-message') { var myNick = getMUCNick(hash); // If an user quoted our nick (with some checks) var regex = new RegExp('((^)|( )|(@))' + escapeRegex(myNick) + '(($)|(:)|(,)|( ))', 'gi'); if(body.match(regex) && (myNick != resource) && (message_type == 'user-message')) nickQuote = ' my-nick'; // We notify the user if there's a new personnal message if(nickQuote) { messageNotify(hash, 'personnal'); soundPlay(1); } // We notify the user there's a new unread muc message else messageNotify(hash, 'unread'); } // Display the received message displayMessage(type, from, hash, resource.htmlEnc(), body, time, stamp, message_type, notXHTML, nickQuote); } // Chat message else if((type == 'chat') || !type) { // Gets the nickname of the user var fromName = resource; var chatType = 'chat'; // Must send a receipt notification? if(hasReceipt(message) && (id != null)) sendReceived(type, from, id); // It does not come from a groupchat user, get the full name if(!GCUser) fromName = getBuddyName(xid); else chatType = 'private'; // If the chat isn't yet opened, open it ! if(!exists('#' + hash)) { // We create a new chat chatCreate(hash, xid, fromName, chatType); // We tell the user that a new chat has started soundPlay(0); } else soundPlay(1); // Display the received message displayMessage(type, xid, hash, fromName.htmlEnc(), body, time, stamp, 'user-message', notXHTML, '', 'him'); // We notify the user messageNotify(hash, 'personnal'); } return false; } return false; } // Sends a given message function sendMessage(hash, type) { // Get the values var message_area = $('#' + hash + ' .message-area'); var body = trim(message_area.val()); var xid = unescape(message_area.attr('data-to')); // If the user didn't entered any message, stop if(!body || !xid) return false; try { // We send the message through the XMPP network var aMsg = new JSJaCMessage(); aMsg.setTo(xid); // Set an ID var id = genID(); aMsg.setID(id); // /help shortcut if(body.match(/^\/help\s*(.*)/)) { // Help text var help_text = '<p class="help" xmlns="http://www.w3.org/1999/xhtml">'; help_text += '<b>' + _e("Available shortcuts:") + '</b>'; // Shortcuts array var shortcuts = []; // Common shortcuts shortcuts.push(printf(_e("%s removes the chat logs"), '<em>/clear</em>')); shortcuts.push(printf(_e("%s joins a groupchat"), '<em>/join jid</em>')); shortcuts.push(printf(_e("%s closes the chat"), '<em>/part</em>')); shortcuts.push(printf(_e("%s shows the user profile"), '<em>/whois jid</em>')); // Groupchat shortcuts if(type == 'groupchat') { shortcuts.push(printf(_e("%s sends a message to the room"), '<em>/say message</em>')); shortcuts.push(printf(_e("%s changes your nickname"), '<em>/nick nickname</em>')); shortcuts.push(printf(_e("%s sends a message to someone in the room"), '<em>/msg nickname message</em>')); shortcuts.push(printf(_e("%s changes the room topic"), '<em>/topic subject</em>')); shortcuts.push(printf(_e("%s kicks an user of the room"), '<em>/kick nickname reason</em>')); shortcuts.push(printf(_e("%s bans an user of the room"), '<em>/ban nickname reason</em>')); shortcuts.push(printf(_e("%s invites someone to join the room"), '<em>/invite jid message</em>')); } // Generate the code from the array shortcuts = shortcuts.sort(); for(s in shortcuts) help_text += shortcuts[s] + '<br />'; help_text += '</p>'; // Display the message displayMessage(type, xid, hash, 'help', help_text, getCompleteTime(), getTimeStamp(), 'system-message', false); // Reset chatstate chatStateSend('active', xid, hash); } // /clear shortcut else if(body.match(/^\/clear/)) { cleanChat(hex_md5(xid)); // Reset chatstate chatStateSend('active', xid, hash); } // /join shortcut else if(body.match(/^\/join (\S+)\s*(.*)/)) { // Join var room = generateXID(RegExp.$1, 'groupchat'); var pass = RegExp.$2; checkChatCreate(room, 'groupchat'); // Reset chatstate chatStateSend('active', xid, hash); } // /part shortcut else if(body.match(/^\/part\s*(.*)/) && (!isAnonymous() || (isAnonymous() && (xid != generateXID(ANONYMOUS_ROOM, 'groupchat'))))) quitThisChat(xid, hex_md5(xid), type); // /whois shortcut else if(body.match(/^\/whois(( (\S+))|($))/)) { var whois_xid = RegExp.$3; // Groupchat WHOIS if(type == 'groupchat') { var nXID = getMUCUserXID(xid, whois_xid); if(!nXID) openThisInfo(6); else openUserInfos(nXID); } // Chat or private WHOIS else { if(!whois_xid) openUserInfos(xid); else openUserInfos(whois_xid); } // Reset chatstate chatStateSend('active', xid, hash); } // Chat message type else if(type == 'chat') { aMsg.setType('chat'); // Generates the correct message depending of the choosen style var notXHTML = true; var genMsg = generateMessage(aMsg, body, hash); if(genMsg == 'XHTML') notXHTML = false; // Receipt request var receipt_request = receiptRequest(hash); if(receipt_request) aMsg.appendNode('request', {'xmlns': NS_URN_RECEIPTS}); // Chatstate aMsg.appendNode('active', {'xmlns': NS_CHATSTATES}); // Send it! con.send(aMsg, handleErrorReply); // Filter the xHTML message (for us!) if(!notXHTML) body = filterThisXHTML(aMsg.getNode()); // Finally we display the message we just sent var my_xid = getXID(); displayMessage('chat', my_xid, hash, getBuddyName(my_xid).htmlEnc(), body, getCompleteTime(), getTimeStamp(), 'user-message', notXHTML, '', 'me', id); // Receipt timer if(receipt_request) checkReceived(hash, id); } // Groupchat message type else if(type == 'groupchat') { // /say shortcut if(body.match(/^\/say (.+)/)) { body = body.replace(/^\/say (.+)/, '$1'); aMsg.setType('groupchat'); generateMessage(aMsg, body, hash); con.send(aMsg, handleErrorReply); } // /nick shortcut else if(body.match(/^\/nick (.+)/)) { var nick = body.replace(/^\/nick (.+)/, '$1'); // Does not exist yet? if(!getMUCUserXID(xid, nick)) { // Send a new presence sendPresence(xid + '/' + nick, '', getUserShow(), getUserStatus(), '', false, false, handleErrorReply); // Change the stored nickname $('#' + hex_md5(xid)).attr('data-nick', escape(nick)); // Reset chatstate chatStateSend('active', xid, hash); } } // /msg shortcut else if(body.match(/^\/msg (\S+)\s+(.+)/)) { var nick = RegExp.$1; var body = RegExp.$2; var nXID = getMUCUserXID(xid, nick); // We check if the user exists if(!nXID) openThisInfo(6); // If the private message is not empty else if(body) { aMsg.setType('chat'); aMsg.setTo(nXID); generateMessage(aMsg, body, hash); con.send(aMsg, handleErrorReply); } } // /topic shortcut else if(body.match(/^\/topic (.+)/)) { var topic = body.replace(/^\/topic (.+)/, '$1'); aMsg.setType('groupchat'); aMsg.setSubject(topic); con.send(aMsg, handleMessageError); // Reset chatstate chatStateSend('active', xid, hash); } // /ban shortcut else if(body.match(/^\/ban (\S+)\s*(.*)/)) { var nick = RegExp.$1; var reason = RegExp.$2; var nXID = getMUCUserRealXID(xid, nick); // We check if the user exists if(!nXID) openThisInfo(6); else { // We generate the ban IQ var iq = new JSJaCIQ(); iq.setTo(xid); iq.setType('set'); var iqQuery = iq.setQuery(NS_MUC_ADMIN); var item = iqQuery.appendChild(iq.buildNode('item', {'affiliation': 'outcast', 'jid': nXID, 'xmlns': NS_MUC_ADMIN})); if(reason) item.appendChild(iq.buildNode('reason', {'xmlns': NS_MUC_ADMIN}, reason)); con.send(iq, handleErrorReply); } // Reset chatstate chatStateSend('active', xid, hash); } // /kick shortcut else if(body.match(/^\/kick (\S+)\s*(.*)/)) { var nick = RegExp.$1; var reason = RegExp.$2; var nXID = getMUCUserXID(xid, nick); // We check if the user exists if(!nXID) openThisInfo(6); else { // We generate the kick IQ var iq = new JSJaCIQ(); iq.setTo(xid); iq.setType('set'); var iqQuery = iq.setQuery(NS_MUC_ADMIN); var item = iqQuery.appendChild(iq.buildNode('item', {'nick': nick, 'role': 'none', 'xmlns': NS_MUC_ADMIN})); if(reason) item.appendChild(iq.buildNode('reason', {'xmlns': NS_MUC_ADMIN}, reason)); con.send(iq, handleErrorReply); } // Reset chatstate chatStateSend('active', xid, hash); } // /invite shortcut else if(body.match(/^\/invite (\S+)\s*(.*)/)) { var i_xid = RegExp.$1; var reason = RegExp.$2; var x = aMsg.appendNode('x', {'xmlns': NS_MUC_USER}); var aNode = x.appendChild(aMsg.buildNode('invite', {'to': i_xid, 'xmlns': NS_MUC_USER})); if(reason) aNode.appendChild(aMsg.buildNode('reason', {'xmlns': NS_MUC_USER}, reason)); con.send(aMsg, handleErrorReply); // Reset chatstate chatStateSend('active', xid, hash); } // No shortcut, this is a message else { aMsg.setType('groupchat'); // Chatstate aMsg.appendNode('active', {'xmlns': NS_CHATSTATES}); generateMessage(aMsg, body, hash); con.send(aMsg, handleMessageError); logThis('Message sent to: ' + xid + ' / ' + type, 3); } } // We reset the message input $('#' + hash + ' .message-area').val(''); } finally { return false; } } // Generates the correct message area style function generateStyle(hash) { // Initialize the vars var styles = '#' + hash + ' div.bubble-style'; var checkbox = styles + ' input[type=checkbox]'; var color = styles + ' a.color.selected'; var style = ''; // Loop the input values $(checkbox).filter(':checked').each(function() { // If there is a previous element if(style) style += ' '; // Get the current style switch($(this).attr('class')) { case 'bold': style += 'font-weight: bold;'; break; case 'italic': style += 'font-style: italic;'; break; case 'underline': style += 'text-decoration: underline;'; break; } }); // Get the color value $(color).each(function() { style += 'color: #' + $(this).attr('data-color'); }); return style; } // Generates the correct message code function generateMessage(aMsg, body, hash) { // Create the classical body aMsg.setBody(body); // Get the style var style = $('#' + hash + ' .message-area').attr('style'); // A message style is choosen if(style) { // Explode the message body new lines (to create one <p /> element by line) var new_lines = new Array(body); if(body.match(/\n/)) new_lines = body.split('\n'); // Create the XML elements var aHtml = aMsg.appendNode('html', {'xmlns': NS_XHTML_IM}); var aBody = aHtml.appendChild(aMsg.buildNode('body', {'xmlns': NS_XHTML})); // Use the exploded body array to create one element per entry for(i in new_lines) { // Current line var cLine = new_lines[i]; // Blank line, we put a <br /> if(cLine.match(/(^)(\s+)($)/) || !cLine) aBody.appendChild(aMsg.buildNode('br', {'xmlns': NS_XHTML})); // Line with content, we put a <p /> else { // HTML encode the line cLine = cLine.htmlEnc(); // Filter the links cLine = applyLinks(cLine, 'xhtml-im', style); // Append the filtered line $(aBody).append($('<p style="' + style + '">' + cLine + '</p>')); } } return 'XHTML'; } return 'PLAIN'; } // Displays a given message in a chat tab function displayMessage(type, xid, hash, name, body, time, stamp, message_type, is_xhtml, nick_quote, mode, id) { // Generate some stuffs var has_avatar = false; var xid_hash = ''; if(!nick_quote) nick_quote = ''; if(message_type != 'system-message') { has_avatar = true; xid_hash = hex_md5(xid); } // Can scroll? var cont_scroll = document.getElementById('chat-content-' + hash); var can_scroll = false; if(!cont_scroll.scrollTop || ((cont_scroll.clientHeight + cont_scroll.scrollTop) == cont_scroll.scrollHeight)) can_scroll = true; // Any ID? var data_id = ''; if(id) data_id = ' data-id="' + id + '"'; // Filter the message var filteredMessage = filterThisMessage(body, name, is_xhtml); // Display the received message in the room var messageCode = '<div class="one-line ' + message_type + nick_quote + '"' + data_id + '>'; // Name color attribute if(type == 'groupchat') attribute = ' style="color: ' + generateColor(name) + ';" class="name'; else { attribute = ' class="name'; if(mode) attribute += ' ' + mode; } // Close the class attribute if(message_type == 'system-message') attribute += ' hidden"'; else attribute += '"'; // Filter the previous displayed message var last = $('#' + hash + ' .one-group:last'); var last_name = last.find('b.name').attr('data-xid'); var last_type = last.attr('data-type'); var last_stamp = parseInt(last.attr('data-stamp')); var grouped = false; // We can group it with another previous message if((last_name == xid) && (message_type == last_type) && ((stamp - last_stamp) <= 1800)) grouped = true; // Is it a /me command? if(body.match(/(^|>)(\/me )([^<]+)/)) filteredMessage = '<i>' + filteredMessage + '</i>'; messageCode += filteredMessage + '</div>'; // Must group it? var group_path = ' .one-group:last'; if(!grouped) { // Generate message headers var message_head = ''; // Any avatar to add? if(has_avatar) message_head += '<div class="avatar-container"><img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" /></div>'; // Add the date & the name message_head += '<span class="date">' + time + '</span><b data-xid="' + encodeQuotes(xid) + '" ' + attribute + '>' + name + '</b>'; // Generate message code group_path = ''; messageCode = '<div class="one-group ' + xid_hash + '" data-type="' + message_type + '" data-stamp="' + stamp + '">' + message_head + messageCode + '</div>'; } // Archive message if(hash == 'archives') $('#archives .logs' + group_path).append(messageCode); // Instant message else { // Write the code in the DOM $('#' + hash + ' .content' + group_path).append(messageCode); // Must get the avatar? if(has_avatar && xid) getAvatar(xid, 'cache', 'true', 'forget'); } // Scroll to this message if(can_scroll) autoScroll(hash); }