(function() { 'use strict'; var weechat = angular.module('weechat'); weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', 'notifications', 'bufferResume', function($rootScope, $log, models, plugins, notifications, bufferResume) { var handleVersionInfo = function(message) { var content = message.objects[0].content; var version = content.value; // Store the WeeChat version in models // this eats things like 1.3-dev -> [1,3] models.version = version.split(".").map(function(c) { return parseInt(c); }); }; var handleConfValue = function(message) { var infolist = message.objects[0].content; for (var i = 0; i < infolist.length ; i++) { var key, val; var item = infolist[i]; for (var j = 0; j < item.length ; j++) { var confitem = item[j]; if (confitem.full_name) { key = confitem.full_name; } if (confitem.value) { val = confitem.value; } } if (key && val) { $log.debug('Setting wconfig "' + key + '" to value "' + val + '"'); models.wconfig[key] = val; } } }; var handleBufferClosing = function(message) { var bufferMessage = message.objects[0].content[0]; var bufferId = bufferMessage.pointers[0]; models.closeBuffer(bufferId); }; // inject a fake buffer line for date change if needed var injectDateChangeMessageIfNeeded = function(buffer, manually, old_date, new_date) { if (buffer.bufferType === 1) { // Don't add date change messages to free buffers return; } old_date.setHours(0, 0, 0, 0); new_date.setHours(0, 0, 0, 0); // Check if the date changed if (old_date.valueOf() !== new_date.valueOf()) { if (manually) { // if the message that caused this date change to be sent // would increment buffer.lastSeen, we should increment as // well. ++buffer.lastSeen; } var old_date_plus_one = old_date; old_date_plus_one.setDate(old_date.getDate() + 1); // it's not always true that a date with time 00:00:00 // plus one day will be time 00:00:00 old_date_plus_one.setHours(0, 0, 0, 0); var content = "\u001943"; // this colour corresponds to chat_day_change // Add day of the week if ($rootScope.supports_formatting_date) { content += new_date.toLocaleDateString(window.navigator.language, {weekday: "long"}); } else { // Gross code that only does English dates ew gross var dow_to_word = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; content += dow_to_word[new_date.getDay()]; } // if you're testing different date formats, // make sure to test different locales such as "en-US", // "en-US-u-ca-persian" (which has different weekdays, year 0, and an ERA) // "ja-JP-u-ca-persian-n-thai" (above, diff numbering, diff text) var extra_date_format = { day: "numeric", month: "long" }; if (new_date.getYear() !== old_date.getYear()) { extra_date_format.year = "numeric"; } content += " ("; if ($rootScope.supports_formatting_date) { content += new_date.toLocaleDateString(window.navigator.language, extra_date_format); } else { // ew ew not more gross code var month_to_word = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; content += month_to_word[new_date.getMonth()] + " " + new_date.getDate().toString(); if (extra_date_format.year === "numeric") { content += ", " + new_date.getFullYear().toString(); } } // Result should be something like // Friday (November 27) // or if the year is different, // Friday (November 27, 2015) // Comparing dates in javascript is beyond tedious if (old_date_plus_one.valueOf() !== new_date.valueOf()) { var date_diff = Math.round((new_date - old_date)/(24*60*60*1000)) + 1; if (date_diff < 0) { date_diff = -1*(date_diff); if (date_diff === 1) { content += ", 1 day before"; } else { content += ", " + date_diff + " days before"; } } else { content += ", " + date_diff + " days later"; } // Result: Friday (November 27, 5 days later) } content += ")"; var line = { buffer: buffer.id, date: new_date, prefix: '\u001943\u2500', tags_array: [], displayed: true, highlight: 0, message: content }; var new_message = new models.BufferLine(line); buffer.addLine(new_message); } }; var handleLine = function(line, manually) { var message = new models.BufferLine(line); var buffer = models.getBuffer(message.buffer); buffer.requestedLines++; // Only react to line if its displayed if (message.displayed) { // Check for date change if (buffer.lines.length > 0) { var old_date = new Date(buffer.lines[buffer.lines.length - 1].date), new_date = new Date(message.date); injectDateChangeMessageIfNeeded(buffer, manually, old_date, new_date); } message = plugins.PluginManager.contentForMessage(message); buffer.addLine(message); if (manually) { buffer.lastSeen++; } if (buffer.active && !manually) { $rootScope.scrollWithBuffer(); } if (!manually && (!buffer.active || !$rootScope.isWindowFocused())) { var server = models.getServerForBuffer(buffer); if (buffer.notify > 1 && _.contains(message.tags, 'notify_message') && !_.contains(message.tags, 'notify_none')) { buffer.unread++; server.unread++; $rootScope.$emit('notificationChanged'); } if ((buffer.notify !== 0) && (message.highlight || _.contains(message.tags, 'notify_private'))) { buffer.notification++; server.unread++; notifications.createHighlight(buffer, message); $rootScope.$emit('notificationChanged'); } } } }; var handleBufferInfo = function(message) { var bufferInfos = message.objects[0].content; // buffers objects for (var i = 0; i < bufferInfos.length ; i++) { var bufferId = bufferInfos[i].pointers[0]; var buffer = models.getBuffer(bufferId); if (buffer !== undefined) { // We already know this buffer handleBufferUpdate(buffer, bufferInfos[i]); } else { buffer = new models.Buffer(bufferInfos[i]); if (buffer.type === 'server') { models.registerServer(buffer); } else { var server = models.getServerForBuffer(buffer); server.unread += buffer.unread + buffer.notification; } models.addBuffer(buffer); // Switch to first buffer on startup var shouldResume = bufferResume.shouldResume(buffer); if(shouldResume){ models.setActiveBuffer(buffer.id); } } } // If there was no buffer to autmatically load, go to the first one. if (!bufferResume.wasAbleToResume()) { var first = bufferInfos[0].pointers[0]; models.setActiveBuffer(first); } }; var handleBufferUpdate = function(buffer, message) { if (message.pointers[0] !== buffer.id) { // this is information about some other buffer! return; } // weechat properties -- short name can be changed buffer.shortName = message.short_name; buffer.trimmedName = buffer.shortName.replace(/^[#&+]/, ''); buffer.title = message.title; buffer.number = message.number; buffer.hidden = message.hidden; // reset unread counts, hotlist info will arrive shortly var server = models.getServerForBuffer(buffer); server.unread -= (buffer.unread + buffer.notification); buffer.notification = 0; buffer.unread = 0; buffer.lastSeen = -1; if (message.local_variables.type !== undefined) { buffer.type = message.local_variables.type; buffer.indent = (['channel', 'private'].indexOf(buffer.type) >= 0); } if (message.notify !== undefined) { buffer.notify = message.notify; } }; var handleBufferLineAdded = function(message) { message.objects[0].content.forEach(function(l) { handleLine(l, false); }); }; var handleBufferOpened = function(message) { var bufferMessage = message.objects[0].content[0]; var buffer = new models.Buffer(bufferMessage); if (buffer.type === 'server') { models.registerServer(buffer); } else { var server = models.getServerForBuffer(buffer); server.unread += buffer.unread + buffer.notification; } models.addBuffer(buffer); }; var handleBufferTitleChanged = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; var old = models.getBuffer(buffer); old.fullName = obj.full_name; old.title = models.parseRichText(obj.title); old.number = obj.number; old.rtitle = ""; for (var i = 0; i < old.title.length; ++i) { old.rtitle += old.title[i].text; } }; var handleBufferRenamed = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; var old = models.getBuffer(buffer); old.fullName = obj.full_name; old.shortName = obj.short_name; // If it's a channel, trim away the prefix (#, &, or +). If that is empty and the buffer // has a short name, use a space (because the prefix will be displayed separately, and we don't want // prefix + fullname, which would happen otherwise). Else, use null so that full_name is used old.trimmedName = obj.short_name.replace(/^[#&+]/, '') || (obj.short_name ? ' ' : null); old.prefix = ['#', '&', '+'].indexOf(obj.short_name.charAt(0)) >= 0 ? obj.short_name.charAt(0) : ''; // After a buffer openes we get the name change event from relay protocol // Here we check our outgoing commands that openes a buffer and switch // to it if we find the buffer name it the list var position = models.outgoingQueries.indexOf(old.shortName); if (position >= 0) { models.outgoingQueries.splice(position, 1); models.setActiveBuffer(old.id); } }; var handleBufferMoved = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; var old = models.getBuffer(buffer); var old_number = old.number; var new_number = obj.number; _.each(models.getBuffers(), function(buffer) { if (buffer.number > old_number && buffer.number <= new_number) { buffer.number -= 1; } if (buffer.number < old_number && buffer.number >= new_number) { buffer.number += 1; } }); old.number = new_number; }; var handleBufferHidden = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; var old = models.getBuffer(buffer); old.hidden = true; }; var handleBufferUnhidden = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; var old = models.getBuffer(buffer); old.hidden = false; }; var handleBufferLocalvarChanged = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; var old = models.getBuffer(buffer); var localvars = obj.local_variables; if (old !== undefined && localvars !== undefined) { // Update indentation status old.type = localvars.type; old.indent = (['channel', 'private'].indexOf(localvars.type) >= 0); // Update serverSortKey and related variables old.plugin = localvars.plugin; old.server = localvars.server; old.serverSortKey = old.plugin + "." + old.server + (old.type === "server" ? "" : ("." + old.shortName)); old.pinned = localvars.pinned === "true"; } }; var handleBufferTypeChanged = function(message) { var obj = message.objects[0].content[0]; var buffer = obj.pointers[0]; //var old = models.getBuffer(buffer); // 0 = formatted (normal); 1 = free buffer.bufferType = obj.type; }; /* * Handle answers to (lineinfo) messages * * (lineinfo) messages are specified by this client. It is request after bufinfo completes */ var handleLineInfo = function(message, manually) { var lines = message.objects[0].content.reverse(); if (manually === undefined) { manually = true; } lines.forEach(function(l) { handleLine(l, manually); }); if (message.objects[0].content.length > 0) { // fiddle out the buffer ID and take the last line's date var last_line = message.objects[0].content[message.objects[0].content.length-1]; var buffer = models.getBuffer(last_line.buffer); if (buffer.lines.length > 0) { var last_date = new Date(buffer.lines[buffer.lines.length - 1].date); injectDateChangeMessageIfNeeded(buffer, true, last_date, new Date()); } } }; /* * Handle answers to hotlist request */ var handleHotlistInfo = function(message) { // Hotlist includes only buffers with unread counts so first we // iterate all our buffers and resets the counts. _.each(models.getBuffers(), function(buffer) { buffer.unread = 0; buffer.notification = 0; }); _.each(models.getServers(), function(server) { server.unread = 0; }); if (message.objects.length > 0) { var hotlist = message.objects[0].content; hotlist.forEach(function(l) { var buffer = models.getBuffer(l.buffer); // If buffer is active in gb, but not active in WeeChat the // hotlist in WeeChat will increase but we should ignore that // in gb. if (buffer.active) { return; } // 1 is message buffer.unread = l.count[1]; // 2 is private // Use += so count[2] or count[3] doesn't overwrite each other buffer.notification += l.count[2]; // 3 is highlight // Use += so count[2] or count[3] doesn't overwrite each other buffer.notification += l.count[3]; /* Since there is unread messages, we can guess * what the last read line is and update it accordingly */ var unreadSum = _.reduce(l.count, function(memo, num) { return memo + num; }, 0); buffer.lastSeen = buffer.lines.length - 1 - unreadSum; // update server buffer. Don't incude index 0 -> not unreadSum models.getServerForBuffer(buffer).unread += l.count[1] + l.count[2] + l.count[3]; }); } // the unread badges in the bufferlist doesn't update if we don't do this setTimeout(function() { $rootScope.$apply(); $rootScope.$emit('notificationChanged'); }); }; /* * Handle nicklist event * * This event can either fill or clear a nicklist. It is always a complete nicklist. */ var handleNicklist = function(message) { var nicklist = message.objects[0].content; var group = 'root'; //clear the nicklists in case we are clearing if (nicklist.length==1) { models.getBuffer(nicklist[0].pointers[0]).clearNicklist(); } //fill the nicklist nicklist.forEach(function(n) { var buffer = models.getBuffer(n.pointers[0]); //buffer nicklist if (n.group === 1) { var g = new models.NickGroup(n); group = g.name; buffer.nicklist[group] = g; } else { var nick = new models.Nick(n); buffer.addNick(group, nick); } }); //check if nicklist should be hidden or not $rootScope.$emit('nickListChanged'); }; /* * Handle nicklist diff event */ var handleNicklistDiff = function(message) { var nicklist = message.objects[0].content; var group; nicklist.forEach(function(n) { var buffer = models.getBuffer(n.pointers[0]); var d = n._diff; if (n.group === 1) { group = n.name; if (group === undefined) { var g = new models.NickGroup(n); buffer.nicklist[group] = g; group = g.name; } } else { var nick = new models.Nick(n); if (d === 43) { // + buffer.addNick(group, nick); } else if (d === 45) { // - buffer.delNick(group, nick); } else if (d === 42) { // * buffer.updateNick(group, nick); } } }); }; var eventHandlers = { _buffer_closing: handleBufferClosing, _buffer_line_added: handleBufferLineAdded, _buffer_localvar_added: handleBufferLocalvarChanged, _buffer_localvar_removed: handleBufferLocalvarChanged, _buffer_localvar_changed: handleBufferLocalvarChanged, _buffer_moved: handleBufferMoved, _buffer_opened: handleBufferOpened, _buffer_title_changed: handleBufferTitleChanged, _buffer_type_changed: handleBufferTypeChanged, _buffer_renamed: handleBufferRenamed, _buffer_hidden: handleBufferHidden, _buffer_unhidden: handleBufferUnhidden, _nicklist: handleNicklist, _nicklist_diff: handleNicklistDiff }; $rootScope.$on('onMessage', function(event, message) { if (_.has(eventHandlers, message.id)) { eventHandlers[message.id](message); } else { $log.debug('Unhandled event received: ' + message.id); } }); var handleEvent = function(event) { if (_.has(eventHandlers, event.id)) { eventHandlers[event.id](event); } }; return { handleVersionInfo: handleVersionInfo, handleConfValue: handleConfValue, handleEvent: handleEvent, handleLineInfo: handleLineInfo, handleHotlistInfo: handleHotlistInfo, handleNicklist: handleNicklist, handleBufferInfo: handleBufferInfo }; }]); })();