--- /dev/null
+/*
+
+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);
+}