Skip to content

Commit

Permalink
Merge pull request #1 from jeremybanks/http-and-ws
Browse files Browse the repository at this point in the history
Merges with recent changes, updates package.json, and adds jQuery and moment
  • Loading branch information
voldyman committed Jun 24, 2014
2 parents fa4f0cf + 33ade06 commit 867a50e
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 35 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ Work in progress.

In the webclient/ directory there is a javascript and HTML file that
show the general idea behind how a client would work.

Note that for Heroku-based deployments, you will need to enable
WebSockets for your app:

`heroku labs:enable websockets -a APPNAME`
140 changes: 113 additions & 27 deletions accidentalbot.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use strict';

var sugar = require('sugar');
var irc = require('irc');
var webSocket = require('ws');
var express = require('express');
var http = require('http');

var channel = '#atptest';
var webAddress = 'http://www.caseyliss.com/showbot'
var channel = '#atp';
var webAddress = 'http://www.caseyliss.com/showbot';
var TITLE_LIMIT = 75;

var titles = [];
var connections = [];
Expand All @@ -22,20 +25,20 @@ function saveBackup() {
}

function handleNewSuggestion(from, message) {
if (message.startsWith('!suggest')) {
title = message.substring(9);
} else if (message.startsWith('!s')) {
title = message.substring(3);
var title = '';
if (message.match(/^!s(?:uggest)?\s+(.+)/)) {
title = RegExp.$1.compact();
}

if (title.length > 75) {
client.say(from, 'That title is too long; please try again.');
if (title.length > TITLE_LIMIT) {
client.say(from, 'That title is too long (over ' + TITLE_LIMIT +
' characters); please try again.');
title = '';
}
if (title.length > 0) {
// Make sure this isn't a duplicate.
if (titles.findAll({titleLower: title.toLowerCase()}).length === 0) {
var title = {
title = {
id: titles.length,
author: from,
title: title,
Expand Down Expand Up @@ -91,7 +94,7 @@ function handleNewLink(from, message) {
function handleHelp(from) {
client.say(from, 'Options:');
client.say(from, '!s {title} - suggest a title.');
client.say(from, '!votes - get the three most highly voted titles.')
client.say(from, '!votes - get the three most highly voted titles.');
client.say(from, '!link {URL} - suggest a link.');
client.say(from, '!help - see this message.');
client.say(from, 'To see titles/links, go to: ' + webAddress);
Expand All @@ -107,8 +110,6 @@ client.addListener('join', function (channel, nick, message) {
});

client.addListener('message', function (from, to, message) {
var title = '';

if (message.startsWith('!s')) {
handleNewSuggestion(from, message);
} else if (message.startsWith("!votes")) {
Expand All @@ -121,9 +122,10 @@ client.addListener('message', function (from, to, message) {
});

client.addListener('error', function (message) {
console.log('error: ', message)
console.log('error: ', message);
});


/***************************************************
* HTTP SERVER *
***************************************************/
Expand All @@ -138,43 +140,127 @@ app.get('/', function(req, res) {

var server = http.createServer(app);

var port = Number(process.env.PORT || 5001);
server.listen(port);

/***************************************************
* WEB SOCKETS *
***************************************************/

var proxied = process.env.PROXIED === 'true';
var socketServer = new webSocket.Server({server: server});

var port = Number(process.env.PORT || 5001);
server.listen(port);
console.log("HTTP and WS server listening on port " + port + ".");

// DOS protection - we disconnect any address which sends more than windowLimit
// messages in a window of windowSize milliseconds.
var windowLimit = 50;
var windowSize = 5000;
var currentWindow = 0;
var recentMessages = {};
function floodedBy(socket) {
// To be called each time we get a message or connection attempt.
//
// If that address has been flooding us, we disconnect all open connections
// from that address and return `true` to indicate that it should be
// ignored. (They will not be prevented from re-connecting after waiting
// for the next window.)
if (socket.readyState == socket.CLOSED) {
return true;
}

var address = getRequestAddress(socket.upgradeReq);

var updatedWindow = 0 | ((new Date) / windowSize);
if (currentWindow !== updatedWindow) {
currentWindow = updatedWindow;
recentMessages = {};
}

if (address in recentMessages) {
recentMessages[address]++;
} else {
recentMessages[address] = 1;
}

if (recentMessages[address] > windowLimit) {
console.warn("Disconnecting flooding address: " + address);
socket.terminate();

for (var i = 0, l = connections.length; i < l; i++) {
if (getRequestAddress(connections[i].upgradeReq) === address &&
connections[i] != socket) {
console.log("Disconnecting additional connection.");
connections[i].terminate();
}
}

return true;
} else {
return false;
}
}

function getRequestAddress(request) {
if (proxied && 'x-forwarded-for' in request.headers) {
// This assumes that the X-Forwarded-For header is generated by a
// trusted proxy such as Heroku. If not, a malicious user could take
// advantage of this logic and use it to to spoof their IP.
var forwardedForAddresses = request.headers['x-forwarded-for'].split(',');
return forwardedForAddresses[forwardedForAddresses.length - 1].trim();
} else {
// This is valid for direct deployments, without routing/load balancing.
return request.connection.remoteAddress;
}
}

socketServer.on('connection', function(socket) {
if (floodedBy(socket)) return;

connections.push(socket);
var address = socket.upgradeReq.connection.remoteAddress;
var address = getRequestAddress(socket.upgradeReq);
console.log('Client connected: ' + address);

// Instead of sending all of the information about current titles to the
// newly-connecting user, which would include the IP addresses of other
// users, we just send down the information they need.
var titlesWithVotes = titles.map(function (title) {
if (title.votesBy.any(address)) {
var newTitle = title;
newTitle['voted'] = true;
return newTitle;
} else {
return title;
}
var isVoted = title.votesBy.some(function (testAddress) {
return testAddress === address;
});
var newTitle = {
id: title.id,
author: title.author,
title: title.title,
votes: title.votes,
voted: isVoted,
time: title.time
};
return newTitle;
});
console.log(JSON.stringify(titlesWithVotes));
socket.send(JSON.stringify({operation: 'REFRESH', titles: titles, links: links}));
socket.send(JSON.stringify({operation: 'REFRESH', titles: titlesWithVotes, links: links}));

socket.on('close', function () {
console.log('Client disconnected: ' + address);
connections.splice(connections.indexOf(socket), 1);
});

socket.on('message', function (data, flags) {
if (floodedBy(socket)) return;

if (flags.binary) {
console.log("ignoring binary message from " + address);
return;
}

var packet = JSON.parse(data);
if (packet.operation === 'VOTE') {
var matches = titles.findAll({id: packet['id']});

if (matches.length > 0) {
var upvoted = matches[0];
if (upvoted['votesBy'].any(address) == false) {
upvoted['votes'] = new Number(upvoted['votes']) + 1;
if (upvoted['votesBy'].any(address) === false) {
upvoted['votes'] = Number(upvoted['votes']) + 1;
upvoted['votesBy'].push(address);
sendToAll({operation: 'VOTE', votes: upvoted['votes'], id: upvoted['id']});
console.log('+1 for ' + upvoted['title'] + ' by ' + address);
Expand Down
22 changes: 21 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"license": "MIT",
"dependencies": {
"express": "^4.4.4",
"irc": "^0.3.6",
"sugar": "^1.4.1",
"ws": "^0.4.31"
Expand All @@ -27,5 +28,24 @@
},
"engines": {
"node": "0.10.x"
}
},
"contributors": [
{
"name": "Jeremy Banks",
"url": "http://www.jeremybanks.ca/"
},
{
"name": "Dave Eilers",
"url": "https://www.facebook.com/david.eilers"
},
{
"name": "Kyle Cronin",
"url": "http://kylecronin.me/"
},
{
"name": "Brad Choate",
"url": "http://bradchoate.com/"
}
],
"bugs": "https://github.com/cliss/accidentalbot/issues"
}
4 changes: 4 additions & 0 deletions webclient/jquery-1.11.1.min.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions webclient/moment-2.6.0.min.js

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions webclient/showbot.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<html>
<head>
<title>Showbot</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="jquery-1.11.1.min.js"></script>
<script src="handlebars-v1.3.0.js"></script>
<script src="showbot.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.6.0/moment.min.js"></script>

<script src="moment-2.6.0.min.js"></script>
</head>
<body>
<script id="titleRow" type="text/x-handlebars-template">
Expand Down
9 changes: 5 additions & 4 deletions webclient/showbot.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
var Showbot = Showbot || {};
'use strict';

window.Showbot = window.Showbot || {};

Showbot.Bot = (function ($) {

Expand Down Expand Up @@ -31,7 +33,7 @@ Showbot.Bot = (function ($) {
var id = $(anchor).closest('tr').data('id');
connection.send(JSON.stringify({operation: 'VOTE', id: id}));
var voteSpan = $(anchor).parent().find('.votes');
voteSpan.html(new Number(voteSpan.html()) + 1);
voteSpan.html(Number(voteSpan.html()) + 1);
$(anchor).remove();
return false;
}
Expand Down Expand Up @@ -65,7 +67,7 @@ Showbot.Bot = (function ($) {
function connectSocket() {
if (connection == null || connection.readyState == 3) {
// Connect to the server and await feedback.
if (window.location.hostname == 'localhost') {
if (window.location.hostname == 'localhost' || window.location.hostname == '') {
connection = new WebSocket('ws://localhost:5001');
} else {
connection = new WebSocket('ws://thawing-bayou-3232.herokuapp.com:80');
Expand Down Expand Up @@ -127,7 +129,6 @@ Showbot.Bot = (function ($) {
var span = $(row).find('.votes').html(packet.votes);
} else if (packet.operation == 'PONG') {
// NOOP
console.log('PONG');
}
};

Expand Down

0 comments on commit 867a50e

Please sign in to comment.