diff --git a/index.html b/index.html index e0880c0..6594bc9 100644 --- a/index.html +++ b/index.html @@ -50,7 +50,7 @@ {{ content.full_name }}
- {{ bufferline.timestamp | date: 'h:mm:ss'}} + {{ bufferline.date | date: 'H:mm:ss'}} {{ part.text }} diff --git a/js/websockets.js b/js/websockets.js index 6344fa9..d89748a 100644 --- a/js/websockets.js +++ b/js/websockets.js @@ -99,7 +99,7 @@ weechat.factory('colors', [function($scope) { return { - + setAttrs: setAttrs, getColor: getColor, prepareCss: prepareCss, @@ -118,7 +118,7 @@ weechat.factory('pluginManager', ['youtubePlugin', 'urlPlugin', 'imagePlugin', f } var contentForMessage = function(message) { - + var content = []; for (var i = 0; i < plugins.length; i++) { var pluginContent = plugins[i].contentForMessage(message); @@ -131,7 +131,7 @@ weechat.factory('pluginManager', ['youtubePlugin', 'urlPlugin', 'imagePlugin', f } } } - + return content; } @@ -204,7 +204,7 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ /* * Parse the text elements from the buffer line added - * + * */ function parseLineAddedTextElements(message) { var prefix = colors.parse(message['objects'][0]['content'][0]['prefix']); @@ -220,11 +220,11 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ return text_element; }); return text_elements; - } + } var buffer = message['objects'][0]['content'][0]['buffer']; - var timestamp = parseInt(message['objects'][0]['content'][0]['date']) * 1e3; + var date = message['objects'][0]['content'][0]['date']; var text = colors.parse(message['objects'][0]['content'][0]['message']); var content = parseLineAddedTextElements(message); var additionalContent = pluginManager.contentForMessage(text[0]['text']); @@ -232,7 +232,7 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ return { metadata: additionalContent, content: content, - timestamp: timestamp, + date: date, buffer: buffer } @@ -240,9 +240,9 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ var handleBufferLineAdded = function(message) { var buffer_line = {} - + message = new BufferLine(message); - + if (!_isActiveBuffer(message.buffer)) { $rootScope.buffers[message.buffer]['notification'] = true; } @@ -265,7 +265,7 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ var fullName = message['objects'][0]['content'][0]['full_name'] var buffer = message['objects'][0]['content'][0]['pointers'][0] $rootScope.buffers[buffer] = { 'id': buffer, 'lines':[], 'full_name':fullName } - + } /* @@ -292,7 +292,7 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ } $rootScope.buffers = buffers; } - + var handleEvent = function(event) { if (_.has(eventHandlers, event['id'])) { eventHandlers[event['id']](event); @@ -311,7 +311,7 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($ var eventHandlers = { bufinfo: handleBufferInfo, - _buffer_closing: handleBufferClosing, + _buffer_closing: handleBufferClosing, _buffer_line_added: handleBufferLineAdded, _buffer_opened: handleBufferOpened } @@ -328,7 +328,7 @@ weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', funct var websocket = null; - // Sanitizes messages to be sent to the weechat relay + // Sanitizes messages to be sent to the weechat relay var doSend = function(message) { msgs = message.replace(/[\r\n]+$/g, "").split("\n"); for (var i = 0; i < msgs.length; i++) { @@ -337,18 +337,25 @@ weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', funct } websocket.send(message); } - + // Takes care of the connection and websocket hooks - var connect = function (hostport, proto, password) { + var connect = function (hostport, proto, passwd) { websocket = new WebSocket("ws://" + hostport + "/weechat"); websocket.binaryType = "arraybuffer" websocket.onopen = function (evt) { // FIXME: does password need to be sent only if protocol is not weechat? if (proto == "weechat") { - if (password) { - doSend("init compression=off,password=" + password + "\n(bufinfo) hdata buffer:gui_buffers(*) full_name\nsync\n"); - } + doSend(WeeChatProtocol.formatInit({ + password: passwd, + compression: 'off' + })); + doSend(WeeChatProtocol.formatHdata({ + id: 'bufinfo', + path: 'buffer:gui_buffers(*)', + keys: ['full_name'] + })); + doSend(WeeChatProtocol.formatSync({})); } else { } @@ -381,8 +388,10 @@ weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', funct } var sendMessage = function(message) { - message = "input " + $rootScope.activeBuffer['full_name'] + " " + message + "\n" - doSend(message); + doSend(WeeChatProtocol.formatInput({ + buffer: $rootScope.activeBuffer['full_name'], + data: message + })); } return { diff --git a/js/weechat-protocol.js b/js/weechat-protocol.js index b2829ae..362763e 100644 --- a/js/weechat-protocol.js +++ b/js/weechat-protocol.js @@ -1,4 +1,12 @@ +/** + * WeeChat protocol handling. + * + * This object parses messages and formats commands for the WeeChat + * protocol. It's independent from the communication layer and thus + * may be used with any network mechanism. + */ var WeeChatProtocol = function() { + // specific parsing for each message type this._types = { 'chr': this._getChar, 'int': this._getInt, @@ -10,8 +18,13 @@ var WeeChatProtocol = function() { 'tim': this._getTime, 'buf': this._getString, 'arr': this._getArray, - 'htb': this._getHashTable + 'htb': this._getHashTable, + 'inl': function() { + this._warnUnimplemented('infolist'); + } }; + + // string value for some message types this._typesStr = { 'chr': this._strDirect, 'str': this._strDirect, @@ -20,6 +33,13 @@ var WeeChatProtocol = function() { 'ptr': this._strDirect }; }; + +/** + * Unsigned integer array to string. + * + * @param uia Unsigned integer array + * @return Decoded string + */ WeeChatProtocol._uia2s = function(uia) { var str = []; @@ -29,36 +49,352 @@ WeeChatProtocol._uia2s = function(uia) { return decodeURIComponent(escape(str.join(''))); }; + +/** + * Merges default parameters with overriding parameters. + * + * @param defaults Default parameters + * @param override Overriding parameters + * @return Merged parameters + */ +WeeChatProtocol._mergeParams = function(defaults, override) { + for (var v in override) { + defaults[v] = override[v]; + } + + return defaults; +} + +/** + * Formats a command. + * + * @param id Command ID (null for no ID) + * @param name Command name + * @param parts Command parts + * @return Formatted command string + */ +WeeChatProtocol._formatCmd = function(id, name, parts) { + var cmdIdName; + var cmd; + + cmdIdName = (id !== null) ? '(' + id + ') ' : ''; + cmdIdName += name; + parts.unshift(cmdIdName); + cmd = parts.join(' '); + cmd += '\n'; + + return cmd; +}; + +/** + * Formats an init command. + * + * @param params Parameters: + * password: password (optional) + * compression: compression ('off' or 'zlib') (optional) + * @return Formatted init command string + */ +WeeChatProtocol.formatInit = function(params) { + var defaultParams = { + password: null, + compression: 'off' + }; + var keys = []; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + keys.push('compression=' + params.compression); + if (params.password !== null) { + keys.push('password=' + params.password); + } + parts.push(keys.join(',')); + + return WeeChatProtocol._formatCmd(null, 'init', parts); +}; + +/** + * Formats an hdata command. + * + * @param params Parameters: + * id: command ID (optional) + * path: hdata path (mandatory) + * keys: array of keys (optional) + * @return Formatted hdata command string + */ +WeeChatProtocol.formatHdata = function(params) { + var defaultParams = { + id: null, + keys: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + parts.push(params.path); + if (params.keys !== null) { + parts.push(params.keys.join(',')); + } + + return WeeChatProtocol._formatCmd(params.id, 'hdata', parts); +}; + +/** + * Formats an info command. + * + * @param params Parameters: + * id: command ID (optional) + * name: info name (mandatory) + * @return Formatted info command string + */ +WeeChatProtocol.formatInfo = function(params) { + var defaultParams = { + id: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + parts.push(params.name); + + return WeeChatProtocol._formatCmd(params.id, 'info', parts); +}; + +/** + * Formats a nicklist command. + * + * @param params Parameters: + * id: command ID (optional) + * buffer: buffer name (optional) + * @return Formatted nicklist command string + */ +WeeChatProtocol.formatNicklist = function(params) { + var defaultParams = { + id: null, + buffer: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + if (params.buffer !== null) { + parts.push(params.buffer); + } + + return WeeChatProtocol._formatCmd(params.id, 'nicklist', parts); +}; + +/** + * Formats an input command. + * + * @param params Parameters: + * id: command ID (optional) + * buffer: target buffer (mandatory) + * data: input data (mandatory) + * @return Formatted input command string + */ +WeeChatProtocol.formatInput = function(params) { + var defaultParams = { + id: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + parts.push(params.buffer); + parts.push(params.data); + + return WeeChatProtocol._formatCmd(params.id, 'input', parts); +}; + +/** + * Formats a sync or a desync command. + * + * @param params Parameters (see _formatSync and _formatDesync) + * @return Formatted sync/desync command string + */ +WeeChatProtocol._formatSyncDesync = function(cmdName, params) { + var defaultParams = { + id: null, + buffers: null, + options: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + if (params.buffers !== null) { + parts.push(params.buffers.join(',')); + if (params.options !== null) { + parts.push(params.options.join(',')); + } + } + + return WeeChatProtocol._formatCmd(params.id, cmdName, parts); +} + +/** + * Formats a sync command. + * + * @param params Parameters: + * id: command ID (optional) + * buffers: array of buffers to sync (optional) + * options: array of options (optional) + * @return Formatted sync command string + */ +WeeChatProtocol.formatSync = function(params) { + return WeeChatProtocol._formatSyncDesync('sync', params); +}; + +/** + * Formats a desync command. + * + * @param params Parameters: + * id: command ID (optional) + * buffers: array of buffers to desync (optional) + * options: array of options (optional) + * @return Formatted desync command string + */ +WeeChatProtocol.formatDesync = function(params) { + return WeeChatProtocol._formatSyncDesync('desync', params); +}; + +/** + * Formats a test command. + * + * @param params Parameters: + * id: command ID (optional) + * @return Formatted test command string + */ +WeeChatProtocol.formatTest = function(params) { + var defaultParams = { + id: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + + return WeeChatProtocol._formatCmd(params.id, 'test', parts); +}; + +/** + * Formats a quit command. + * + * @return Formatted quit command string + */ +WeeChatProtocol.formatQuit = function() { + return WeeChatProtocol._formatCmd(null, 'quit', []); +}; + +/** + * Formats a ping command. + * + * @param params Parameters: + * id: command ID (optional) + * args: array of custom arguments (optional) + * @return Formatted ping command string + */ +WeeChatProtocol.formatPing = function(params) { + var defaultParams = { + id: null, + args: null + }; + var parts = []; + + params = WeeChatProtocol._mergeParams(defaultParams, params); + if (params.args !== null) { + parts.push(params.args.join(' ')); + } + + return WeeChatProtocol._formatCmd(params.id, 'ping', parts); +}; + WeeChatProtocol.prototype = { + /** + * Warns that message parsing is not implemented for a + * specific type. + * + * @param type Message type to display + */ + _warnUnimplemented: function(type) { + console.log('Warning: ' + type + ' message parsing is not implemented'); + }, + + /** + * Reads a 3-character message type token value from current + * set data. + * + * @return Type + */ _getType: function() { var t = this._getSlice(3); + if (!t) { + return null; + } + return WeeChatProtocol._uia2s(new Uint8Array(t)); }, + + /** + * Runs the appropriate read routine for the specified message type. + * + * @param type Message type + * @return Data value + */ _runType: function(type) { var cb = this._types[type]; var boundCb = cb.bind(this); return boundCb(); }, + + /** + * Reads a "number as a string" token value from current set data. + * + * @return Number as a string + */ _getStrNumber: function() { var len = this._getByte(); var str = this._getSlice(len); return WeeChatProtocol._uia2s(new Uint8Array(str)); }, + + /** + * Returns the passed object. + * + * @param obj Object + * @return Passed object + */ _strDirect: function(obj) { return obj; }, + + /** + * Calls toString() on the passed object and returns the value. + * + * @param obj Object to call toString() on + * @return String value of object + */ _strToString: function(obj) { return obj.toString(); }, + + /** + * Gets the string value of an object representing the message + * value for a specified type. + * + * @param obj Object for which to get the string value + * @param type Message type + * @return String value of object + */ _objToString: function(obj, type) { var cb = this._typesStr[type]; var boundCb = cb.bind(this); return boundCb(obj); }, + + /** + * Reads an info token value from current set data. + * + * @return Info object + */ _getInfo: function() { var info = {}; info.key = this._getString(); @@ -66,6 +402,12 @@ WeeChatProtocol.prototype = { return info; }, + + /** + * Reads an hdata token value from current set data. + * + * @return Hdata object + */ _getHdata: function() { var self = this; var paths; @@ -95,14 +437,32 @@ WeeChatProtocol.prototype = { return objs; }, + + /** + * Reads a pointer token value from current set data. + * + * @return Pointer value + */ _getPointer: function() { return this._getStrNumber(); }, + + /** + * Reads a time token value from current set data. + * + * @return Time value (Date) + */ _getTime: function() { var str = this._getStrNumber(); - return new Date(parseInt(str)); + return new Date(parseInt(str) * 1000); }, + + /** + * Reads an integer token value from current set data. + * + * @return Integer value + */ _getInt: function() { var parsedData = new Uint8Array(this._getSlice(4)); @@ -111,14 +471,32 @@ WeeChatProtocol.prototype = { ((parsedData[2] & 0xff) << 8) | (parsedData[3] & 0xff); }, + + /** + * Reads a byte from current set data. + * + * @return Byte value (integer) + */ _getByte: function() { var parsedData = new Uint8Array(this._getSlice(1)); return parsedData[0]; }, + + /** + * Reads a character token value from current set data. + * + * @return Character (string) + */ _getChar: function() { return String.fromCharCode(this._getByte()); }, + + /** + * Reads a string token value from current set data. + * + * @return String value + */ _getString: function() { var l = this._getInt(); @@ -131,6 +509,12 @@ WeeChatProtocol.prototype = { return ""; }, + + /** + * Reads a message header from current set data. + * + * @return Header object + */ _getHeader: function() { var len = this._getInt(); var comp = this._getByte(); @@ -140,9 +524,21 @@ WeeChatProtocol.prototype = { compression: comp, }; }, + + /** + * Reads a message header ID from current set data. + * + * @return Message ID (string) + */ _getId: function() { return this._getString(); }, + + /** + * Reads an arbitrary object token from current set data. + * + * @return Object value + */ _getObject: function() { var self = this; var type = this._getType(); @@ -154,6 +550,12 @@ WeeChatProtocol.prototype = { }; } }, + + /** + * Reads an hash table token from current set data. + * + * @return Hash table + */ _getHashTable: function() { var self = this; var typeKeys, typeValues, count; @@ -166,12 +568,18 @@ WeeChatProtocol.prototype = { for (var i = 0; i < count; ++i) { var key = self._runType(typeKeys); var keyStr = self._objToString(key, typeKeys); - var value = self.runType(typeValues); + var value = self._runType(typeValues); dict[keyStr] = value; } return dict; }, + + /** + * Reads an array token from current set data. + * + * @return Array + */ _getArray: function() { var self = this; var type; @@ -188,16 +596,40 @@ WeeChatProtocol.prototype = { return values; }, + + /** + * Reads a specified number of bytes from current set data. + * + * @param length Number of bytes to read + * @return Sliced array + */ _getSlice: function(length) { + if (this.dataAt + length > this._data.byteLength) { + return null; + } + var slice = this._data.slice(this._dataAt, this._dataAt + length); this._dataAt += length; return slice; }, + + /** + * Sets the current data. + * + * @param data Current data + */ _setData: function (data) { this._data = data; }, + + /** + * Parses a WeeChat message. + * + * @param data Message data (ArrayBuffer) + * @return Message value + */ parse: function(data) { var self = this;