Merge branch 'master' into gh-pages
This commit is contained in:
commit
b94edd0ff5
8 changed files with 1158 additions and 497 deletions
|
@ -1,6 +1,8 @@
|
|||
A web client for WeeChat
|
||||
========================
|
||||
|
||||
Required Weechat version: 0.4.2
|
||||
|
||||
To use the web interface you first need to set a relay and a password:
|
||||
|
||||
/relay add weechat 9001
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
html {
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background-color: #222;
|
||||
|
@ -8,6 +6,11 @@ body {
|
|||
padding-bottom:70px;
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
input#sendMessage {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.content {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
|
|
188
index.html
188
index.html
|
@ -1,126 +1,126 @@
|
|||
<!DOCTYPE html>
|
||||
<html ng-app="weechat" ng-cloak>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title ng-bind-template="WeeChat {{ pageTitle}}"></title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title ng-bind-template="WeeChat {{ pageTitle}}"></title>
|
||||
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
<link rel="shortcut icon" type="image/png" href="img/favicon.png" >
|
||||
<link href="css/glowingbear.css" rel="stylesheet" media="screen">
|
||||
<script type="text/javascript" src="js/angular.min.js"></script>
|
||||
<script type="text/javascript" src="js/underscore.js"></script>
|
||||
<script type="text/javascript" src="js/localstorage.js"></script>
|
||||
<script type="text/javascript" src="js/protocol.js"></script>
|
||||
<script type="text/javascript" src="js/weechat-protocol.js"></script>
|
||||
<script type="text/javascript" src="js/websockets.js"></script>
|
||||
<script type="text/javascript" src="js/models.js"></script>
|
||||
<script type="text/javascript" src="js/plugins.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div ng-controller="WeechatCtrl">
|
||||
<div ng-hide="connected" class="container">
|
||||
<h2>
|
||||
<img src="img/favicon.png">
|
||||
glowing bear
|
||||
<small>
|
||||
WeeChat web frontend
|
||||
</small>
|
||||
</h2>
|
||||
<div>To start using, please enable relay in your WeeChat client:
|
||||
<pre>
|
||||
<body ng-controller="WeechatCtrl">
|
||||
<nav ng-show="connected" class="navbar navbar-default navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav ">
|
||||
<li class="label" ng-class="{'active': content.active }" ng-repeat="(key, content) in buffers | toArray | orderBy:'content.number':true">
|
||||
<a href="#" ng-click="setActiveBuffer(content.id)" title="{{ content.fullName }}">{{ content.shortName }} <span class="badge" ng-class="{'danger': content.notification }" ng-bind="content.unread"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div ng-hide="connected" class="container">
|
||||
<h2>
|
||||
<img src="img/favicon.png">
|
||||
glowing bear
|
||||
<small>
|
||||
WeeChat web frontend
|
||||
</small>
|
||||
</h2>
|
||||
<div>To start using, please enable relay in your WeeChat client:
|
||||
<pre>
|
||||
/set relay.network.password yourpassword
|
||||
/relay add weechat 9001</pre>
|
||||
Note: The communication goes directly between your browser and your weechat in clear text.
|
||||
Connection settings are saved between sessions, including password, in your own browser.
|
||||
<h4>Encryption</h4>
|
||||
If you want to use encrypted session you first have to set up the relay using SSL
|
||||
<pre>
|
||||
Note: The communication goes directly between your browser and your weechat in clear text.
|
||||
Connection settings are saved between sessions, including password, in your own browser.
|
||||
<h4>Encryption</h4>
|
||||
If you want to use encrypted session you first have to set up the relay using SSL
|
||||
<pre>
|
||||
$ mkdir -p ~/.weechat/ssl
|
||||
$ cd ~/.weechat/ssl
|
||||
$ openssl req -nodes -newkey rsa:2048 -keyout relay.pem -x509 -days 365 -out relay.pem
|
||||
</pre>
|
||||
If WeeChat is already running, you can reload the certificate and private key with command:
|
||||
<pre>
|
||||
If WeeChat is already running, you can reload the certificate and private key with command:
|
||||
<pre>
|
||||
/relay sslcertkey
|
||||
/relay add ssl.weechat 8000
|
||||
</pre>
|
||||
</div>
|
||||
<h3>Connection settings</h3>
|
||||
<form role="form">
|
||||
<div class="alert alert-danger" ng-show="errorMessage">
|
||||
<strong>Oh no!</strong> We cannot connect!
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="hostport">Hostname and port</label>
|
||||
<input type="text" class="form-control" id="hostport" ng-model="hostport" placeholder="Hostport">
|
||||
<p class="help-block">Enter the hostname and the port to the WeeChat relay, separated by a :</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="password">WeeChat relay password</label>
|
||||
<input type="password"class="form-control" id="password" ng-model="password" placeholder="Password">
|
||||
<p class="help-block">Password will be stored in your browser session</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="proto">Encryption</label>
|
||||
<input type="checkbox" class="form-control" id="ssl" ng-model="ssl">
|
||||
<p class="help-block">Check the box if you want to encrypt communication between browser and WeeChat. <strong>Note</strong>: Due to a <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=594502">bug</a> encryption will not work in Firefox. You must also first visit the URL https://weechathost:relayport/ to accept the certificate</p>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary" ng-click="connect()">Connect!</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="content" ng-show="connected">
|
||||
<nav class="navbar navbar-default navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav ">
|
||||
<li class="label" ng-class="{'active': content.active }" ng-repeat="(key, content) in buffers | toArray | orderBy:'content.number':true">
|
||||
<a ng-click="setActiveBuffer(content.id)" title="{{ content.full_name }}">{{ content.short_name }} <span class="badge" ng-class="{'danger': content.highlight }" ng-bind="content.unread"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="bufferlines">
|
||||
<div class="bufferline" ng-repeat="bufferline in activeBuffer.lines">
|
||||
<span class="date text-muted">
|
||||
{{ bufferline.date | date:'HH:mm' }}
|
||||
</span>
|
||||
<h3>Connection settings</h3>
|
||||
<form role="form">
|
||||
<div class="alert alert-danger" ng-show="errorMessage">
|
||||
<strong>Oh no!</strong> We cannot connect!
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="hostport">Hostname and port</label>
|
||||
<input type="text" class="form-control" id="hostport" ng-model="hostport" placeholder="Hostport">
|
||||
<p class="help-block">Enter the hostname and the port to the WeeChat relay, separated by a :</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="password">WeeChat relay password</label>
|
||||
<input type="password" class="form-control" id="password" ng-model="password" placeholder="Password">
|
||||
<p class="help-block">Password will be stored in your browser session</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="proto">Encryption</label>
|
||||
<input type="checkbox" class="form-control" id="ssl" ng-model="ssl">
|
||||
<p class="help-block">Check the box if you want to encrypt communication between browser and WeeChat. <strong>Note</strong>: Due to a <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=594502">bug</a> encryption will not work in Firefox. You must also first visit the URL https://weechathost:relayport/ to accept the certificate</p>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary" ng-click="connect()">Connect!</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="content" ng-show="connected">
|
||||
<div class="bufferlines">
|
||||
<div class="bufferline" ng-repeat="bufferline in activeBuffer().lines">
|
||||
<span class="date text-muted">
|
||||
{{ bufferline.date | date:'HH:mm' }}
|
||||
</span>
|
||||
|
||||
<span ng-repeat="part in bufferline.message" class="text" style="{{ part.fg }}">
|
||||
{{ part.text }}
|
||||
</span>
|
||||
<span ng-repeat="part in bufferline.content" class="text" style="{{ part.fg }}">
|
||||
{{ part.text }}
|
||||
</span>
|
||||
|
||||
<div ng-repeat="metadata in bufferline.metadata">
|
||||
<div ng-show="metadata.visible">
|
||||
<a ng-click="metadata.visible = false">Hide additional content</a>
|
||||
<div ng-bind-html-unsafe="metadata.content"></div>
|
||||
|
||||
</div>
|
||||
<div ng-hide="metadata.visible">
|
||||
<a ng-click="metadata.visible = true">Show additional content</a>
|
||||
</div>
|
||||
<div ng-repeat="metadata in bufferline.metadata">
|
||||
<div ng-show="metadata.visible">
|
||||
<a ng-click="metadata.visible = false">Hide additional content</a>
|
||||
<div ng-bind-html-unsafe="metadata.content"></div>
|
||||
|
||||
</div>
|
||||
<div ng-hide="metadata.visible">
|
||||
<a ng-click="metadata.visible = true">Show additional content</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-bottom">
|
||||
<form class="form form-horizontal" ng-submit="sendMessage()">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" ng-model="command"></input>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-primary">Send</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer" ng-show="connected">
|
||||
<div class="navbar navbar-inverse navbar-fixed-bottom">
|
||||
<form class="form form-horizontal" ng-submit="sendMessage()">
|
||||
<div class="input-group">
|
||||
<input id="sendMessage" type="text" class="form-control" ng-model="command">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-primary">Send</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
183
js/models.js
Normal file
183
js/models.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* This file contains the weechat models and various
|
||||
* helper methods to work with them.
|
||||
*/
|
||||
var models = angular.module('weechatModels', []);
|
||||
|
||||
models.service('models', ['colors', function(colors) {
|
||||
/*
|
||||
* Buffer class
|
||||
*/
|
||||
this.Buffer = function(message) {
|
||||
// weechat properties
|
||||
var fullName = message['full_name']
|
||||
var shortName = message['short_name']
|
||||
var title = message['title']
|
||||
var number = message['number']
|
||||
var pointer = message['pointers'][0]
|
||||
var lines = []
|
||||
var active = false;
|
||||
var notification = false;
|
||||
var unread = '';
|
||||
|
||||
/*
|
||||
* Adds a line to this buffer
|
||||
*
|
||||
* @param line the BufferLine object
|
||||
* @return undefined
|
||||
*/
|
||||
var addLine = function(line) {
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
return {
|
||||
id: pointer,
|
||||
fullName: fullName,
|
||||
shortName: shortName,
|
||||
number: number,
|
||||
title: title,
|
||||
lines: lines,
|
||||
addLine: addLine
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* BufferLine class
|
||||
*/
|
||||
this.BufferLine = function(message) {
|
||||
|
||||
/*
|
||||
* Parse the text elements from the buffer line added
|
||||
*
|
||||
* @param message weechat message
|
||||
*/
|
||||
function parseLineAddedTextElements(message) {
|
||||
var prefix = colors.parse(message['prefix']);
|
||||
|
||||
var buffer = message['buffer'];
|
||||
text_elements = _.union(prefix, text);
|
||||
text_elements =_.map(text_elements, function(text_element) {
|
||||
if (text_element && ('fg' in text_element)) {
|
||||
text_element['fg'] = colors.prepareCss(text_element['fg']);
|
||||
}
|
||||
// TODO: parse background as well
|
||||
|
||||
return text_element;
|
||||
});
|
||||
return text_elements;
|
||||
}
|
||||
|
||||
|
||||
var buffer = message['buffer'];
|
||||
var date = message['date'];
|
||||
var text = colors.parse(message['message']);
|
||||
var tags_array = message['tags_array'];
|
||||
var displayed = message['displayed'];
|
||||
var highlight = message['highlight'];
|
||||
var content = parseLineAddedTextElements(message);
|
||||
var text = "";
|
||||
if(text[0] != undefined) {
|
||||
text = text[0]['text'];
|
||||
}
|
||||
|
||||
return {
|
||||
content: content,
|
||||
date: date,
|
||||
buffer: buffer,
|
||||
tags: tags_array,
|
||||
highlight: highlight,
|
||||
displayed: displayed,
|
||||
text: text,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
var BufferList = []
|
||||
activeBuffer = null;
|
||||
|
||||
this.model = { 'buffers': {} }
|
||||
|
||||
/*
|
||||
* Adds a buffer to the list
|
||||
*
|
||||
* @param buffer buffer object
|
||||
* @return undefined
|
||||
*/
|
||||
this.addBuffer = function(buffer) {
|
||||
BufferList[buffer.id] = buffer;
|
||||
if (BufferList.length == 1) {
|
||||
activeBuffer = buffer.id;
|
||||
}
|
||||
this.model.buffers[buffer.id] = buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the current active buffer
|
||||
*
|
||||
* @return active buffer object
|
||||
*/
|
||||
this.getActiveBuffer = function() {
|
||||
return activeBuffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the buffer specifiee by bufferId as active.
|
||||
* Deactivates the previous current buffer.
|
||||
*
|
||||
* @param bufferId id of the new active buffer
|
||||
* @return undefined
|
||||
*/
|
||||
this.setActiveBuffer = function(bufferId) {
|
||||
|
||||
if (this.getActiveBuffer()) {
|
||||
this.getActiveBuffer().active = false;
|
||||
}
|
||||
|
||||
activeBuffer = _.find(this.model['buffers'], function(buffer) {
|
||||
if (buffer['id'] == bufferId) {
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
activeBuffer.notification = false;
|
||||
activeBuffer.active = true;
|
||||
activeBuffer.unread = '';
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the buffer list
|
||||
*/
|
||||
this.getBuffers = function() {
|
||||
return BufferList;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a specific buffer object
|
||||
*
|
||||
* @param bufferId id of the buffer
|
||||
* @return the buffer object
|
||||
*/
|
||||
this.getBuffer = function(bufferId) {
|
||||
return _.find(this.model['buffers'], function(buffer) {
|
||||
if (buffer['id'] == bufferId) {
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Closes a weechat buffer. Sets the first buffer
|
||||
* as active.
|
||||
*
|
||||
* @param bufferId id of the buffer to close
|
||||
* @return undefined
|
||||
*/
|
||||
this.closeBuffer = function(bufferId) {
|
||||
|
||||
delete(this.model['buffers'][bufferId.id]);
|
||||
var firstBuffer = _.keys(this.model['buffers'])[0];
|
||||
this.setActiveBuffer(firstBuffer);
|
||||
}
|
||||
}]);
|
132
js/plugins.js
Normal file
132
js/plugins.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* This file contains the plugin definitions
|
||||
*/
|
||||
|
||||
plugins = angular.module('plugins', []);
|
||||
|
||||
/*
|
||||
* Definition of a user provided plugin with sensible default values
|
||||
*
|
||||
* User plugins are created by providing a contentForMessage function
|
||||
* that parses a string and return any additional content.
|
||||
*/
|
||||
var Plugin = function(contentForMessage) {
|
||||
|
||||
return {
|
||||
contentForMessage: contentForMessage,
|
||||
exclusive: false,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This service provides access to the plugin manager
|
||||
*
|
||||
* The plugin manager is where the various user provided plugins
|
||||
* are registered. It is responsible for finding additional content
|
||||
* to display when messages are received.
|
||||
*
|
||||
*/
|
||||
plugins.service('plugins', ['userPlugins', function(userPlugins) {
|
||||
|
||||
/*
|
||||
* Defines the plugin manager object
|
||||
*/
|
||||
var PluginManagerObject = function() {
|
||||
|
||||
var plugins = [];
|
||||
|
||||
/*
|
||||
* Register the user provides plugins
|
||||
*
|
||||
* @param userPlugins user provided plugins
|
||||
*/
|
||||
var registerPlugins = function(userPlugins) {
|
||||
for (var i = 0; i < userPlugins.length; i++) {
|
||||
plugins.push(userPlugins[i]);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterates through all the registered plugins
|
||||
* and run their contentForMessage function.
|
||||
*/
|
||||
var contentForMessage = function(message) {
|
||||
|
||||
var content = [];
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var pluginContent = plugins[i].contentForMessage(message);
|
||||
if (pluginContent) {
|
||||
var pluginContent = {'visible': false,
|
||||
'content': pluginContent }
|
||||
content.push(pluginContent);
|
||||
|
||||
if (plugins[i].exclusive) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
return {
|
||||
registerPlugins: registerPlugins,
|
||||
contentForMessage: contentForMessage
|
||||
}
|
||||
}
|
||||
|
||||
// Instanciates and registers the plugin manager.
|
||||
this.PluginManager = new PluginManagerObject();
|
||||
this.PluginManager.registerPlugins(userPlugins.plugins);
|
||||
|
||||
}]);
|
||||
|
||||
/*
|
||||
* This factory exposes the collection of user provided plugins.
|
||||
*
|
||||
* To create your own plugin, you need to:
|
||||
*
|
||||
* 1. Define it's contentForMessage function. The contentForMessage
|
||||
* function takes a string as a parameter and returns a HTML string.
|
||||
*
|
||||
* 2. Instanciate a Plugin object with contentForMessage function as it's
|
||||
* argument.
|
||||
*
|
||||
* 3. Add it to the plugins array.
|
||||
*
|
||||
*/
|
||||
plugins.factory('userPlugins', function() {
|
||||
|
||||
var youtubePlugin = new Plugin(function(message) {
|
||||
|
||||
if (message.indexOf('youtube.com') != -1) {
|
||||
var index = message.indexOf("?v=");
|
||||
var token = message.substr(index+3);
|
||||
return '<iframe width="560" height="315" src="http://www.youtube.com/embed/' + token + '" frameborder="0" allowfullscreen></iframe>'
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
var urlPlugin = new Plugin(function(message) {
|
||||
var urlPattern = /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/;
|
||||
var url = message.match(urlPattern);
|
||||
if (url) {
|
||||
return '<a href="' + url[0] + '">' + message + '</a>';
|
||||
}
|
||||
return null;
|
||||
|
||||
});
|
||||
|
||||
var imagePlugin = new Plugin(function(message) {
|
||||
var urls = message.match(/https?:\/\/[^\s]*\.(jpg|png|gif)\b/)
|
||||
if (urls != null) {
|
||||
var url = urls[0]; /* Actually parse one url per message */
|
||||
return '<img src="' + url + '" height="300">';
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return {
|
||||
plugins: [youtubePlugin, urlPlugin, imagePlugin]
|
||||
}
|
||||
});
|
180
js/protocol.js
180
js/protocol.js
|
@ -1,180 +0,0 @@
|
|||
var Protocol = function() {
|
||||
var self = this;
|
||||
|
||||
var getInfo = function() {
|
||||
var info = {};
|
||||
info.key = getString();
|
||||
info.value = getString();
|
||||
return info;
|
||||
};
|
||||
|
||||
var getHdata = function() {
|
||||
var paths;
|
||||
var count;
|
||||
var objs = [];
|
||||
var hpath = getString();
|
||||
|
||||
|
||||
|
||||
keys = getString().split(',');
|
||||
paths = hpath.split('/');
|
||||
count = getInt();
|
||||
|
||||
keys = keys.map(function(key) {
|
||||
return key.split(':');
|
||||
});
|
||||
var i;
|
||||
for (i = 0; i < count; i++) {
|
||||
var tmp = {};
|
||||
|
||||
tmp.pointers = paths.map(function(path) {
|
||||
return getPointer();
|
||||
});
|
||||
|
||||
keys.forEach(function(key) {
|
||||
tmp[key[0]] = runType(key[1]);
|
||||
});
|
||||
objs.push(tmp);
|
||||
};
|
||||
return objs;
|
||||
};
|
||||
|
||||
function getPointer() {
|
||||
var l = getChar();
|
||||
|
||||
var pointer = getSlice(l)
|
||||
var parsed_data = new Uint8Array(pointer);
|
||||
return _uiatos(parsed_data);
|
||||
|
||||
};
|
||||
|
||||
var _uiatos =function(uia) {
|
||||
var _str = [];
|
||||
for (var c = 0; c < uia.length; c++) {
|
||||
_str[c] = String.fromCharCode(uia[c]);
|
||||
}
|
||||
return decodeURIComponent(escape(_str.join("")));
|
||||
};
|
||||
|
||||
var getInt = function() {
|
||||
var parsed_data = new Uint8Array(getSlice(4));
|
||||
var i = ((parsed_data[0] & 0xff) << 24) | ((parsed_data[1] & 0xff) << 16) | ((parsed_data[2] & 0xff) << 8) | (parsed_data[3] & 0xff);
|
||||
return i;
|
||||
|
||||
};
|
||||
|
||||
var getChar = function() {
|
||||
var parsed_data = new Uint8Array(getSlice(1));
|
||||
return parsed_data[0];
|
||||
};
|
||||
|
||||
var getString = function() {
|
||||
var l = getInt();
|
||||
if (l > 0) {
|
||||
var s = getSlice(l);
|
||||
var parsed_data = new Uint8Array(s);
|
||||
return _uiatos(parsed_data);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
var getSlice = function(length) {
|
||||
var slice = self.data.slice(0,length);
|
||||
self.data = self.data.slice(length);
|
||||
return slice;
|
||||
};
|
||||
|
||||
var getType = function() {
|
||||
var t = getSlice(3);
|
||||
return _uiatos(new Uint8Array(t));
|
||||
};
|
||||
|
||||
var runType = function(type) {
|
||||
if (type in types) {
|
||||
return types[type]();
|
||||
}
|
||||
0;
|
||||
};
|
||||
|
||||
var getHeader = function() {
|
||||
return {
|
||||
length: getInt(),
|
||||
compression: getChar(),
|
||||
}
|
||||
};
|
||||
|
||||
var getId = function() {
|
||||
return getString();
|
||||
}
|
||||
|
||||
var getObject = function() {
|
||||
var type = getType();
|
||||
if (type) {
|
||||
return object = {
|
||||
type: type,
|
||||
content: runType(type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.parse = function(data) {
|
||||
self.setData(data);
|
||||
var header = getHeader();
|
||||
var id = getId();
|
||||
var objects = [];
|
||||
var object = getObject();
|
||||
while(object) {
|
||||
objects.push(object);
|
||||
object = getObject();
|
||||
}
|
||||
return {
|
||||
header: header,
|
||||
id: id,
|
||||
objects: objects,
|
||||
}
|
||||
}
|
||||
|
||||
self.setData = function (data) {
|
||||
self.data = data;
|
||||
};
|
||||
|
||||
function array() {
|
||||
var type;
|
||||
var count;
|
||||
var values;
|
||||
|
||||
type = getType();
|
||||
count = getInt();
|
||||
values = [];
|
||||
var i;
|
||||
for (i = 0; i < count; i++) {
|
||||
values.push(runType(type));
|
||||
};
|
||||
return values;
|
||||
}
|
||||
|
||||
var types = {
|
||||
chr: getChar,
|
||||
"int": getInt,
|
||||
str: getString,
|
||||
inf: getInfo,
|
||||
hda: getHdata,
|
||||
ptr: getPointer,
|
||||
lon: getPointer,
|
||||
tim: getPointer,
|
||||
buf: getString,
|
||||
arr: array
|
||||
};
|
||||
//TODO: IMPLEMENT THIS STUFF
|
||||
// chr: this.getChar,
|
||||
// 'int': getInt,
|
||||
// hacks
|
||||
|
||||
// hacks
|
||||
// htb: getHashtable,
|
||||
// inf: Protocol.getInfo,
|
||||
// inl: getInfolist,
|
||||
|
||||
// },
|
||||
|
||||
}
|
308
js/websockets.js
308
js/websockets.js
|
@ -1,4 +1,5 @@
|
|||
var weechat = angular.module('weechat', ['localStorage']);
|
||||
var weechat = angular.module('weechat', ['localStorage', 'weechatModels', 'plugins']);
|
||||
|
||||
weechat.filter('toArray', function () {
|
||||
'use strict';
|
||||
|
||||
|
@ -167,7 +168,7 @@ weechat.factory('colors', [function($scope) {
|
|||
|
||||
|
||||
return {
|
||||
|
||||
|
||||
setAttrs: setAttrs,
|
||||
getColor: getColor,
|
||||
prepareCss: prepareCss,
|
||||
|
@ -177,142 +178,39 @@ weechat.factory('colors', [function($scope) {
|
|||
|
||||
}]);
|
||||
|
||||
weechat.factory('pluginManager', ['youtubePlugin', 'urlPlugin', 'imagePlugin', function(youtubePlugin, urlPlugin, imagePlugin) {
|
||||
|
||||
var plugins = [youtubePlugin, urlPlugin, imagePlugin]
|
||||
|
||||
var hookPlugin = function(plugin) {
|
||||
plugins.push(plugin);
|
||||
}
|
||||
|
||||
var contentForMessage = function(message) {
|
||||
|
||||
console.log('Message: ', message);
|
||||
var content = [];
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var pluginContent = plugins[i].contentForMessage(message);
|
||||
if (pluginContent) {
|
||||
var pluginContent = {'visible': false, 'content': pluginContent }
|
||||
content.push(pluginContent);
|
||||
|
||||
if (plugins[i].exclusive) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Content: ', content);
|
||||
return content;
|
||||
}
|
||||
|
||||
return {
|
||||
hookPlugin: hookPlugin,
|
||||
contentForMessage: contentForMessage
|
||||
}
|
||||
|
||||
}]);
|
||||
|
||||
weechat.factory('youtubePlugin', [function() {
|
||||
|
||||
var contentForMessage = function(message) {
|
||||
if (message.indexOf('youtube.com') != -1) {
|
||||
var index = message.indexOf("?v=");
|
||||
var token = message.substr(index+3);
|
||||
return '<iframe width="560" height="315" src="http://www.youtube.com/embed/' + token + '" frameborder="0" allowfullscreen></iframe>'
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
contentForMessage: contentForMessage,
|
||||
exclusive: true
|
||||
}
|
||||
|
||||
}]);
|
||||
|
||||
weechat.factory('urlPlugin', [function() {
|
||||
var contentForMessage = function(message) {
|
||||
var urlPattern = /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/;
|
||||
var url = message.match(urlPattern);
|
||||
if (url) {
|
||||
return '<a target="_blank" href="' + url[0] + '">' + message + '</a>';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
contentForMessage: contentForMessage,
|
||||
exclusive: false
|
||||
}
|
||||
}]);
|
||||
|
||||
weechat.factory('imagePlugin', [function() {
|
||||
var contentForMessage = function(message) {
|
||||
var urls = message.match(/https?:\/\/[^\s]*\.(jpg|png|gif)\b/)
|
||||
if (urls != null) {
|
||||
var url = urls[0]; /* Actually parse one url per message */
|
||||
return '<img src="' + url + '" height="300">';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
contentForMessage: contentForMessage
|
||||
}
|
||||
}]);
|
||||
|
||||
weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($rootScope, colors, pluginManager) {
|
||||
weechat.factory('handlers', ['$rootScope', 'colors', 'models', 'plugins', function($rootScope, colors, models, plugins) {
|
||||
|
||||
var handleBufferClosing = function(message) {
|
||||
var buffer_pointer = message['objects'][0]['content'][0]['pointers'][0];
|
||||
$rootScope.closeBuffer(buffer_pointer);
|
||||
var bufferMessage = message['objects'][0]['content'][0];
|
||||
var buffer = new models.Buffer(bufferMessage);
|
||||
models.closeBuffer(buffer);
|
||||
}
|
||||
|
||||
var handleLine = function(line, initial) {
|
||||
var buffer_line = {}
|
||||
var date = line['date']*1000;
|
||||
var prefix = colors.parse(line['prefix']);
|
||||
var text = colors.parse(line['message']);
|
||||
var buffer = line['buffer'];
|
||||
var tags_array = line['tags_array'];
|
||||
var displayed = line['displayed'];
|
||||
var highlight = line['highlight'];
|
||||
var message = _.union(prefix, text);
|
||||
message =_.map(message, function(message) {
|
||||
if (message != "" && 'fg' in message) {
|
||||
message['fg'] = colors.prepareCss(message['fg']);
|
||||
}
|
||||
return message;
|
||||
});
|
||||
var message = new models.BufferLine(line);
|
||||
// Only react to line if its displayed
|
||||
if (displayed) {
|
||||
buffer_line['message'] = message;
|
||||
if(message.displayed) {
|
||||
var buffer = models.getBuffer(message.buffer);
|
||||
message.metadata = plugins.PluginManager.contentForMessage(message.text);
|
||||
buffer.addLine(message);
|
||||
|
||||
if (buffer.active) {
|
||||
$rootScope.scrollToBottom();
|
||||
}
|
||||
|
||||
if (!_isActiveBuffer(buffer) && !initial && !_.contains(tags_array, 'notify_none')) {
|
||||
|
||||
if ($rootScope.buffers[buffer]['unread'] == '') {
|
||||
$rootScope.buffers[buffer]['unread'] = 1;
|
||||
}else {
|
||||
$rootScope.buffers[buffer]['unread'] = parseInt($rootScope.buffers[buffer]['unread']) + 1;
|
||||
if (!initial) {
|
||||
if (!buffer.active && _.contains(message.tags, 'notify_message') && !_.contains(message.tags, 'notify_none')) {
|
||||
if (buffer.unread == '' || buffer.unread == undefined) {
|
||||
buffer.unread = 1;
|
||||
}else {
|
||||
buffer.unread++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text[0] != undefined) {
|
||||
var additionalContent = pluginManager.contentForMessage(text[0]['text']);
|
||||
|
||||
if (additionalContent) {
|
||||
buffer_line['metadata'] = additionalContent;
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.addLine(buffer, buffer_line);
|
||||
|
||||
buffer_line['date'] = date;
|
||||
|
||||
if(!initial && (highlight || _.contains(tags_array, 'notify_private')) ) {
|
||||
$rootScope.createHighlight(prefix, text, message, buffer, additionalContent);
|
||||
$rootScope.buffers[buffer]['highlight'] = true;
|
||||
if(message.highlight || _.contains(message.tags, 'notify_private') ) {
|
||||
$rootScope.createHighlight(buffer, message);
|
||||
buffer.notification = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -323,26 +221,10 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($
|
|||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether or not this buffer is the active buffer
|
||||
*/
|
||||
var _isActiveBuffer = function(buffer) {
|
||||
if ($rootScope.activeBuffer['id'] == buffer) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var handleBufferOpened = function(message) {
|
||||
var obj = message['objects'][0]['content'][0];
|
||||
var fullName = obj['full_name'];
|
||||
var buffer = obj['pointers'][0];
|
||||
var short_name = obj['short_name'];
|
||||
var title = obj['title'];
|
||||
|
||||
$rootScope.buffers[buffer] = { 'id': buffer, 'lines':[], 'full_name':fullName, 'short_name':short_name, 'title':title, 'unread':'' }
|
||||
|
||||
var bufferMessage = message['objects'][0]['content'][0];
|
||||
var buffer = new models.Buffer(bufferMessage);
|
||||
models.addBuffer(buffer);
|
||||
}
|
||||
|
||||
var handleBufferTitleChanged = function(message) {
|
||||
|
@ -371,21 +253,10 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($
|
|||
// buffer info from message
|
||||
var bufferInfos = message['objects'][0]['content'];
|
||||
// buffers objects
|
||||
var buffers = {};
|
||||
for (var i = 0; i < bufferInfos.length ; i++) {
|
||||
var bufferInfo = bufferInfos[i];
|
||||
var pointer = bufferInfo['pointers'][0];
|
||||
bufferInfo['id'] = pointer;
|
||||
bufferInfo['lines'] = [];
|
||||
bufferInfo['unread'] = '';
|
||||
buffers[pointer] = bufferInfo
|
||||
if (i == 0) {
|
||||
// first buffer is active buffer by default
|
||||
$rootScope.activeBuffer = buffers[pointer];
|
||||
$rootScope.activeBuffer['active'] = true;
|
||||
}
|
||||
var buffer = new models.Buffer(bufferInfos[i]);
|
||||
models.addBuffer(buffer);
|
||||
}
|
||||
$rootScope.buffers = buffers;
|
||||
|
||||
// Request latest buffer lines for each buffer
|
||||
$rootScope.getLines();
|
||||
|
@ -403,7 +274,7 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($
|
|||
handleLine(l, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var handleEvent = function(event) {
|
||||
if (_.has(eventHandlers, event['id'])) {
|
||||
eventHandlers[event['id']](event);
|
||||
|
@ -411,15 +282,6 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($
|
|||
|
||||
}
|
||||
|
||||
var findMetaData = function(message) {
|
||||
if (message.indexOf('youtube.com') != -1) {
|
||||
var index = message.indexOf("?v=");
|
||||
var token = message.substr(index+3);
|
||||
return '<iframe width="560" height="315" src="http://www.youtube.com/embed/' + token + '" frameborder="0" allowfullscreen></iframe>'
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var eventHandlers = {
|
||||
bufinfo: handleBufferInfo,
|
||||
lineinfo: handleLineInfo,
|
||||
|
@ -437,12 +299,12 @@ weechat.factory('handlers', ['$rootScope', 'colors', 'pluginManager', function($
|
|||
|
||||
}]);
|
||||
|
||||
weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', function($rootScope, $log, handlers, colors) {
|
||||
protocol = new Protocol();
|
||||
weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', 'models', function($rootScope, $log, handlers, colors, models) {
|
||||
protocol = new WeeChatProtocol();
|
||||
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++) {
|
||||
|
@ -451,23 +313,26 @@ weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', funct
|
|||
}
|
||||
websocket.send(message);
|
||||
}
|
||||
|
||||
|
||||
// Takes care of the connection and websocket hooks
|
||||
var connect = function (hostport, password, ssl) {
|
||||
var connect = function (hostport, passwd, ssl) {
|
||||
var proto = ssl ? 'wss':'ws';
|
||||
websocket = new WebSocket(proto+"://" + hostport + "/weechat");
|
||||
websocket.binaryType = "arraybuffer"
|
||||
|
||||
websocket.onopen = function (evt) {
|
||||
var send = "";
|
||||
if (password) {
|
||||
send += "init compression=off,password=" + password + "\n";
|
||||
}
|
||||
doSend(WeeChatProtocol.formatInit({
|
||||
password: passwd,
|
||||
compression: 'off'
|
||||
}));
|
||||
doSend(WeeChatProtocol.formatHdata({
|
||||
id: 'bufinfo',
|
||||
path: 'buffer:gui_buffers(*)',
|
||||
keys: ['number,full_name,short_name,title']
|
||||
}));
|
||||
doSend(WeeChatProtocol.formatSync({}));
|
||||
|
||||
send += "(bufinfo) hdata buffer:gui_buffers(*) number,full_name,short_name,title\n";
|
||||
send += "sync\n";
|
||||
$log.info("Connected to relay");
|
||||
doSend(send);
|
||||
$rootScope.connected = true;
|
||||
$rootScope.$apply();
|
||||
}
|
||||
|
@ -496,13 +361,18 @@ 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: models.getActiveBuffer()['fullName'],
|
||||
data: message
|
||||
}));
|
||||
}
|
||||
|
||||
var getLines = function(count) {
|
||||
var message = "(lineinfo) hdata buffer:gui_buffers(*)/own_lines/last_line(-"+count+")/data\n";
|
||||
doSend(message)
|
||||
doSend(WeeChatProtocol.formatHdata({
|
||||
id: 'lineinfo',
|
||||
path: "buffer:gui_buffers(*)/own_lines/last_line(-"+count+")/data",
|
||||
keys: []
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -513,8 +383,8 @@ weechat.factory('connection', ['$rootScope', '$log', 'handlers', 'colors', funct
|
|||
}
|
||||
}]);
|
||||
|
||||
weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', 'connection', function ($rootScope, $scope, $store, connection) {
|
||||
|
||||
weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', 'models', 'connection', function ($rootScope, $scope, $store, models, connection, testService) {
|
||||
|
||||
// Request notification permission
|
||||
Notification.requestPermission(function (status) {
|
||||
console.log('Notification permission status:',status);
|
||||
|
@ -529,30 +399,38 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', 'connection
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
$scope.buffers = models.model.buffers;
|
||||
$scope.activeBuffer = models.getActiveBuffer
|
||||
|
||||
$scope.incrementAge = function () {
|
||||
models.model.age++;
|
||||
models.model.cats.push('nouveau chat');
|
||||
}
|
||||
|
||||
|
||||
$scope.clickS = function () {
|
||||
$scope.countS = testService.incrementCount();
|
||||
};
|
||||
|
||||
$rootScope.commands = []
|
||||
|
||||
$rootScope.models = models;
|
||||
|
||||
$rootScope.buffer = []
|
||||
$rootScope.buffers = {}
|
||||
$rootScope.activeBuffer = null;
|
||||
|
||||
$store.bind($scope, "hostport", "localhost:9001");
|
||||
$store.bind($scope, "proto", "weechat");
|
||||
$store.bind($scope, "password", "");
|
||||
$store.bind($scope, "ssl", false);
|
||||
// TODO checkbox for saving password or not?
|
||||
// $scope.password = "";
|
||||
|
||||
$rootScope.closeBuffer = function(buffer_pointer) {
|
||||
delete($rootScope.buffers[buffer_pointer]);
|
||||
var first_buffer = _.keys($rootScope.buffers)[0];
|
||||
$scope.setActiveBuffer(first_buffer);
|
||||
}
|
||||
|
||||
$scope.setActiveBuffer = function(key) {
|
||||
$rootScope.activeBuffer['active'] = false;
|
||||
$rootScope.buffers[key]['active'] = true;
|
||||
$rootScope.buffers[key]['highlight'] = false;
|
||||
$rootScope.buffers[key]['unread'] = '';
|
||||
$rootScope.activeBuffer = $rootScope.buffers[key];
|
||||
$rootScope.pageTitle = $rootScope.activeBuffer['short_name'] + ' | ' + $rootScope.activeBuffer['title'];
|
||||
models.setActiveBuffer(key);
|
||||
var ab = models.getActiveBuffer();
|
||||
$rootScope.pageTitle = ab.shortName + ' | ' + ab.title;
|
||||
$rootScope.scrollToBottom();
|
||||
};
|
||||
|
||||
|
@ -562,14 +440,6 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', 'connection
|
|||
}
|
||||
});
|
||||
|
||||
$rootScope.addLine = function(buffer, line) {
|
||||
$rootScope.buffers[buffer]['lines'].push(line);
|
||||
// Scroll if needed
|
||||
if ($rootScope.activeBuffer['id'] == buffer) {
|
||||
$rootScope.scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.scrollToBottom = function() {
|
||||
setTimeout(function() {
|
||||
window.scrollTo(0, window.scrollMaxY);
|
||||
|
@ -590,22 +460,18 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', 'connection
|
|||
}
|
||||
|
||||
/* Function gets called from bufferLineAdded code if user should be notified */
|
||||
$rootScope.createHighlight = function(prefix, text, message, buffer, additionalContent) {
|
||||
var prefixs = "";
|
||||
prefixs += prefix[0].text;
|
||||
if(prefix[1] != undefined) {
|
||||
prefixs += prefix[1].text;
|
||||
}
|
||||
$rootScope.createHighlight = function(buffer, message) {
|
||||
var messages = "";
|
||||
messages += text[0].text;
|
||||
message.content.forEach(function(part) {
|
||||
if (part.text != undefined)
|
||||
messages += part.text + " ";
|
||||
});
|
||||
|
||||
var buffers = $rootScope.buffers[buffer];
|
||||
|
||||
var title = buffers.full_name;
|
||||
var content = "<"+prefixs+">"+messages;
|
||||
var title = buffer.fullName;
|
||||
var content = messages;
|
||||
|
||||
var timeout = 15*1000;
|
||||
console.log('Displaying notification:',title,',with timeout:',timeout);
|
||||
console.log('Displaying notification:buffer:',buffer,',message:',message,',with timeout:',timeout);
|
||||
var notification = new Notification(title, {body:content, icon:'img/favicon.png'});
|
||||
// Cancel notification automatically
|
||||
notification.onshow = function() {
|
||||
|
|
655
js/weechat-protocol.js
Normal file
655
js/weechat-protocol.js
Normal file
|
@ -0,0 +1,655 @@
|
|||
/**
|
||||
* 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,
|
||||
'str': this._getString,
|
||||
'inf': this._getInfo,
|
||||
'hda': this._getHdata,
|
||||
'ptr': this._getPointer,
|
||||
'lon': this._getStrNumber,
|
||||
'tim': this._getTime,
|
||||
'buf': this._getString,
|
||||
'arr': this._getArray,
|
||||
'htb': this._getHashTable,
|
||||
'inl': function() {
|
||||
this._warnUnimplemented('infolist');
|
||||
}
|
||||
};
|
||||
|
||||
// string value for some message types
|
||||
this._typesStr = {
|
||||
'chr': this._strDirect,
|
||||
'str': this._strDirect,
|
||||
'int': this._strToString,
|
||||
'tim': this._strToString,
|
||||
'ptr': this._strDirect
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsigned integer array to string.
|
||||
*
|
||||
* @param uia Unsigned integer array
|
||||
* @return Decoded string
|
||||
*/
|
||||
WeeChatProtocol._uia2s = function(uia) {
|
||||
var str = [];
|
||||
|
||||
for (var c = 0; c < uia.length; c++) {
|
||||
str.push(String.fromCharCode(uia[c]));
|
||||
}
|
||||
|
||||
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();
|
||||
info.value = this._getString();
|
||||
|
||||
return info;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads an hdata token value from current set data.
|
||||
*
|
||||
* @return Hdata object
|
||||
*/
|
||||
_getHdata: function() {
|
||||
var self = this;
|
||||
var paths;
|
||||
var count;
|
||||
var objs = [];
|
||||
var hpath = this._getString();
|
||||
|
||||
keys = this._getString().split(',');
|
||||
paths = hpath.split('/');
|
||||
count = this._getInt();
|
||||
|
||||
keys = keys.map(function(key) {
|
||||
return key.split(':');
|
||||
});
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
var tmp = {};
|
||||
|
||||
tmp.pointers = paths.map(function(path) {
|
||||
return self._getPointer();
|
||||
});
|
||||
keys.forEach(function(key) {
|
||||
tmp[key[0]] = self._runType(key[1]);
|
||||
});
|
||||
objs.push(tmp);
|
||||
};
|
||||
|
||||
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) * 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads an integer token value from current set data.
|
||||
*
|
||||
* @return Integer value
|
||||
*/
|
||||
_getInt: function() {
|
||||
var parsedData = new Uint8Array(this._getSlice(4));
|
||||
|
||||
return ((parsedData[0] & 0xff) << 24) |
|
||||
((parsedData[1] & 0xff) << 16) |
|
||||
((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 this._getByte();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads a string token value from current set data.
|
||||
*
|
||||
* @return String value
|
||||
*/
|
||||
_getString: function() {
|
||||
var l = this._getInt();
|
||||
|
||||
if (l > 0) {
|
||||
var s = this._getSlice(l);
|
||||
var parsedData = new Uint8Array(s);
|
||||
|
||||
return WeeChatProtocol._uia2s(parsedData);
|
||||
}
|
||||
|
||||
return "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads a message header from current set data.
|
||||
*
|
||||
* @return Header object
|
||||
*/
|
||||
_getHeader: function() {
|
||||
var len = this._getInt();
|
||||
var comp = this._getByte();
|
||||
|
||||
return {
|
||||
length: len,
|
||||
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();
|
||||
|
||||
if (type) {
|
||||
return {
|
||||
type: type,
|
||||
content: self._runType(type),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads an hash table token from current set data.
|
||||
*
|
||||
* @return Hash table
|
||||
*/
|
||||
_getHashTable: function() {
|
||||
var self = this;
|
||||
var typeKeys, typeValues, count;
|
||||
var dict = {};
|
||||
|
||||
typeKeys = this._getType();
|
||||
typeValues = this._getType();
|
||||
count = this._getInt();
|
||||
|
||||
for (var i = 0; i < count; ++i) {
|
||||
var key = self._runType(typeKeys);
|
||||
var keyStr = self._objToString(key, typeKeys);
|
||||
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;
|
||||
var count;
|
||||
var values;
|
||||
|
||||
type = this._getType();
|
||||
count = this._getInt();
|
||||
values = [];
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
values.push(self._runType(type));
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
this._setData(data);
|
||||
this._dataAt = 0;
|
||||
|
||||
var header = this._getHeader();
|
||||
var id = this._getId();
|
||||
var objects = [];
|
||||
var object = this._getObject();
|
||||
|
||||
while (object) {
|
||||
objects.push(object);
|
||||
object = self._getObject();
|
||||
}
|
||||
|
||||
return {
|
||||
header: header,
|
||||
id: id,
|
||||
objects: objects,
|
||||
};
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue