]> git.example.dev Git - binbsis50.git/commitdiff
code cleanup
authorLuigi Pinca <luigipinca@gmail.com>
Sun, 27 May 2012 16:21:11 +0000 (18:21 +0200)
committerLuigi Pinca <luigipinca@gmail.com>
Sun, 27 May 2012 16:21:11 +0000 (18:21 +0200)
23 files changed:
app.js [new file with mode: 0644]
config.js [deleted file]
config.json [new file with mode: 0644]
lib/captcha.js
lib/match.js
lib/room.js
lib/stats.js
lib/user.js [new file with mode: 0644]
package.json
public/static/css/style.css
public/static/js/home.js
public/static/js/room.js
routes/site.js [new file with mode: 0644]
routes/user.js [new file with mode: 0644]
server.js [deleted file]
views/footer.jade
views/header.jade
views/index.jade
views/login.jade
views/room.jade
views/signup.jade
views/user.jade
views/uv.jade

diff --git a/app.js b/app.js
new file mode 100644 (file)
index 0000000..bca2c84
--- /dev/null
+++ b/app.js
@@ -0,0 +1,184 @@
+/**
+ * Module dependencies.
+ */
+
+var config = require('./config')
+    , express = require('express')
+    , parseCookie = require('connect').utils.parseCookie
+    , redisstore = require('connect-redis')(express)
+    , redisurl = require('redis-url')
+    , site = require('./routes/site')
+    , user = require('./routes/user');
+
+/**
+ * Setting up redis.
+ */
+
+var songsdb = redisurl.createClient(config.songsdburl);
+var usersdb = redisurl.createClient(config.usersdburl);
+
+songsdb.on('error', function(err) {
+    console.log(err.toString());
+});
+
+usersdb.on('error', function(err) {
+    console.log(err.toString());
+});
+
+/**
+ * Setting up Express.
+ */
+var sessionstore = new redisstore({client:usersdb})
+    , app = express.createServer();
+
+// Configuration
+app.use(express.static(__dirname + '/public'), {maxAge: 2592000000});
+app.use(express.favicon(__dirname + '/public/static/images/favicon.ico', {maxAge: 2592000000}));
+app.use(express.bodyParser());
+app.use(express.cookieParser());
+app.use(express.session({secret:config.sessionsecret,store:sessionstore}));
+
+app.set('view engine', 'jade');
+app.set('view options', {layout:false});
+
+app.dynamicHelpers({
+    errors: function(req, res) {
+        var errors = req.session.errors;
+        delete req.session.errors;
+        return errors;
+    },
+    oldvalues: function(req, res) {
+        var oldvalues = req.session.oldvalues;
+        delete req.session.oldvalues;
+        return oldvalues;
+    }
+});
+
+// Routes
+site.use({db:songsdb,rooms:config.rooms});
+user.use({db:usersdb});
+
+app.get('/', site.index);
+app.get('/artworks', site.artworks);
+app.get('/signup', site.signup);
+app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
+app.get('/login', site.login);
+app.post('/login', user.validateLogin, user.checkUser, user.authenticate);
+app.get('/logout', user.logout);
+app.get('/:room', site.room);
+app.get('/user/*', user.profile);
+
+// App listen
+app.listen(config.port);
+
+/**
+ * Setting up Socket.IO.
+ */
+
+var io = require('socket.io').listen(app)
+    , sockets = Object.create(null); // Sockets of all rooms
+
+// Configuration
+io.enable('browser client minification');
+io.enable('browser client etag');
+io.enable('browser client gzip');
+io.set('log level', 1);
+io.set('transports', [
+    'websocket'
+    , 'htmlfile'
+    , 'xhr-polling'
+    , 'jsonp-polling'
+]);
+
+// Authorization
+io.set('authorization', function(data, accept) {
+    if(!data.headers.cookie) {
+        return accept('no cookie transmitted', false);
+    }
+    var cookie = parseCookie(data.headers.cookie);
+    sessionstore.get(cookie['connect.sid'], function(err, session) {
+        if (err) {
+            return accept(err.toString(), false);
+        }
+        else if (!session) {
+            return accept('session not found', false);
+        }
+        data.session = session;
+        accept(null, true);
+    });
+});
+
+io.sockets.on('connection', function(socket) {
+    var session = socket.handshake.session;
+    socket.on('getoverview', function() {
+        var data = Object.create(null);
+        for (var prop in rooms) {
+            data[prop] = rooms[prop].getPopulation();
+        }
+        socket.join('home');
+        socket.emit('overview', data);
+    });
+    socket.on('loggedin', function(fn) {
+        return (session.user) ? fn(session.user) : fn(false);
+    });
+    socket.on('joinroom', function(room) {
+        if (session.user && typeof room === 'string' && config.rooms.indexOf(room) !== -1) {
+            if (sockets[session.user]) { // User already in a room
+                socket.emit('alreadyinaroom');
+                return;
+            }
+            socket.nickname = session.user;
+            rooms[room].joinRoom(socket);
+        }
+    });
+    socket.on('joinanonymously', function(nickname, roomname) {
+        if (!socket.nickname && typeof nickname === 'string' && nickname !== '' && 
+            typeof roomname === 'string' && config.rooms.indexOf(roomname) !== -1) {
+            rooms[roomname].setNickName(socket, nickname);
+        }
+    });
+    socket.on('getstatus', function() {
+        if (socket.roomname) {
+            rooms[socket.roomname].sendStatus(socket);
+        }
+    });
+    socket.on('sendchatmsg', function(msg, to) {
+        if (socket.roomname && typeof msg === 'string') {
+            rooms[socket.roomname].sendChatMessage(msg, socket, to);
+        }
+    });
+    socket.on('guess', function(guess) {
+        if (socket.roomname && typeof guess === 'string') {
+            rooms[socket.roomname].guess(socket, guess);
+        }
+    });
+    socket.on('disconnect', function() {
+        if (socket.roomname) {
+            rooms[socket.roomname].removeUser(socket.nickname);
+        }
+    });
+});
+
+/**
+ * Setting up the rooms.
+ */
+
+var roomoptions = {
+    songsdb: songsdb,
+    usersdb: usersdb,
+    io: io,
+    sockets: sockets,
+    songsinarun: config.songsinarun,
+    fifolength: config.songsinarun * config.gameswithnorepeats,
+    threshold: config.allowederrors
+};
+
+var Room = require('./lib/room')(roomoptions)
+    , rooms = Object.create(null); // The Object that contains all the room instances
+
+for (var i=0; i<config.rooms.length; i++) {
+    rooms[config.rooms[i]] = new Room(config.rooms[i]);
+    rooms[config.rooms[i]].start();
+}
+
+console.log('   binb started and listening on port ' + config.port);
diff --git a/config.js b/config.js
deleted file mode 100644 (file)
index fad7b5c..0000000
--- a/config.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/* The Base Configuration file */
-
-exports.configure = function() {
-       this.port = 80;
-       this.songsdburl = '';
-       this.usersdburl = '';
-       this.sessionsecret = '';
-       this.songsinarun = 15;
-       this.fifolength = this.songsinarun * 3; // 3 is the number of games with no repeats of songs
-       this.threshold = 2; // Edit distance threshold
-       this.rooms = ["pop", "rock", "rap", "80s", "mixed"];
-       return this;
-};
diff --git a/config.json b/config.json
new file mode 100644 (file)
index 0000000..3d08ff1
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "port": 80,
+  "songsdburl": "",
+  "usersdburl": "",
+  "sessionsecret": "",
+  "songsinarun": 15,
+  "gameswithnorepeats": 3,
+  "allowederrors": 2,
+  "rooms": ["pop", "rock", "rap", "80s", "mixed"]
+}
index 6f4e605b7957c1940ab30d91c78603c377241925..fcf5f665f45152cf71300938fc7a5bf1e0cb3246 100644 (file)
@@ -1,29 +1,37 @@
-var canvas = require("canvas");
+/**
+ * Module dependencies.
+ */
 
-module.exports = function(characters) {
+var canvas = require('canvas')
+    , characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 
-       function Captcha() {
-               var code = "";
-               while (code.length < 4) {
-                       code += characters[Math.floor(Math.random() * characters.length)];
-               }
-               var _canvas = new canvas(64, 26);
-               var ctx = _canvas.getContext('2d');
-               ctx.fillStyle = "#DDDDDD";
-               ctx.fillRect(0, 0, 64, 26);
-               ctx.font = "bold 20px Helvetica";
-               ctx.lineWidth = 1;
-               ctx.textAlign = "center";
-               ctx.strokeStyle = "#080";
-               ctx.strokeText(code, 31, 20);
-               ctx.save();
-               this.getCode = function() {
-                       return code;
-               };
-               this.toDataURL = function() {
-                       return _canvas.toDataURL();
-               };
-       }
+/**
+ * Export the class.
+ */
 
-       return Captcha;
+module.exports = function() {
+    var code = ''
+        , _canvas = new canvas(64, 26)
+        , ctx = _canvas.getContext('2d');
+
+    while (code.length < 4) {
+        code += characters[Math.floor(Math.random() * characters.length)];
+    }
+
+    ctx.fillStyle = '#DDDDDD';
+    ctx.fillRect(0, 0, 64, 26);
+    ctx.font = 'bold 20px Helvetica';
+    ctx.lineWidth = 1;
+    ctx.textAlign = "center";
+    ctx.strokeStyle = '#080';
+    ctx.strokeText(code, 31, 20);
+    ctx.save();
+
+    this.getCode = function() {
+        return code;
+    };
+
+    this.toDataURL = function() {
+        return _canvas.toDataURL();
+    };
 };
index 24a3b0879c1e57424a2410713917c6332ed330f0..196c6ca481c90623a63a43f3f001cfd49afebe8e 100644 (file)
-module.exports = function(threshold) {
+/**
+ * Check if the edit distance between two strings is smaller than a threshold k.
+ * We dont need to trace back the optimal alignment, so we can run the Levenshtein distance 
+ * algorithm in better than O(n*m).
+ * We use only a diagonal stripe of width 2k+1 in the matrix.
+ * See Algorithms on strings, trees, and sequences: computer science and computational biology.
+ * Cambridge, UK: Cambridge University Press. pp 263-264. ISBN 0-521-58519-8.
+ */
+var checkDistance = function(s1, s2, k) {
+    if (k === 0) {
+        return s1 === s2;
+    }
+    if (Math.abs(s1.length - s2.length) > k) {
+        return false;
+    }
+    var d = [];
+    for (var i=0; i <= s1.length; i++) {
+        d[i] = []; // Now d is a matrix with s1.length + 1 rows
+        d[i][0] = i;
+    }
+    for (var j=1; j <= s2.length; j++) {
+        d[0][j] = j;
+    }
+    for (i=1; i <= s1.length; i++) {
+        var l = ((i-k) < 1) ? 1 : i-k;
+        var m = ((i+k) > s2.length) ? s2.length : i+k;
+        for (j=l; j<=m; j++) {
+            if (s1.charAt(i-1) === s2.charAt(j-1)) {
+                d[i][j] = d[i-1][j-1];
+            }
+            else {
+                if ((j === l) && (d[i][j-1] === undefined)) {
+                    d[i][j] = Math.min(d[i-1][j-1]+1, d[i-1][j]+1);
+                }
+                else if ((j === m) && (d[i-1][j] === undefined)) {
+                    d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1);
+                }
+                else {
+                    d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1, d[i-1][j]+1);
+                }
+            }
+        }
+    }
+    return d[s1.length][s2.length] <= k;
+};
+
+/**
+ * Return `amatch` function.
+ */
+
+module.exports = function(allowederrors) {
+
+    /**
+     * Edit distance threshold.
+     */
+
+    var threshold = allowederrors;
+
+    var amatch = function(subject, guess, enableartistrules) {
+        if (checkDistance(subject, guess, threshold)) {
+            return true;
+        }
+
+        var splitted, trimmed;
+
+        // Ignore dots
+        if (subject.match(/\./) && 
+            checkDistance(subject.replace(/\./g, ""), guess, threshold)) {
+            return true;
+        }
+        // Ignore commas
+        if (subject.match(/,/) && 
+            checkDistance(subject.replace(/,/g, ""), guess, threshold)) {
+            return true;
+        }
+        // Ignore dashes
+        if (subject.match(/\-/) && 
+            checkDistance(subject.replace(/\-/g, ""), guess, threshold)) {
+            return true;
+        }
+        // Allow to write "and" in place of "+"
+        if (subject.match(/\+/) && 
+            checkDistance(subject.replace(/\+/, "and"), guess, threshold)) {
+            return true;
+        }
 
-       /*
-       Check if the edit distance between two strings is smaller than a threshold k.
-       We dont need to trace back the optimal alignment, so we can run the Levenshtein distance algorithm 
-       in better than O(n*m).
-       We use only a diagonal stripe of width 2k+1 in the matrix.
-       See Algorithms on strings, trees, and sequences: computer science and computational biology.
-       Cambridge, UK: Cambridge University Press. pp 263-264. ISBN 0-521-58519-8.
-       */
-       var checkDistance = function(s1, s2, k) {
-               if (k === 0) {
-                       return s1 === s2;
-               }
-               if (Math.abs(s1.length - s2.length) > k) {
-                       return false;
-               }
-               var d = [];
-               for (var i=0; i <= s1.length; i++) {
-                       d[i] = []; // Now d is a matrix with s1.length + 1 rows
-                       d[i][0] = i;
-               }
-               for (var j=0; j <= s2.length; j++) {
-                       d[0][j] = j;
-               }
-               for (i=1; i <= s1.length; i++) {
-                       var l = ((i-k) < 1) ? 1 : i-k;
-                       var m = ((i+k) > s2.length) ? s2.length : i+k;
-                       for (j=l; j<=m; j++) {
-                               if (s1.charAt(i-1) === s2.charAt(j-1)) {
-                                       d[i][j] = d[i-1][j-1];
-                               }
-                               else {
-                                       if ((j === l) && (d[i][j-1] === undefined)) {
-                                               d[i][j] = Math.min(d[i-1][j-1]+1, d[i-1][j]+1);
-                                       }
-                                       else if ((j === m) && (d[i-1][j] === undefined)) {
-                                               d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1);
-                                       }
-                                       else {
-                                               d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1, d[i-1][j]+1);
-                                       }
-                               }
-                       }
-               }
-               return d[s1.length][s2.length] <= k;
-       };
+        if (enableartistrules) {
+            // Ignore "the" at the beginning of artist name
+            if (subject.match(/^the /)) {
+                var nothe = subject.replace(/^the /, "");
+                if (checkDistance(nothe, guess, threshold)) {
+                    return true;
+                }
+                if (nothe.match(/jimi hendrix experience/) && 
+                    checkDistance(nothe.replace(/ experience/, ""), guess, threshold)) {
+                    return true;
+                }
+            }
+            // Split artist name on "&" (artist name can be composed by more names)
+            splitted = subject.split("&");
+            if (splitted.length !== 1) {
+                for (var i=0; i<splitted.length; i++) {
+                    trimmed = splitted[i].replace(/^ +/, "").replace(/ +$/, "");
+                    if (checkDistance(trimmed, guess, threshold)) {
+                        return true;
+                    }
+                    if (trimmed.match(/^the /) && 
+                        checkDistance(trimmed.replace(/^the /, ""), guess, threshold)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        else {
+            // Allow to write "and" in place of "&"
+            if (subject.match(/ & /) && !subject.match(/\(/) &&
+                checkDistance(subject.replace(/ & /, " and "), guess, threshold)) {
+                return true;
+            }
+            // Ignore additional info e.g. "(Love Theme from Titanic)"
+            if (subject.match(/\(.+\)\??(?: \[.+\])?/)) {
+                var normalized = subject.replace(/\(.+\)\??(?: \[.+\])?/, "")
+                                        .replace(/^ +/, "").replace(/ +$/, "");
+                if (checkDistance(normalized, guess, threshold)) {
+                    return true;
+                }
+                if (normalized.match(/ & /) && 
+                    checkDistance(normalized.replace(/ & /, " and "), guess, threshold)) {
+                    return true;
+                }
+            }
+            if (subject.match(/, [pP]t\. [0-9]$/) && 
+                checkDistance(subject.replace(/, [pP]t\. [0-9]$/, ""), guess, threshold)) {
+                return true;
+            }
+        }
 
-       var amatch = function(subject, guess, enableartistrules) {
-               if (checkDistance(subject, guess, threshold)) {
-                       return true;
-               }
-               var splitted, trimmed;
-               if (subject.match(/\./) && 
-                       checkDistance(subject.replace(/\./g, ""), guess, threshold)) {
-                       return true;
-               }
-               if (subject.match(/\-/) && 
-                       checkDistance(subject.replace(/\-/g, ""), guess, threshold)) {
-                       return true;
-               }
-               if (subject.match(/\+/) && 
-                       checkDistance(subject.replace(/\+/, "and"), guess, threshold)) {
-                       return true;
-               }
-               if (enableartistrules) {
-                       if (subject.match(/^the /)) {
-                               var nothe = subject.replace(/^the /, "");
-                               if (checkDistance(nothe, guess, threshold)) {
-                                       return true;
-                               }
-                               if (nothe.match(/jimi hendrix experience/) && 
-                                       checkDistance(nothe.replace(/ experience/, ""), guess, threshold)) {
-                                       return true;
-                               }
-                       }
-                       splitted = subject.split("&");
-                       if (splitted.length !== 1) {
-                               for (var i=0; i<splitted.length; i++) {
-                                       trimmed = splitted[i].replace(/^ +/, "").replace(/ +$/, "");
-                                       if (checkDistance(trimmed, guess, threshold)) {
-                                               return true;
-                                       }
-                                       if (trimmed.match(/^the /) && 
-                                               checkDistance(trimmed.replace(/^the /, ""), guess, threshold)) {
-                                               return true;
-                                       }
-                               }
-                       }
-               }
-               else {
-                       if (subject.match(/,/) && 
-                               checkDistance(subject.replace(/,/g, ""), guess, threshold)) {
-                               return true;
-                       }
-                       if (subject.match(/ & /) && !subject.match(/\(/) &&
-                               checkDistance(subject.replace(/ & /, " and "), guess, threshold)) {
-                               return true;
-                       }
-                       if (subject.match(/\(.+\)\??(?: \[.+\])?/)) {
-                               var normalized = subject.replace(/\(.+\)\??(?: \[.+\])?/, "")
-                                                                               .replace(/^ +/, "").replace(/ +$/, "");
-                               if (checkDistance(normalized, guess, threshold)) {
-                                       return true;
-                               }
-                               if (normalized.match(/ & /) && 
-                                       checkDistance(normalized.replace(/ & /, " and "), guess, threshold)) {
-                                       return true;
-                               }
-                       }
-                       if (subject.match(/, [pP]t\. [0-9]$/) && 
-                               checkDistance(subject.replace(/, [pP]t\. [0-9]$/, ""), guess, threshold)) {
-                               return true;
-                       }
-               }
-               return false;
-       };
+        return false;
+    };
 
-       return amatch;
+    return amatch;
 };
index 62859e3facee8562caf1fde9ec8694c8ad00d22b..b096b19da19a7c3b2dbe8e74d03e3b2a0fb0cce2 100644 (file)
+/**
+ * Return the `Room` class
+ */
+
 module.exports = function(params) {
 
-       var songsdb = params.songsdb;
-       var usersdb = params.usersdb;
-       var io = params.io;
-       var sockets = params.sockets;
-       var songsinarun = params.songsinarun;
-       var fifolength = params.fifolength;
-       var threshold = params.threshold;
-
-       var amatch = require('./match.js')(threshold);
-       var collectStats = require('./stats.js')(usersdb);
-
-       function Room(roomname) {
-
-               var roomname = roomname;
-               var totusers = 0;
-       
-               var usersData = Object.create(null);
-               var playedtracks = []; // The list of already played songs
-       
-               var artistName = null;
-               var artistlcase = null;
-               var trackName = null;
-               var tracklcase = null;
-               var collectionName = null;
-               var previewUrl = null;
-               var artworkUrl = null;
-               var trackViewUrl = null;
-               var finishline = 1;
-               var allowedguess = false;
-               var status = null;
-               var songtimeleft = null; // Milliseconds
-               var songcounter = 0;
-               var trackscount = 0;
-
-               this.getPopulation = function() {
-                       return totusers;
-               };
-
-               var addUser = function(socket, loggedin) {
-                       sockets[socket.nickname] = socket;
-                       usersData[socket.nickname] = {
-                               nickname: socket.nickname,
-                               registered: loggedin,
-                               points: 0,
-                               roundpoints: 0,
-                               matched: null,
-                               guessed: 0,
-                               guesstime: null,
-                               totguesstime: 0,
-                               golds: 0,
-                               silvers: 0,
-                               bronzes: 0
-                       };
-                       totusers = totusers + 1;
-                       io.sockets.in('home').emit('update', {room:roomname,players:totusers});
-                       // Broadcast new user event
-                       socket.emit('ready', {users:usersData,trackscount:trackscount,loggedin:loggedin});
-                       socket.broadcast.to(roomname).emit('newuser', {nickname:socket.nickname,users:usersData});
-               };
-
-               var removeUser = function(nickname) {
-                       // Delete the references
-                       delete sockets[nickname];
-                       delete usersData[nickname];
-                       totusers = totusers - 1;
-                       io.sockets.in('home').emit('update', {room:roomname,players:totusers});
-               };
-
-               var userExists = function(nickname) {
-                       var user = usersData[nickname];
-                       if (user) {
-                               return true;
-                       }
-                       return false;
-               };
-       
-               this.joinRoom = function(socket) {
-                       socket.roomname = roomname;
-                       socket.join(roomname);
-                       addUser(socket, true);
-               };
-       
-               // A user requested an invalid name
-               var invalidNickName = function(socket, feedback) {
-                       socket.emit('invalidnickname', feedback);
-               };
-
-               // A user is submitting a name
-               this.setNickName = function(socket, data) {
-                       var feedback = null;
-                       if (data.nickname.length > 15) {
-                               feedback = '<span class="label label-important">That name is too long.</span>';
-                       }
-                       else if (data.nickname === "binb") {
-                               feedback = '<span class="label label-important">That name is reserved.</span>';
-                       }
-                       else if (sockets[data.nickname]) {
-                               feedback = '<span class="label label-important">Name already taken.</span>';
-                       }
-                       if (feedback) {
-                               return invalidNickName(socket, feedback);
-                       }
-               
-                       var key = "user:"+data.nickname;
-                       usersdb.exists(key, function(err, resp) {
-                               if (resp === 1) { // User already exists
-                                       feedback = '<span class="label label-important">That name belongs ';
-                                       feedback += 'to a registered user.</span>';
-                                       return invalidNickName(socket, feedback);
-                               }
-                               else {
-                                       socket.nickname = data.nickname;
-                                       socket.roomname = roomname;
-                                       socket.join(roomname);
-                                       // Add user to the list of active users
-                                       addUser(socket, false);
-                               }
-                       });
-               };
-
-               // A user has left (DCed, etc.)
-               this.userLeft = function(nickname) {
-                       removeUser(nickname);
-                       io.sockets.in(roomname).emit('userleft', {nickname:nickname,users:usersData});
-               };
-       
-               this.sendChatMessage = function (socket, data) {
-                       if (typeof data === "string") {
-                               var datalcase = data.toLowerCase();
-                               if (allowedguess && (amatch(artistlcase, datalcase, true) || 
-                                                                       amatch(tracklcase, datalcase))) {
-                                       var msg = "You are probably right, but you have to use the box above.";
-                                       socket.emit('chatmsg', {from:"binb",to:socket.nickname,chatmsg:msg});
-                                       return;
-                               }
-                               io.sockets.in(roomname).emit('chatmsg', {from:socket.nickname,chatmsg:data});
-                       }
-                       else if (typeof data === "object" && typeof data.to === "string" && 
-                                               userExists(data.to) && typeof data.chatmsg === "string") {
-                               // Private message
-                               socket.emit('chatmsg', {from:socket.nickname,to:data.to,chatmsg:data.chatmsg});
-                               var recipient = sockets[data.to];
-                               recipient.emit('chatmsg', {from:socket.nickname,to:data.to,chatmsg:data.chatmsg});
-                       }
-               };
-
-               var addPoints = function(nickname, allinone) {
-                       usersData[nickname].guesstime = 30000 - songtimeleft;
-                       var stats = {};
-                       switch (finishline) {
-                               case 1:
-                                       finishline++;
-                                       usersData[nickname].roundpoints = 6;
-                                       if (allinone) {
-                                               usersData[nickname].points += 6;
-                                               stats.points = 6;
-                                       }
-                                       else {
-                                               usersData[nickname].points += 5;
-                                               stats.points = 5;
-                                       }
-                                       usersData[nickname].golds++;
-                                       stats.gold = true;
-                                       break;
-                               case 2:
-                                       finishline++;
-                                       usersData[nickname].roundpoints = 5;
-                                       if (allinone) {
-                                               usersData[nickname].points += 5;
-                                               stats.points = 5;
-                                       }
-                                       else {
-                                               usersData[nickname].points += 4;
-                                               stats.points = 4;
-                                       }
-                                       usersData[nickname].silvers++;
-                                       stats.silver = true;
-                                       break;
-                               case 3:
-                                       finishline++;
-                                       usersData[nickname].roundpoints = 4;
-                                       if (allinone) {
-                                               usersData[nickname].points += 4;
-                                               stats.points = 4;
-                                       }
-                                       else {
-                                               usersData[nickname].points += 3;
-                                               stats.points = 3;
-                                       }
-                                       usersData[nickname].bronzes++;
-                                       stats.bronze = true;
-                                       break;
-                               default:
-                                       usersData[nickname].roundpoints = 3;
-                                       if (allinone) {
-                                               usersData[nickname].points += 3;
-                                               stats.points = 3;
-                                       }
-                                       else {
-                                               usersData[nickname].points += 2;
-                                               stats.points = 2;
-                                       }
-                       }
-                       usersData[nickname].matched = 'both';
-                       usersData[nickname].guessed++;
-                       usersData[nickname].totguesstime += usersData[nickname].guesstime;
-
-                       if (usersData[nickname].registered) {
-                               stats.userscore = usersData[nickname].points;
-                               stats.guesstime = usersData[nickname].guesstime;
-                               collectStats(nickname, stats);
-                       }
-               };
-       
-               this.guess = function(socket, guess) {
-                       if (allowedguess) {
-                               if (!usersData[socket.nickname].matched) { // No track no artist
-                                       if ((artistlcase === tracklcase) && amatch(tracklcase, guess, true)) {
-                                               addPoints(socket.nickname, true);
-                                               socket.emit('bothmatched');
-                                               io.sockets.in(roomname).emit('updateusers', {users:usersData});
-                                       }
-                                       else if (amatch(artistlcase, guess, true)) {
-                                               usersData[socket.nickname].roundpoints++;
-                                               usersData[socket.nickname].points++;
-                                               usersData[socket.nickname].matched = 'artist';
-                                               socket.emit('artistmatched');
-                                               io.sockets.in(roomname).emit('updateusers', {users:usersData});
-                                               if (usersData[socket.nickname].registered) {
-                                                       var stats = {points:1,userscore:usersData[socket.nickname].points};
-                                                       collectStats(socket.nickname, stats);
-                                               }
-                                       }
-                                       else if (amatch(tracklcase, guess)) {
-                                               usersData[socket.nickname].roundpoints++;
-                                               usersData[socket.nickname].points++;
-                                               usersData[socket.nickname].matched = 'title';
-                                               socket.emit('titlematched');
-                                               io.sockets.in(roomname).emit('updateusers', {users:usersData});
-                                               if (usersData[socket.nickname].registered) {
-                                                       var stats = {points:1,userscore:usersData[socket.nickname].points};
-                                                       collectStats(socket.nickname, stats);
-                                               }
-                                       }
-                                       else {
-                                               socket.emit('nomatch');
-                                       }
-                               }
-                               else if (usersData[socket.nickname].matched !== 'both') { // Track or artist
-                                       if (usersData[socket.nickname].matched === 'artist') {
-                                               if (amatch(tracklcase, guess)) {
-                                                       addPoints(socket.nickname, false);
-                                                       socket.emit('bothmatched');
-                                                       io.sockets.in(roomname).emit('updateusers', {users:usersData});
-                                               }
-                                               else {
-                                                       socket.emit('nomatch');
-                                               }
-                                       }
-                                       else {
-                                               if (amatch(artistlcase, guess, true)) {
-                                                       addPoints(socket.nickname, false);
-                                                       socket.emit('bothmatched');
-                                                       io.sockets.in(roomname).emit('updateusers', {users:usersData});
-                                               }
-                                               else {
-                                                       socket.emit('nomatch');
-                                               }
-                                       }
-                               }
-                               else { // The user has guessed both track and artist
-                                       socket.emit('stoptrying');
-                               }
-                       }
-                       else {
-                               socket.emit('noguesstime');
-                       }
-               };
-       
-               var resetPoints = function(roundonly) {
-                       for (var key in usersData) {
-                               if (!roundonly) {
-                                       usersData[key].points = 0;
-                                       usersData[key].guessed = 0;
-                                       usersData[key].totguesstime = 0;
-                                       usersData[key].golds = 0;
-                                       usersData[key].silvers = 0;
-                                       usersData[key].bronzes = 0;
-                               }
-                               usersData[key].roundpoints = 0;
-                               usersData[key].matched = null;
-                               usersData[key].guesstime = null;
-                       }
-               };
-
-               var sendLoadTrack = function() {
-                       songsdb.srandmember(roomname, function(err, res) {
-                               songsdb.hmget(res, "artistName", "trackName", "collectionName", "previewUrl",
-                                                               "artworkUrl60", "trackViewUrl", function(e, replies) {
-                                       if (playedtracks.indexOf(res) !== -1) {
-                                               return sendLoadTrack();
-                                       }
-                                       playedtracks.push(res);
-                                       artistName = replies[0];
-                                       artistlcase = artistName.toLowerCase();
-                                       trackName = replies[1];
-                                       tracklcase = trackName.toLowerCase();
-                                       collectionName = replies[2];
-                                       previewUrl = replies[3];
-                                       artworkUrl = replies[4];
-                                       trackViewUrl = replies[5];
-                                       io.sockets.in(roomname).emit('loadtrack', {previewUrl:previewUrl});
-                                       setTimeout(sendPlayTrack, 5000);
-                               });
-                       });
-                       status = 1; // Loading next song
-               };
-
-               var sendPlayTrack = function() {
-                       songcounter = songcounter + 1;
-                       status = 0; // Playing track
-                       io.sockets.in(roomname).emit('playtrack', {counter:songcounter,tot:songsinarun,
-                                                                                       users:usersData});
-                       songTimeLeft(Date.now()+30000, 50);
-                       allowedguess = true;
-                       setTimeout(sendTrackInfo, 30000);
-               };
-       
-               var songTimeLeft = function(end, delay) {
-                       songtimeleft = end - Date.now();
-                       if (songtimeleft < delay) {
-                               return;
-                       }
-                       setTimeout(songTimeLeft, delay, end, delay);
-               };
-       
-               var sendTrackInfo = function() {
-                       io.sockets.in(roomname).emit('trackinfo', {artworkUrl:artworkUrl,artistName:artistName,
-                                                                                       trackName:trackName,collectionName:collectionName,
-                                                                                       trackViewUrl:trackViewUrl});
-                       finishline = 1;
-                       allowedguess = false;
-                       if (songcounter < songsinarun) {
-                               resetPoints(true);
-                               sendLoadTrack();
-                       }
-                       else {
-                               status = 2; // Sending last track info
-                               setTimeout(gameOver, 5000);
-                       }
-               };
-
-               var gameOver = function() {
-                       status = 3; // Game over
-                       var users = [];
-                       for (var key in usersData) {
-                               users.push(usersData[key]);
-                       }
-                       users.sort(function(a, b) {return b.points - a.points;});
-                       var podium = users.slice(0,3);
-                       io.sockets.in(roomname).emit('gameover', {users:podium});
-                       if (podium[0] && podium[0].registered) {
-                               collectStats(podium[0].nickname, {firstplace:true});
-                       }
-                       if (podium[1] && podium[1].registered) {
-                               collectStats(podium[1].nickname, {secondplace:true});
-                       }
-                       if (podium[2] && podium[2].registered) {
-                               collectStats(podium[2].nickname, {thirdplace:true});
-                       }
-                       resetPoints(false);
-                       setTimeout(reset, 5000);
-               };
-
-               this.sendStatus = function(socket) {
-                       socket.emit('status', {status:status,timeleft:songtimeleft,previewUrl:previewUrl});
-               };
-
-               var reset = function() {
-                       songcounter = 0;
-                       if (playedtracks.length === fifolength) {
-                               playedtracks.splice(0, songsinarun);
-                       }
-                       sendLoadTrack();
-               };
-
-               // Start the room
-               this.start = function() {
-                       songsdb.scard(roomname, function(err, res) {
-                               trackscount = res;
-                       });
-                       sendLoadTrack();
-               };
-       }
-
-       return Room;
+    /**
+     * Room dependencies.
+     */
+
+    var amatch = require('./match')(params.threshold)
+        , collectStats = require('./stats')(params.usersdb)
+        , fifolength = params.fifolength
+        , io = params.io
+        , sockets = params.sockets
+        , songsdb = params.songsdb
+        , songsinarun = params.songsinarun
+        , usersdb = params.usersdb;
+
+    /**
+     * Room class.
+     */
+
+    function Room(roomname) {
+
+        var allowedguess = false
+            , artistlcase
+            , artistName
+            , artworkUrl
+            , collectionName
+            , finishline = 1
+            , playedtracks = [] // The list of already played songs
+            , previewUrl
+            , songcounter = 0
+            , songtimeleft // Milliseconds
+            , status
+            , tracklcase
+            , trackName
+            , trackscount = 0
+            , trackViewUrl
+            , totusers = 0
+            , usersData = Object.create(null);
+
+        // Add a new user in the room.
+        var addUser = function(socket, loggedin) {
+            sockets[socket.nickname] = socket;
+            usersData[socket.nickname] = {
+                nickname: socket.nickname,
+                registered: loggedin,
+                points: 0,
+                roundpoints: 0,
+                matched: null,
+                guessed: 0,
+                guesstime: null,
+                totguesstime: 0,
+                golds: 0,
+                silvers: 0,
+                bronzes: 0
+            };
+            totusers++;
+            // Broadcast new user event
+            io.sockets.in('home').emit('update', roomname, totusers);
+            socket.emit('ready', usersData, trackscount, loggedin);
+            socket.broadcast.to(roomname).emit('newuser', socket.nickname, usersData);
+        };
+
+        // A user has left (DCed, etc.)
+        this.removeUser = function(nickname) {
+            // Delete the references
+            delete sockets[nickname];
+            delete usersData[nickname];
+            totusers--;
+            // Broadcast the event
+            io.sockets.in('home').emit('update', roomname, totusers);
+            io.sockets.in(roomname).emit('userleft', nickname, usersData);
+        };
+
+        // Return the number of users in the room.
+        this.getPopulation = function() {
+            return totusers;
+        };
+
+        this.joinRoom = function(socket) {
+            socket.roomname = roomname;
+            socket.join(roomname);
+            addUser(socket, true);
+        };
+
+        // A user requested an invalid name
+        var invalidNickName = function(socket, feedback) {
+            socket.emit('invalidnickname', feedback);
+        };
+
+        // A user is submitting a name
+        this.setNickName = function(socket, nickname) {
+            var feedback = null;
+
+            if (nickname.length > 15) {
+                feedback = '<span class="label label-important">That name is too long.</span>';
+            }
+            else if (nickname === 'binb') {
+                feedback = '<span class="label label-important">That name is reserved.</span>';
+            }
+            else if (sockets[nickname]) {
+                feedback = '<span class="label label-important">Name already taken.</span>';
+            }
+
+            if (feedback) {
+                return invalidNickName(socket, feedback);
+            }
+
+            // Check if requested nickname belong to a registered user
+            var key = 'user:'+nickname;
+            usersdb.exists(key, function(err, resp) {
+                if (resp === 1) {
+                    feedback = '<span class="label label-important">That name belongs ';
+                    feedback += 'to a registered user.</span>';
+                    return invalidNickName(socket, feedback);
+                }
+                socket.nickname = nickname;
+                socket.roomname = roomname;
+                socket.join(roomname);
+                addUser(socket, false);
+            });
+        };
+
+        // A user is sending a chat message
+        this.sendChatMessage = function(msg, socket, to) {
+            if (typeof to === 'string') {
+                // Check if the recipient is in the room
+                if (usersData[to]) {
+                    socket.emit('chatmsg', msg, socket.nickname, to);
+                    var recipient = sockets[to];
+                    recipient.emit('chatmsg', msg, socket.nickname, to);
+                }
+                return;
+            }
+            // Censor answers from chat
+            var msglcase = msg.toLowerCase();
+            if (allowedguess && (amatch(artistlcase, msglcase, true) || 
+                                amatch(tracklcase, msglcase))) {
+                var notice = 'You are probably right, but you have to use the box above.';
+                socket.emit('chatmsg', notice, 'binb', socket.nickname);
+                return;
+            }
+            io.sockets.in(roomname).emit('chatmsg', msg, socket.nickname);
+        };
+
+        // User points and statistics
+        var addPointsAndStats = function(nickname, allinone) {
+            usersData[nickname].guesstime = 30000 - songtimeleft;
+            var stats = {};
+            switch (finishline) {
+                case 1:
+                    finishline++;
+                    usersData[nickname].roundpoints = 6;
+                    if (allinone) {
+                        usersData[nickname].points += 6;
+                        stats.points = 6;
+                    }
+                    else {
+                        usersData[nickname].points += 5;
+                        stats.points = 5;
+                    }
+                    usersData[nickname].golds++;
+                    stats.gold = true;
+                    break;
+                case 2:
+                    finishline++;
+                    usersData[nickname].roundpoints = 5;
+                    if (allinone) {
+                        usersData[nickname].points += 5;
+                        stats.points = 5;
+                    }
+                    else {
+                        usersData[nickname].points += 4;
+                        stats.points = 4;
+                    }
+                    usersData[nickname].silvers++;
+                    stats.silver = true;
+                    break;
+                case 3:
+                    finishline++;
+                    usersData[nickname].roundpoints = 4;
+                    if (allinone) {
+                        usersData[nickname].points += 4;
+                        stats.points = 4;
+                    }
+                    else {
+                        usersData[nickname].points += 3;
+                        stats.points = 3;
+                    }
+                    usersData[nickname].bronzes++;
+                    stats.bronze = true;
+                    break;
+                default:
+                    usersData[nickname].roundpoints = 3;
+                    if (allinone) {
+                        usersData[nickname].points += 3;
+                        stats.points = 3;
+                    }
+                    else {
+                        usersData[nickname].points += 2;
+                        stats.points = 2;
+                    }
+            }
+            usersData[nickname].matched = 'both';
+            usersData[nickname].guessed++;
+            usersData[nickname].totguesstime += usersData[nickname].guesstime;
+
+            if (usersData[nickname].registered) {
+                stats.userscore = usersData[nickname].points;
+                stats.guesstime = usersData[nickname].guesstime;
+                collectStats(nickname, stats);
+            }
+        };
+
+        // A user is sending a guess
+        this.guess = function(socket, guess) {
+            if (allowedguess) {
+                if (!usersData[socket.nickname].matched) { // No track no artist
+                    if ((artistlcase === tracklcase) && amatch(tracklcase, guess, true)) {
+                        addPointsAndStats(socket.nickname, true);
+                        socket.emit('bothmatched');
+                        io.sockets.in(roomname).emit('updateusers', usersData);
+                    }
+                    else if (amatch(artistlcase, guess, true)) {
+                        usersData[socket.nickname].roundpoints++;
+                        usersData[socket.nickname].points++;
+                        usersData[socket.nickname].matched = 'artist';
+                        socket.emit('artistmatched');
+                        io.sockets.in(roomname).emit('updateusers', usersData);
+                        if (usersData[socket.nickname].registered) {
+                            var stats = {points:1,userscore:usersData[socket.nickname].points};
+                            collectStats(socket.nickname, stats);
+                        }
+                    }
+                    else if (amatch(tracklcase, guess)) {
+                        usersData[socket.nickname].roundpoints++;
+                        usersData[socket.nickname].points++;
+                        usersData[socket.nickname].matched = 'title';
+                        socket.emit('titlematched');
+                        io.sockets.in(roomname).emit('updateusers', usersData);
+                        if (usersData[socket.nickname].registered) {
+                            var stats = {points:1,userscore:usersData[socket.nickname].points};
+                            collectStats(socket.nickname, stats);
+                        }
+                    }
+                    else {
+                        socket.emit('nomatch');
+                    }
+                }
+                else if (usersData[socket.nickname].matched !== 'both') { // Track or artist
+                    if (usersData[socket.nickname].matched === 'artist') {
+                        if (amatch(tracklcase, guess)) {
+                            addPointsAndStats(socket.nickname, false);
+                            socket.emit('bothmatched');
+                            io.sockets.in(roomname).emit('updateusers', usersData);
+                        }
+                        else {
+                            socket.emit('nomatch');
+                        }
+                    }
+                    else {
+                        if (amatch(artistlcase, guess, true)) {
+                            addPointsAndStats(socket.nickname, false);
+                            socket.emit('bothmatched');
+                            io.sockets.in(roomname).emit('updateusers', usersData);
+                        }
+                        else {
+                            socket.emit('nomatch');
+                        }
+                    }
+                }
+                else { // The user has guessed both track and artist
+                    socket.emit('stoptrying');
+                }
+            }
+            else {
+                socket.emit('noguesstime');
+            }
+        };
+
+        var resetPoints = function(roundonly) {
+            for (var key in usersData) {
+                if (!roundonly) {
+                    usersData[key].points = 0;
+                    usersData[key].guessed = 0;
+                    usersData[key].totguesstime = 0;
+                    usersData[key].golds = 0;
+                    usersData[key].silvers = 0;
+                    usersData[key].bronzes = 0;
+                }
+                usersData[key].roundpoints = 0;
+                usersData[key].matched = null;
+                usersData[key].guesstime = null;
+            }
+        };
+
+        // Extract a random track from the database and send the load event
+        var sendLoadTrack = function() {
+            songsdb.srandmember(roomname, function(err, res) {
+                songsdb.hmget(res, 'artistName', 'trackName', 'collectionName', 'previewUrl',
+                                'artworkUrl60', 'trackViewUrl', function(e, replies) {
+                    // Check if extracted track is in the list of already played tracks
+                    if (playedtracks.indexOf(res) !== -1) {
+                        return sendLoadTrack();
+                    }
+                    playedtracks.push(res);
+                    artistName = replies[0];
+                    artistlcase = artistName.toLowerCase();
+                    trackName = replies[1];
+                    tracklcase = trackName.toLowerCase();
+                    collectionName = replies[2];
+                    previewUrl = replies[3];
+                    artworkUrl = replies[4];
+                    trackViewUrl = replies[5];
+                    io.sockets.in(roomname).emit('loadtrack', previewUrl);
+                    setTimeout(sendPlayTrack, 5000);
+                });
+            });
+            status = 1; // Loading next song
+        };
+
+        // Timer for the playing song
+        var songTimeLeft = function(end, delay) {
+            songtimeleft = end - Date.now();
+            if (songtimeleft < delay) {
+                return;
+            }
+            setTimeout(songTimeLeft, delay, end, delay);
+        };
+
+        var sendPlayTrack = function() {
+            songcounter++;
+            status = 0; // Playing track
+            var data = {
+                counter:songcounter,
+                tot:songsinarun,
+                users:usersData
+            };
+            io.sockets.in(roomname).emit('playtrack', data);
+            songTimeLeft(Date.now() + 30000, 50);
+            allowedguess = true;
+            setTimeout(sendTrackInfo, 30000);
+        };
+
+        var sendTrackInfo = function() {
+            var trackinfo = {
+                artworkUrl:artworkUrl,
+                artistName:artistName,
+                trackName:trackName,
+                trackViewUrl:trackViewUrl,
+                collectionName:collectionName
+            };
+            io.sockets.in(roomname).emit('trackinfo', trackinfo);
+            finishline = 1;
+            allowedguess = false;
+
+            if (songcounter < songsinarun) {
+                resetPoints(true);
+                sendLoadTrack();
+                return;
+            }
+
+            status = 2; // Sending last track info
+            setTimeout(gameOver, 5000);
+        };
+
+        var gameOver = function() {
+            status = 3; // Game over
+
+            // Build podium
+            var users = [];
+            for (var key in usersData) {
+                users.push(usersData[key]);
+            }
+            users.sort(function(a, b) {return b.points - a.points;});
+            var podium = users.slice(0,3);
+            io.sockets.in(roomname).emit('gameover', podium);
+
+            // Collect podium stats
+            if (podium[0] && podium[0].registered) {
+                collectStats(podium[0].nickname, {firstplace:true});
+            }
+            if (podium[1] && podium[1].registered) {
+                collectStats(podium[1].nickname, {secondplace:true});
+            }
+            if (podium[2] && podium[2].registered) {
+                collectStats(podium[2].nickname, {thirdplace:true});
+            }
+
+            resetPoints(false);
+            songcounter = 0;
+            // Check if FIFO is full
+            if (playedtracks.length === fifolength) {
+                playedtracks.splice(0, songsinarun);
+            }
+
+            // Start a new game
+            setTimeout(sendLoadTrack, 5000);
+        };
+
+        // Send the room status
+        this.sendStatus = function(socket) {
+            var data = {
+                status:status,
+                timeleft:songtimeleft,
+                previewUrl:previewUrl
+            };
+            socket.emit('status', data);
+        };
+
+        // Start the room
+        this.start = function() {
+            songsdb.scard(roomname, function(err, res) {
+                trackscount = res;
+            });
+            sendLoadTrack();
+        };
+    }
+
+    return Room;
 };
index 3f89aa79f44bea040bc00fc587308d82ae735b8b..c52689398143f672b1690cdc5774398c3e4f1ddc 100644 (file)
@@ -1,51 +1,62 @@
+/**
+ * Return `collectStats` function.
+ */
+
 module.exports = function(db) {
 
-       var collectStats = function(username, stats) {
-               var key = "user:"+username;
-               if (stats.points) {
-                       db.hincrby(key, "totpoints", stats.points);
-               }
-               if (stats.userscore) {
-                       // Set personal best
-                       db.hget(key, "bestscore", function(err, res) {
-                               if (res < stats.userscore) {
-                                       db.hset(key, "bestscore", stats.userscore);
-                               }
-                       });
-               }
-               if (stats.gold) {
-                       db.hincrby(key, "golds", 1);
-               }
-               if (stats.silver) {
-                       db.hincrby(key, "silvers", 1);
-               }
-               if (stats.bronze) {
-                       db.hincrby(key, "bronzes", 1);
-               }
-               if (stats.guesstime) {
-                       db.hincrby(key, "guessed", 1);
-                       db.hincrby(key, "totguesstime", stats.guesstime);
-                       db.hget(key, "bestguesstime", function(err, res) {
-                               if (stats.guesstime < res) {
-                                       db.hset(key, "bestguesstime", stats.guesstime);
-                               }
-                       });
-                       db.hget(key, "worstguesstime", function(err, res) {
-                               if (stats.guesstime > res) {
-                                       db.hset(key, "worstguesstime", stats.guesstime);
-                               }
-                       });
-               }
-               if (stats.firstplace) {
-                       db.hincrby(key, "victories", 1);
-               }
-               if (stats.secondplace) {
-                       db.hincrby(key, "secondplaces", 1);
-               }
-               if (stats.thirdplace) {
-                       db.hincrby(key, "thirdplaces", 1);
-               }
-       };
+    var collectStats = function(username, stats) {
+        var key = 'user:'+username;
+        if (stats.points) {
+            // Update total points
+            db.hincrby(key, 'totpoints', stats.points);
+        }
+        if (stats.userscore) {
+            // Set personal best
+            db.hget(key, 'bestscore', function(err, res) {
+                if (res < stats.userscore) {
+                    db.hset(key, 'bestscore', stats.userscore);
+                }
+            });
+        }
+        if (stats.gold) {
+            // Update the number of golds
+            db.hincrby(key, 'golds', 1);
+        }
+        if (stats.silver) {
+            db.hincrby(key, 'silvers', 1);
+        }
+        if (stats.bronze) {
+            db.hincrby(key, 'bronzes', 1);
+        }
+        if (stats.guesstime) {
+            // Update the number of guessed tracks
+            db.hincrby(key, 'guessed', 1);
+            // Update total guess time
+            db.hincrby(key, 'totguesstime', stats.guesstime);
+            // Set best answer time
+            db.hget(key, 'bestguesstime', function(err, res) {
+                if (stats.guesstime < res) {
+                    db.hset(key, 'bestguesstime', stats.guesstime);
+                }
+            });
+            // Set worst answer time
+            db.hget(key, 'worstguesstime', function(err, res) {
+                if (stats.guesstime > res) {
+                    db.hset(key, 'worstguesstime', stats.guesstime);
+                }
+            });
+        }
+        if (stats.firstplace) {
+            // Update the number of first places
+            db.hincrby(key, 'victories', 1);
+        }
+        if (stats.secondplace) {
+            db.hincrby(key, 'secondplaces', 1);
+        }
+        if (stats.thirdplace) {
+            db.hincrby(key, 'thirdplaces', 1);
+        }
+    };
 
-       return collectStats;
+    return collectStats;
 };
diff --git a/lib/user.js b/lib/user.js
new file mode 100644 (file)
index 0000000..3b88b5f
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Export the class.
+ */
+
+module.exports = function(username, email, salt, hash, joindate) {
+    this.username = username;
+    this.email = email;
+    this.salt = salt;
+    this.password = hash;
+    this.joindate = joindate;
+    this.totpoints = 0;
+    this.bestscore = 0;
+    this.golds = 0;
+    this.silvers = 0;
+    this.bronzes = 0;
+    this.bestguesstime = 30000;
+    this.worstguesstime = 0;
+    this.totguesstime = 0;
+    this.guessed = 0;
+    this.victories = 0;
+    this.secondplaces = 0;
+    this.thirdplaces = 0;
+};
index e9c34861f7acebb47625f5a6bd3e167621a67e59..5f286881b53fba15b61a805afe15dad4fa49376f 100644 (file)
@@ -6,17 +6,16 @@
     "connect": "1.8.x",
     "connect-redis": "1.3.x",
     "express": "2.5.x",
-    "express-form": "0.6.x",
     "jade": "0.26.x",
     "redis-url": "0.1.x",
     "socket.io": "0.9.x"
   },
   "subdomain": "binb",
   "scripts": {
-    "start": "server.js"
+    "start": "app.js"
   },
   "engines": {
     "node": "0.6.x"
   },
-  "version": "0.3.0-13"
-}
\ No newline at end of file
+  "version": "0.3.1"
+}
index 348a5503f045a6c473de547975c46d5989a66438..935c9864b4f488a3e99bdb0cc9617cfa6119c748 100644 (file)
     font-style: normal;
 }
 body {
-       background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
-       padding-top:45px;
+    background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
+    padding-top:45px;
 }
 section {
-       margin-top:30px;
+    margin-top:30px;
 }
 .navbar .brand {
-       padding: 2px 20px 4px;
+    padding: 2px 20px 4px;
 }
 .logo {
-       font-family: 'ChristopherhandRegular';
+    font-family: 'ChristopherhandRegular';
 }
 .navbar .brand .logo {
-       height: 34px;
-       padding-left: 68px;
-       line-height: 40px;
-       background: url('/static/images/logo.png') no-repeat 0px -54px;
-       font-size: 25px;
+    height: 34px;
+    padding-left: 68px;
+    line-height: 40px;
+    background: url('/static/images/logo.png') no-repeat 0px -54px;
+    font-size: 25px;
 }
 .page-header .logo {
-       display: inline-block;
-       height: 54px;
-       padding-left: 109px;
-       line-height: 68px;
-       background: url('/static/images/logo.png') no-repeat 0 0;
-       font-size: 34px;
-       color: #0088CC;
+    display: inline-block;
+    height: 54px;
+    padding-left: 109px;
+    line-height: 68px;
+    background: url('/static/images/logo.png') no-repeat 0 0;
+    font-size: 34px;
+    color: #0088CC;
 }
 .navbar .navbar-text {
-       line-height:19px;
-       padding: 10px 10px 11px;
+    line-height:19px;
+    padding: 10px 10px 11px;
 }
 .form-horizontal .control-group {
-       margin-bottom: 10px;
+    margin-bottom: 10px;
 }
 .form-horizontal .control-label {
-       width: 100px;
+    width: 100px;
 }
 .form-horizontal .controls {
-       margin-left: 120px;
+    margin-left: 120px;
 }
 form .clearfix {
-       margin-bottom: 10px;
+    margin-bottom: 10px;
 }
 .well {
-       background-color: #DDDDDD;
-       margin-bottom: 18px;
+    background-color: #DDDDDD;
+    margin-bottom: 18px;
 }
 .alert {
-       margin-bottom: 9px;
+    margin-bottom: 9px;
 }
 #signup-button {
-       margin-left: 120px;
-       margin-top: 9px;
+    margin-left: 120px;
+    margin-top: 9px;
 }
 #captcha-input {
-       width: 126px;
+    width: 126px;
 }
 #captcha {
-       margin-right:20px;
+    margin-right:20px;
 }
 .page-header {
-       padding: 0;
-       margin: 0 0 17px 0;
-       border-bottom:1px solid #ddd;
-       -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
-       -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
-       box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
+    padding: 0;
+    margin: 0 0 17px 0;
+    border-bottom:1px solid #ddd;
+    -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
+    -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
+    box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
 }
 input {
-       margin-bottom: 0;
+    margin-bottom: 0;
 }
 .modal-footer {
-       text-align: left;
+    text-align: left;
 }
 .modal-footer .btn {
-       float: right;
+    float: right;
 }
 .thumbnails {
-       margin-bottom: 0px;
+    margin-bottom: 0px;
 }
 .thumbnails > li {
-       margin: 0 0 18px 80px;
+    margin: 0 0 18px 80px;
 }
 .thumbnail {
-       border: 1px solid #ccc;
-       height: 140px;
-       opacity: 0.7;
+    border: 1px solid #ccc;
+    height: 140px;
+    opacity: 0.7;
 }
 .thumbnail:hover {
-       opacity: 1;
+    opacity: 1;
 }
 .thumbnail img {
-       float: left;
-       width: 70px;
-       height: 70px;
+    float: left;
+    width: 70px;
+    height: 70px;
 }
 .profile {
-       font-size: 24px;
-       font-weight: bold;
-       line-height: 32px;
+    font-size: 24px;
+    font-weight: bold;
+    line-height: 32px;
 }
 .profile .img {
-       width: 32px;
-       height: 32px;
-       float: left;
-       margin-right: 5px;
-       background: url('/static/images/sprites.png') no-repeat 0px -56px;
+    width: 32px;
+    height: 32px;
+    float: left;
+    margin-right: 5px;
+    background: url('/static/images/sprites.png') no-repeat 0px -56px;
 }
 .stats {
-       border: 1px solid #ccc;
-       border-left: 0;
-       margin-top:8px;
+    border: 1px solid #ccc;
+    border-left: 0;
+    margin-top:8px;
 }
 .stats td {
-       border-left: 1px solid #ccc;
-       border-top: 1px solid #ccc;
-       vertical-align: middle;
+    border-left: 1px solid #ccc;
+    border-top: 1px solid #ccc;
+    vertical-align: middle;
 }
 .stats tbody tr:nth-child(odd) td {
-       background-color: #ddd;
+    background-color: #ddd;
 }
 .stats tbody tr:hover td {
-       background-color: #dadada;
+    background-color: #dadada;
 }
 .room {
-       height: 25px;
-       line-height: 25px;
-       position: absolute;
-       top: 61px;
-       background-color: rgba(51,51,51, 0.7);
-       color: white;
-       font-weight: bold;
-       font-size: 14px;
-       width: 210px;
-       text-align: center;
-       text-transform: capitalize;
+    height: 25px;
+    line-height: 25px;
+    position: absolute;
+    top: 61px;
+    background-color: rgba(51,51,51, 0.7);
+    color: white;
+    font-weight: bold;
+    font-size: 14px;
+    width: 210px;
+    text-align: center;
+    text-transform: capitalize;
 }
 .dropdown-menu {
-       min-width: 140px;
+    min-width: 140px;
 }
 .dropdown-menu a {
-       padding: 2px 10px;
+    padding: 2px 10px;
 }
 .dropdown-menu .divider {
-       margin: 4px 1px;
+    margin: 4px 1px;
 }
 .matched {
-       color: #f3a22f;
+    color: #f3a22f;
 }
 .cups, .medals {
-       margin-left: auto;
-       margin-right: auto;
+    margin-left: auto;
+    margin-right: auto;
 }
 .cups {
-       width: 16px;
-       height: 16px;
+    width: 16px;
+    height: 16px;
 }
 .medals {
-       width: 32px;
-       height: 32px;
+    width: 32px;
+    height: 32px;
 }
 .rank1 {
-       background: url('/static/images/sprites.png') no-repeat -32px -16px;
+    background: url('/static/images/sprites.png') no-repeat -32px -16px;
 }
 .rank2 {
-       background: url('/static/images/sprites.png') no-repeat -32px -48px;
+    background: url('/static/images/sprites.png') no-repeat -32px -48px;
 }
 .rank3 {
-       background: url('/static/images/sprites.png') no-repeat -32px -80px;
+    background: url('/static/images/sprites.png') no-repeat -32px -80px;
 }
 .scoreboard th, .scoreboard td {
-       vertical-align: middle;
-       text-align: center;
+    vertical-align: middle;
+    text-align: center;
 }
 .scoreboard .name {
-       font-weight:bold;
+    font-weight:bold;
 }
 .relative {
-       position: relative;
+    position: relative;
 }
 #app-name {
-       display: inline-block;
+    display: inline-block;
 }
 #total-tracks {
-       float: right;
-       color: #BFBFBF;
-       margin-top: 34px;
+    float: right;
+    color: #BFBFBF;
+    margin-top: 34px;
 }
 #cassette {
-       margin-top:22px;
-       height: 137px;
-       background: url('/static/images/cassette.png') no-repeat 0 0;
+    margin-top:22px;
+    height: 137px;
+    background: url('/static/images/cassette.png') no-repeat 0 0;
 }
 #countdown {
-       position: absolute;
-       width: 26px;
-       text-align:center;
-       top:80px;
-       left:175px;
+    position: absolute;
+    width: 26px;
+    text-align:center;
+    top:80px;
+    left:175px;
 }
 #wheel-left, #wheel-right {
-       position:absolute;
-       width: 24px;
-       height:24px;
-       top: 49px;
-       background: url('/static/images/sprites.png') no-repeat 0px -32px;
+    position:absolute;
+    width: 24px;
+    height:24px;
+    top: 49px;
+    background: url('/static/images/sprites.png') no-repeat 0px -32px;
 }
 #wheel-left {
-       left:51px;
+    left:51px;
 }
 #wheel-right {
-       left:145px;
+    left:145px;
 }
 #tape-left, #tape-right {
-       height:70px;
-       width:70px;
-       position:absolute;
-       top:25px;
-       background-color: black;
-       -webkit-border-radius: 70px;
-       -moz-border-radius: 70px;
-       border-radius: 70px;
-       z-index:-1;
+    height:70px;
+    width:70px;
+    position:absolute;
+    top:25px;
+    background-color: black;
+    -webkit-border-radius: 70px;
+    -moz-border-radius: 70px;
+    border-radius: 70px;
+    z-index:-1;
 }
 #tape-left {
-       left: 20px;
+    left: 20px;
 }
 #tape-right {
-       left: 106px;
+    left: 106px;
 }
 #progress-bar, #progress {
-       height:7px;
-       -webkit-border-radius:4px;
-       -moz-border-radius:4px;
-       border-radius:4px;
-       -webkit-box-shadow: inset 0px 1px 2px 0px #333;
-       -moz-box-shadow: inset 0px 1px 2px 0px #333;
-       box-shadow: inset 0px 1px 2px 0px #333;
+    height:7px;
+    -webkit-border-radius:4px;
+    -moz-border-radius:4px;
+    border-radius:4px;
+    -webkit-box-shadow: inset 0px 1px 2px 0px #333;
+     -moz-box-shadow: inset 0px 1px 2px 0px #333;
+    box-shadow: inset 0px 1px 2px 0px #333;
 }
 #progress-bar {
-       position:absolute;
-       width: 148px;
-       top:85px;
-       left:20px;
-       border:1px solid #404040;
-       border:1px solid rgba(0, 0, 0, 0.5);
+    position:absolute;
+    width: 148px;
+    top:85px;
+    left:20px;
+    border:1px solid #404040;
+    border:1px solid rgba(0, 0, 0, 0.5);
 }
 #progress {
-       background-color: #6184b7;
-       width:0px;
+    background-color: #6184b7;
+    width:0px;
 }
 #touch-backdrop {
-       width:220px;
-       height:137px;
-       -webkit-border-radius:5px;
-       -moz-border-radius:5px;
-       border-radius:5px;
-       background:rgba(50,50,50,0.8);
+    width:220px;
+    height:137px;
+    -webkit-border-radius:5px;
+    -moz-border-radius:5px;
+    border-radius:5px;
+    background:rgba(50,50,50,0.8);
 }
 #touch-play {
-       margin-top: 54px;
-       margin-left: 77px;
+    margin-top: 54px;
+    margin-left: 77px;
 }
 #volume {
-       height:159px;
+    height:159px;
 }
 #volume-button, #volume-slider, #volume-total, #volume-current, #volume-handle {
-       position: absolute;
+    position: absolute;
 }
 #volume-button {
-       bottom: 0;
-       height: 20px;
-       width: 20px;
+    bottom: 0;
+    height: 20px;
+    width: 20px;
 }
 #volume-button .button {
-       display:block;
-       height: 18px;
-       width: 18px;
-       border:1px solid;
-       border-color: #CCCCCC #CCCCCC #AAAAAA;
-       -webkit-border-radius:3px;
-       -moz-border-radius:3px;
-       border-radius:3px;
-       cursor:pointer;
-       filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffffff,EndColorStr=#ffe0e0e0);
-       background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);
-       background-image:-ms-linear-gradient(top,#fff 0,#e0e0e0 100%);
-       background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);
-       background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e0e0e0));
-       background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);
-       background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);
+    display:block;
+    height: 18px;
+    width: 18px;
+    border:1px solid;
+    border-color: #CCCCCC #CCCCCC #AAAAAA;
+    -webkit-border-radius:3px;
+    -moz-border-radius:3px;
+    border-radius:3px;
+    cursor:pointer;
+    filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffffff,EndColorStr=#ffe0e0e0);
+    background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);
+    background-image:-ms-linear-gradient(top,#fff 0,#e0e0e0 100%);
+    background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);
+    background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e0e0e0));
+    background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);
+    background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);
 }
 #volume-button .button:hover {
-       border-color:#999999;
-       -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
-       -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
-       box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+    border-color:#999999;
+    -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+    -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+    box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
 }
 #volume-button .button:active {
-       border-color:#999999 #AAAAAA #CCCCCC;
-       -webkit-box-shadow: 0px 1px 2px 0px #aaa inset;
-       -moz-box-shadow: 0px 1px 2px 0px #aaa inset;
-       box-shadow: 0px 1px 2px 0px #aaa inset;
-       filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc);
-       background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
-       background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
-       background-image:-o-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
-       background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(100%,#dcdcdc));
-       background-image:-webkit-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
-       background-image:linear-gradient(to bottom,#e6e6e6 0,#dcdcdc 100%);
+    border-color:#999999 #AAAAAA #CCCCCC;
+    -webkit-box-shadow: 0px 1px 2px 0px #aaa inset;
+    -moz-box-shadow: 0px 1px 2px 0px #aaa inset;
+    box-shadow: 0px 1px 2px 0px #aaa inset;
+    filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc);
+    background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+    background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+    background-image:-o-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+    background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(100%,#dcdcdc));
+    background-image:-webkit-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+    background-image:linear-gradient(to bottom,#e6e6e6 0,#dcdcdc 100%);
 }
 #volume-button .button #icon {
-       margin: 1px;
-       width: 16px;
-       height: 16px;
+    margin: 1px;
+    width: 16px;
+    height: 16px;
 }
 #volume-button .button .volume-none {
-       background: url('/static/images/sprites.png') no-repeat 0 -112px;
+    background: url('/static/images/sprites.png') no-repeat 0 -112px;
 }
 #volume-button .button .volume-low {
-       background: url('/static/images/sprites.png') no-repeat -16px -112px;
+    background: url('/static/images/sprites.png') no-repeat -16px -112px;
 }
 #volume-button .button .volume-medium {
-       background: url('/static/images/sprites.png') no-repeat -32px -112px;
+    background: url('/static/images/sprites.png') no-repeat -32px -112px;
 }
 #volume-button .button .volume-high {
-       background: url('/static/images/sprites.png') no-repeat -48px -112px;
+    background: url('/static/images/sprites.png') no-repeat -48px -112px;
 }
 #volume-slider {
-       display: none;
-       height: 116px;
-       width: 20px;
-       top: -116px;
-       -webkit-border-radius:3px;
-       -moz-border-radius:3px;
-       border-radius:3px;
-       background: rgba(50, 50, 50, 0.1);
-       z-index: 1;
+    display: none;
+    height: 116px;
+    width: 20px;
+    top: -116px;
+    -webkit-border-radius:3px;
+    -moz-border-radius:3px;
+    border-radius:3px;
+    background: rgba(50, 50, 50, 0.1);
+    z-index: 1;
 }
 #volume-total, #volume-current {
-       left: 8px;
-       top: 8px;
-       width: 4px;
-       height: 100px;
+    left: 8px;
+    top: 8px;
+    width: 4px;
+    height: 100px;
 }
 #volume-total, #volume-current, #volume-handle {
-       -webkit-border-radius: 2px;
-       -moz-border-radius: 2px;
-       border-radius: 2px;
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    border-radius: 2px;
 }
 #volume-total {
-       -webkit-box-shadow: inset 0px 0px 3px 0px #333;
-       -moz-box-shadow: inset 0px 0px 3px 0px #333;
-       box-shadow: inset 0px 0px 3px 0px #333;
+    -webkit-box-shadow: inset 0px 0px 3px 0px #333;
+    -moz-box-shadow: inset 0px 0px 3px 0px #333;
+    box-shadow: inset 0px 0px 3px 0px #333;
 }
 #volume-current {
-       -webkit-box-shadow: inset 0px 0px 1px 0px #000;
-       -moz-box-shadow: inset 0px 0px 1px 0px #000;
-       box-shadow: inset 0px 0px 1px 0px #000;
-       background-color: #6184b7;
+    -webkit-box-shadow: inset 0px 0px 1px 0px #000;
+    -moz-box-shadow: inset 0px 0px 1px 0px #000;
+    box-shadow: inset 0px 0px 1px 0px #000;
+    background-color: #6184b7;
 }
 #volume-handle {
-       left: 1px;
-       width: 16px;
-       height: 4px;
-       background: #ddd;
-       background: rgba(255, 255, 255, 0.9);
-       cursor: N-resize;
-       border:1px solid #999;
+    left: 1px;
+    width: 16px;
+    height: 4px;
+    background: #ddd;
+    background: rgba(255, 255, 255, 0.9);
+    cursor: N-resize;
+    border:1px solid #999;
 }
 #summary {
-       text-align: center;
-       margin-bottom:18px;
+    text-align: center;
+    margin-bottom:18px;
 }
 #users-wrapper {
-       margin-left: 120px;
-       width: 300px;
+    margin-left: 120px;
+    width: 300px;
 }
 #users {
-       padding-left:20px;
-       margin-bottom: 90px;
-       max-height: 380px;
-       overflow: auto;
+    padding-left:20px;
+    margin-bottom: 90px;
+    max-height: 380px;
+    overflow: auto;
 }
 #users .name, #tracks .artist {
-       font-weight: bold;
+    font-weight: bold;
 }
 #message-wrapper {
-       text-align: right;
+    text-align: right;
 }
 #users li, #tracks li, #chat li {
-       color: #404040;
+    color: #404040;
 }
 #users li {
-       height: 18px;
-       position: relative;
+    height: 18px;
+    position: relative;
 }
 #users .private {
-       display: none;
-       font-size: 9.75px;
-       padding: 2px 4px;
-       position: absolute;
-       left: -19px;
+    display: none;
+    font-size: 9.75px;
+    padding: 2px 4px;
+    position: absolute;
+    left: -19px;
 }
 .registered, .round-rank {
-       height: 16px;
-       width: 16px;
-       margin: 1px 2px 0px 0px;
+    height: 16px;
+    width: 16px;
+    margin: 1px 2px 0px 0px;
 }
 .registered {
-       background: url('/static/images/sprites.png') no-repeat 0px -16px;
+    background: url('/static/images/sprites.png') no-repeat 0px -16px;
 }
 .registered:hover {
-       background: url('/static/images/sprites.png') no-repeat -16px -16px;
+    background: url('/static/images/sprites.png') no-repeat -16px -16px;
 }
 #users .name {
-       margin-right: 4px;
+    margin-right: 4px;
 }
 #users .name, .registered {
-       cursor: pointer;
+    cursor: pointer;
 }
 #users .you {
-       cursor: auto;
+    cursor: auto;
 }
 #users .points, #users .round-points {
-       margin-right: 10px;
+    margin-right: 10px;
 }
 .stand1 {
-       background: url('/static/images/sprites.png') no-repeat 0 0;
+    background: url('/static/images/sprites.png') no-repeat 0 0;
 }
 .stand2 {
-       background: url('/static/images/sprites.png') no-repeat -16px 0;
+    background: url('/static/images/sprites.png') no-repeat -16px 0;
 }
 .stand3 {
-       background: url('/static/images/sprites.png') no-repeat -32px 0;
+    background: url('/static/images/sprites.png') no-repeat -32px 0;
 }
 #users .guess-time {
-       font-size: 11px;
-       line-height: 18px;
+    font-size: 11px;
+    line-height: 18px;
 }
 #toggle-chat {
-       position: absolute;
-       top: -17px;
-       left: 805px;
-       color: #BFBFBF;
-       height: 16px;
-       line-height: 16px;
-       font-size: 11px;
-       border: 1px solid #DDDDDD;
-       -moz-border-radius: 4px 4px 0 0;
-       -webkit-border-radius: 4px 4px 0 0;
-       border-radius: 4px 4px 0 0;
-       cursor: pointer;
-       padding: 0 10px;
-       text-decoration: none;
+    position: absolute;
+    top: -17px;
+    left: 805px;
+    color: #BFBFBF;
+    height: 16px;
+    line-height: 16px;
+    font-size: 11px;
+    border: 1px solid #DDDDDD;
+    -moz-border-radius: 4px 4px 0 0;
+    -webkit-border-radius: 4px 4px 0 0;
+    border-radius: 4px 4px 0 0;
+    cursor: pointer;
+    padding: 0 10px;
+    text-decoration: none;
 }
 #toggle-chat:hover {
-       border-color: #CCCCCC;
-       background-color: #f5f5f5;
-       color: #333333;
-       background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
-       background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
-       background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
-       background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
-       background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
-       background-image: linear-gradient(top, #ffffff, #e6e6e6);
-       filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
-       -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-       -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-       box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+    border-color: #CCCCCC;
+    background-color: #f5f5f5;
+    color: #333333;
+    background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+    background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
+    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+    background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+    background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+    background-image: linear-gradient(top, #ffffff, #e6e6e6);
+    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
 }
 #toggle-chat:active {
-       background-image: none;
-       -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
-       -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
-       box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
-       background-color: #e6e6e6;
-       background-color: #d9d9d9 \9;
-       outline: 0;
+    background-image: none;
+    -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+    -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+    background-color: #e6e6e6;
+    background-color: #d9d9d9 \9;
+    outline: 0;
 }
 #chat-wrapper {
-       margin-bottom: 4px;
-       height: 160px;
-       background-color: white;
+    margin-bottom: 4px;
+    height: 160px;
+    background-color: white;
 }
 #chat {
-       height: 152px;
-       width: 446px;
-       margin: 4px 6px;
-       overflow: auto;
+    height: 152px;
+    width: 446px;
+    margin: 4px 6px;
+    overflow: auto;
 }
 .bordered {
-       border: 1px solid #ccc;
-       -webkit-border-radius: 6px;
-       -moz-border-radius: 6px;
-       border-radius: 6px;
-       -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075);
-       -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075);
-       box-shadow: 0 1px 2px rgba(0,0,0,.075);
+    border: 1px solid #ccc;
+    -webkit-border-radius: 6px;
+    -moz-border-radius: 6px;
+    border-radius: 6px;
+    -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075);
+    -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075);
+    box-shadow: 0 1px 2px rgba(0,0,0,.075);
 }
 #chat .join, #chat .left {
-       font-style: italic;
+    font-style: italic;
 }
 #chat .private {
-       color: #a65fc3;
+    color: #a65fc3;
 }
 #feedback {
-       text-align: center;
+    text-align: center;
 }
 #feedback .correct, #users .correct{
-       color: #46A546;
+    color: #46A546;
 }
 #feedback .wrong, #chat .error {
-       color: #C43C35;
+    color: #C43C35;
 }
 #guess.correct {
-       border-color: #46A546;
+    border-color: #46A546;
 }
 #guess.correct:focus {
-       -webkit-box-shadow: 0 0 6px #7aba7b;
-       -moz-box-shadow: 0 0 6px #7aba7b;
-       box-shadow: 0 0 6px #7aba7b;
+    -webkit-box-shadow: 0 0 6px #7aba7b;
+    -moz-box-shadow: 0 0 6px #7aba7b;
+    box-shadow: 0 0 6px #7aba7b;
 }
 #guess.wrong {
-       border-color: #C43C35;
+    border-color: #C43C35;
 }
 #guess.wrong:focus {
-       -webkit-box-shadow: 0 0 6px #d59392;
-       -moz-box-shadow: 0 0 6px #d59392;
-       box-shadow: 0 0 6px #d59392;
+    -webkit-box-shadow: 0 0 6px #d59392;
+    -moz-box-shadow: 0 0 6px #d59392;
+    box-shadow: 0 0 6px #d59392;
 }
 #tracks {
-       margin: 18px 0;
-       max-height: 240px;
-       overflow: auto;
+    margin: 18px 0;
+    max-height: 240px;
+    overflow: auto;
 }
 #tracks li {
-       margin: 0 2px 2px 0;
-       padding: 8px;
-       min-height: 40px;
-       background: -moz-linear-gradient(center top , #FBFBFB, #F5F5F5);
-       background: -webkit-linear-gradient(center top , #FBFBFB, #F5F5F5);
-       background: -o-linear-gradient(center top , #FBFBFB, #F5F5F5);
-       background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5);
-       background: linear-gradient(center top , #FBFBFB, #F5F5F5);
+    margin: 0 2px 2px 0;
+    padding: 8px;
+    min-height: 40px;
+    background: -moz-linear-gradient(center top , #FBFBFB, #F5F5F5);
+    background: -webkit-linear-gradient(center top , #FBFBFB, #F5F5F5);
+    background: -o-linear-gradient(center top , #FBFBFB, #F5F5F5);
+    background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5);
+    background: linear-gradient(center top , #FBFBFB, #F5F5F5);
 }
 .registered, #users .name, #users .points, .round-rank, .round-points, #users .guess-time, #tracks img.artwork, #tracks .info, #copy, #facebook-button, #twitter-button, #github-button {
-       float:left;
+    float:left;
 }
 #tracks img.artwork {
-       width: 40px;
-       height: 40px;
-       margin-right:10px;
+    width: 40px;
+    height: 40px;
+    margin-right:10px;
 }
 #tracks .info {
-       margin-right:15px;
+    margin-right:15px;
 }
 #tracks .artist {
-       margin-top: 1px;
-       font-size: 14px;
+    margin-top: 1px;
+    font-size: 14px;
 }
 #tracks .round-rank {
-       margin-top:12px;
+    margin-top:12px;
 }
 #tracks .round-points {
-       margin-top: 11px;
+    margin-top: 11px;
 }
 #tracks a {
-       float:right;
-       display:block;
-       width:24px;
-       height:24px;
-       margin-top:8px;
+    float:right;
+    display:block;
+    width:24px;
+    height:24px;
+    margin-top:8px;
 }
 #disclaimer {
-       position: absolute;
-       left: 60px;
-       bottom: 18px;
-       color: #BFBFBF;
-       width:340px;
+    position: absolute;
+    left: 60px;
+    bottom: 18px;
+    color: #BFBFBF;
+    width:340px;
 }
 footer {
-       padding: 0;
-       margin-top: 17px;
-       border-top:1px solid #DDDDDD;
-       color: #BFBFBF;
-       text-shadow: 0 1px 0 #FFFFFF;
+    padding: 0;
+    margin-top: 17px;
+    border-top:1px solid #DDDDDD;
+    color: #BFBFBF;
+    text-shadow: 0 1px 0 #FFFFFF;
 }
 #footer-inner, #twitter-button, #github-button {
-       height:20px;
+    height:20px;
 }
 #footer-inner {
-       border-top:1px solid white;
-       padding-top:5px;
-       margin-bottom:30px;
+    border-top:1px solid white;
+    padding-top:5px;
+    margin-bottom:30px;
 }
 #copy {
-       margin: 0 20px 0 20px;
+    margin: 0 20px 0 20px;
 }
 #facebook-button {
-       width:90px;
-       height:21px
+    width:90px;
+    height:21px
 }
 #twitter-button, #github-button {
-       width:100px;
+    width:100px;
 }
 #copy, #footer-right {
-       line-height:20px;
+    line-height:20px;
 }
 #footer-right {
-       float:right;
-       margin-right:20px;
+    float:right;
+    margin-right:20px;
 }
index 3ae8103818aacfdc404f9730e007124ee263c853..935345b3921b96b1f821237ffaf1bd1c616abac5 100644 (file)
@@ -1,31 +1,31 @@
 $(function() {
-       if ($.browser.mozilla) {
-               // Block ESC button in firefox (it breaks all socket connection).
-               $(document).keypress(function(event) {
-                       if(event.keyCode === 27) {
-                               return false;
-                       }
-               });
-       }
-       $.get("/artworks", function(data) {
-               $(".thumbnail").each(function(index) {
-                       var i = index * 6;
-                       var j = i + 6;
-                       for(i; i < j; i++) {
-                               $('<img src="'+data.results[i]+'" />').appendTo($(this));
-                       }
-               });
-       });
-       var socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
-       socket.on("connect", function() {
-               socket.emit("getoverview");
-               socket.on("overview", function(data) {
-                       for (var prop in data) {
-                               $("#"+prop).text(data[prop]);
-                       }
-               });
-               socket.on("update", function(data) {
-                       $("#"+data.room).text(data.players);
-               });
-       });
+    if ($.browser.mozilla) {
+        // Block ESC button in firefox (it breaks all socket connection).
+        $(document).keypress(function(event) {
+            if(event.keyCode === 27) {
+                return false;
+            }
+        });
+    }
+    $.get("/artworks", function(data) {
+        $(".thumbnail").each(function(index) {
+            var i = index * 6;
+            var j = i + 6;
+            for(i; i < j; i++) {
+                $('<img src="'+data.results[i]+'" />').appendTo($(this));
+            }
+        });
+    });
+    var socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
+    socket.on("connect", function() {
+        socket.emit("getoverview");
+        socket.on("overview", function(data) {
+            for (var prop in data) {
+                $("#"+prop).text(data[prop]);
+            }
+        });
+        socket.on("update", function(room, players) {
+            $("#"+room).text(players);
+        });
+    });
 });
index 2051455f19ddb45318dfed31c335c8e82db195c9..185bf622034c9a93afeaa994df5a4d7006695ad4 100644 (file)
 (function() {
-       
-       var touchplay = null;
-       var elapsedtime = 0;
-       var jplayer = null;
-       var nickname = null;
-       var socket = null;
-       var pvtmsgto = null;
-       var roundpoints = 0;
-       var stopanimation = false;
-       var states = ['A song is already playing, please wait for the next one...',
-                       'Game is about to start...', 'Game is over', 'New game will start soon...'];
-       var tmstrings = ['Yes, you guessed the title. Who is the artist?', 'Now tell me the artist!',
-                                       'Correct, do you also know the artist?'];
-       var amstrings = ['Yes, that\'s the artist. What about the title?', 'Exactly, now tell me the title!',
-                                       'Do you also know the title?'];
-       var bmstrings = ['Yeah true! do you like this track?', 'Good job!', 'Great!',
-                                       'Very well done!', 'Exactly!', 'Excellent!', 'Woohoo!'];
-       var nmstrings = ['Nope, sorry!', 'No way!', 'Fail', 'Nope', 'No', 'That\'s wrong', 'What?!',
-                                       'Wrong', 'Haha, what?!', 'You kidding?', 'Don\'t make me laugh', 'You mad?',
-                                       'Try again'];
-       var historyvalues = [];
-       var historycursor = 0;
-       var DOM = {};
-
-       String.prototype.encodeEntities = function() {
-               return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
-       };
-       
-       // Exact match version of jQuery :contains selector
-       $.expr[":"].econtains = function(obj, index, meta, stack) {
-               return $(obj).html() === meta[0].replace(/^[\s\S]+:econtains\(([\s\S]+)\)$/, "$1");
-       };
-       
-       // Prompt for name and send it.
-       var joinAnonymously = function(msg) {
-               if (/nickname\s*\=/.test(document.cookie) && !msg) {
-                       nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1"));
-                       socket.emit('joinanonymously', {nickname:nickname,roomname:roomname});
-               }
-               else {
-                       if (!$('body').hasClass('modal-open')) {
-                               var html = '<div class="modal-header"><h3>You are joining the '+roomname+' room</h3></div>';
-                               html += '<div class="modal-body"><p>'+(msg || "What's your name?")+'</p></div>';
-                               html += '<div class="modal-footer">';
-                               html += '<input id="login" class="" type="text" name="nickname" />';
-                               html += '<button id="join" class="btn btn-primary">';
-                               html += '<i class="icon-user icon-white"></i> Join the game</button></div>';
-
-                               $(html).appendTo(DOM.modal);
-                               var login = $('#login');
-                               var button = $('#join');
-                               button.click(function() {
-                                       var val = $.trim(login.val());
-                                       if (val !== "") {
-                                               nickname = val;
-                                               socket.emit('joinanonymously', {nickname:nickname,roomname:roomname});
-                                       }
-                                       else {
-                                               var txt = "Nickname can't be empty.";
-                                               invalidNickName('<span class="label label-important">'+txt+'</span>');
-                                       }
-                                       login.val("");
-                               });
-                               login.keyup(function(event) {
-                                       if (event.keyCode === 13) {
-                                               button.click(); 
-                                       }
-                               });
-                               DOM.modal.modal('show');
-                               DOM.modal.on('shown', function() {
-                                       login.focus();
-                               });
-                       }
-                       else {
-                               $('.modal-body p').html(msg);
-                               $('#login').focus();
-                       }
-               }
-       };
-
-       // Your submitted name was invalid
-       var invalidNickName = function(feedback) {
-               joinAnonymously(feedback+"<br/>Try with another one:");
-       };
-       
-       /* Triggered when a logged user tries to join a room from another tab or another browser
-               and he is already in a room */
-       var alreadyInARoom = function() {
-               var html = '<div class="modal-header"><h3>Already in a room</h3></div>';
-               html += '<div class="modal-body"><div class="alert alert-error alert-block">';
-               html += '<h4 class="alert-heading">Warning!</h4>You are already in a room.<br/>';
-               html += 'Leave the other room and refresh this page or close this one.</div></div>';
-               $(html).appendTo(DOM.modal);
-               DOM.modal.modal('show');
-       };
-       
-       // You joined the room
-       var ready = function(data) {
-               if (!data.loggedin && !/nickname\s*\=/.test(document.cookie)) {
-                       document.cookie = "nickname="+escape(nickname)+";path=/;";
-               }
-               DOM.modal.modal('hide').empty();
-               $('#total-tracks span').text(data.trackscount);
-               var msg = nickname+" joined the game";
-               var joinspan = $("<span class='join'></span>");
-               joinspan.text(msg);
-               addChatEntry(joinspan);
-               updateUsers(data);
-
-               DOM.messagebox.keydown(function(event) {
-                       if (event.keyCode === 13) {
-                               var val = $.trim(DOM.messagebox.val());
-                               if (val !== "") {
-                                       var data = (pvtmsgto) ? {to:pvtmsgto,chatmsg:val} : val;
-                                       socket.emit('sendchatmsg', data);
-                               }
-                               DOM.messagebox.val("");
-                       }
-               });
-
-               DOM.guessbox.keydown(function(event) {
-                       switch (event.keyCode) {
-                               case 13: // return
-                                       var guess = $.trim(DOM.guessbox.val());
-                                       if (guess !== "") {
-                                               socket.emit('guess', guess.toLowerCase());
-                                               historyvalues.push(guess);
-                                               if (historyvalues.length > 20) {
-                                                       historyvalues.splice(0, 1);
-                                               }
-                                               historycursor = historyvalues.length;
-                                       }
-                                       DOM.guessbox.val("");
-                                       break;
-                               case 38: // up-arrow
-                                       if (historycursor > 0) {
-                                               DOM.guessbox.val(historyvalues[--historycursor]);
-                                       }
-                                       break;
-                               case 40: // down-arrow
-                                       if (historycursor < historyvalues.length - 1) {
-                                               DOM.guessbox.val(historyvalues[++historycursor]);
-                                       }
-                                       else {
-                                               historycursor = historyvalues.length;
-                                               DOM.guessbox.val("");
-                                       }
-                       }
-               });
-
-               DOM.guessbox.focus();
-               
-               socket.on('newuser', userJoin);
-               socket.on('userleft', userLeft);
-               socket.on('updateusers', updateUsers);
-               socket.on('chatmsg', getChatMessage);
-               socket.on('loadtrack', loadTrack);
-               socket.on('playtrack', playTrack);
-               socket.on('trackinfo', addTrackInfo);
-               socket.on('artistmatched', function() {
-                       var feedback = amstrings[Math.floor(Math.random()*amstrings.length)];
-                       addFeedback(feedback, "correct");
-               });
-               socket.on('titlematched', function() {
-                       var feedback = tmstrings[Math.floor(Math.random()*tmstrings.length)];
-                       addFeedback(feedback, "correct");
-               });
-               socket.on('bothmatched', function() {
-                       var feedback = bmstrings[Math.floor(Math.random()*bmstrings.length)];
-                       addFeedback(feedback, "correct");
-               });
-               socket.on('nomatch', function() {
-                       var feedback = nmstrings[Math.floor(Math.random()*nmstrings.length)];
-                       addFeedback(feedback, "wrong");
-               });
-               socket.on('stoptrying', function() {
-                       addFeedback('You guessed both artist and title. Please wait...');
-               });
-               socket.on('noguesstime', function() {
-                       addFeedback('You have to wait the next song...');
-               });
-               socket.on('gameover', gameOver);
-               socket.on('status', setStatus);
-               socket.emit('getstatus');
-       };
-
-       var setStatus = function(data) {
-               if (data.status === 0) {
-                       cassetteAnimation(Date.now()+data.timeleft, true);
-               }
-               if (data.status === 1) {
-                       loadTrack(data);
-               }
-               addFeedback(states[data.status]);
-       };
-
-       // A new player joined the game
-       var userJoin = function(data) {
-               var msg = data.nickname+" joined the game";
-               var joinspan = $("<span class='join'></span>");
-               joinspan.text(msg);
-               addChatEntry(joinspan);
-               updateUsers(data);
-       };
-
-       // A user left the game
-       var userLeft = function(data) {
-               var leftmsg = data.nickname+" left the game";
-               var leftspan = $("<span class='left'></span>");
-               leftspan.text(leftmsg);
-               addChatEntry(leftspan);
-               updateUsers(data);
-       };
-
-       // Update the list of users
-       var updateUsers = function(data) {
-               DOM.users.empty();
-               var users = [];
-               for (var key in data.users) {
-                       users.push(data.users[key]);
-               }
-               users.sort(function(a, b) {return b.points - a.points;});
-               // Flag to test if our private recipient is in the list of active users
-               var found = false;
-               for (var i=0; i<users.length; i++) {
-                       var user = users[i];
-                       var li = $('<li></li>');
-                       var pvt = $('<span class="private label label-info">P</span>');
-                       var username = $('<span class="name"></span>').text(user.nickname);
-                       var points = $('<span class="points">('+user.points+')</span>');
-                       var roundrank = $('<span></span>');
-                       var roundpointsel = $('<span class="round-points"></span>');
-                       var guesstime = $('<span class="guess-time"></span>');
-                       li.append(pvt, username, points, roundrank, roundpointsel, guesstime);
-                       if (user.registered) {
-                               var href = 'href="/user/'+encodeURI(username.html())+'"';
-                               pvt.after('<a class="registered" target="_blank" '+href+'></a>');
-                       }
-                       DOM.users.append(li);
-                       if (pvtmsgto === user.nickname) {
-                               pvt.show();
-                               username.click(clearPrivate);
-                               found = true;
-                       }
-                       else {
-                               username.click(function() {
-                                       addPrivate($(this).text());
-                               });
-                       }
-                       if (nickname === user.nickname) {
-                               username.addClass("you");
-                               roundpoints = user.roundpoints;
-                               DOM.rank.text(i+1);
-                               DOM.points.text(user.points);
-                       }
-                       if (user.roundpoints > 0) {
-                               roundpointsel.text('+'+user.roundpoints);
-                               if (user.roundpoints === 1) {
-                                       username.addClass("matched");
-                               }
-                               else {
-                                       if (user.roundpoints > 3) {
-                                               var stand = 7 - user.roundpoints;
-                                               roundrank.addClass("round-rank stand"+stand);
-                                               var gtime = (user.guesstime / 1000).toFixed(1);
-                                               guesstime.text(gtime+" s");
-                                       }
-                                       username.addClass("correct");
-                               }
-                       }
-               }
-               if (!found && pvtmsgto) {
-                       var width = DOM.recipient.outerWidth(true) + 1;
-                       DOM.recipient.css('margin-right','0');
-                       DOM.recipient.text("");
-                       DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
-                       pvtmsgto = null;
-                       DOM.messagebox.focus();
-               }
-       };
-       
-       var addPrivate = function(usrname) {
-               if (pvtmsgto) {
-                       clearPrivate();
-               }
-               if (nickname === usrname) {
-                       return;
-               }
-               DOM.recipient.css('margin-right','4px');
-               DOM.recipient.text("To "+usrname+":");
-               var width = DOM.recipient.outerWidth(true) + 1;
-               DOM.recipient.hide();
-               DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();});
-               var el = $("span.name:econtains("+usrname.encodeEntities()+")");
-               el.prevAll(".private").show();
-               el.unbind('click');
-               el.click(clearPrivate);
-               pvtmsgto = usrname;
-               DOM.messagebox.focus();
-       };
-
-       var clearPrivate = function() {
-               var width = DOM.recipient.outerWidth(true) + 1;
-               DOM.recipient.css('margin-right','0');
-               DOM.recipient.text("");
-               DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
-               var el = $("span.name:econtains("+pvtmsgto.encodeEntities()+")");
-               el.prevAll(".private").hide();
-               el.unbind("click");
-               el.click(function() {
-                       addPrivate($(this).text());
-               });
-               pvtmsgto = null;
-               DOM.messagebox.focus();
-       };
-
-       // Receive a chat message
-       var getChatMessage = function(data) {
-               var prefix = data.from;
-               var msgspan = $("<span class='message'></span>");
-               if (data.to) {
-                       // Private Message
-                       prefix = (nickname === data.from) ? '(To '+data.to+')' : '(From '+prefix+')';
-                       msgspan.addClass("private");
-               }
-               var msg = prefix+": "+data.chatmsg;
-               msgspan.text(msg);
-               addChatEntry(msgspan);
-       };
-
-       var loadTrack = function(data) {
-               jplayer.jPlayer("mute");
-               jplayer.jPlayer("setMedia", {m4a: data.previewUrl});
-       };
-
-       // Play a track 
-       var playTrack = function(data) {
-               if (touchplay) {
-                       touchplay.removeClass("btn-danger disabled").addClass("btn-success");
-                       touchplay.html('<i class="icon-play icon-white"></i> Play');
-               }
-               jplayer.jPlayer("unmute");
-               jplayer.jPlayer("play");
-               updateUsers(data);
-               cassetteAnimation(Date.now()+30000, true);
-               if (data.counter === 1) {
-                       DOM.modal.modal('hide').empty();
-                       DOM.tracks.empty();
-               }
-               DOM.track.text(data.counter+'/'+data.tot);
-               addFeedback('What is this song?');
-       };
-
-       // Start cassette animation
-       var cassetteAnimation = function(endtime, forward) {
-               var millisleft = endtime - Date.now();
-               var secleft = millisleft / 1000;
-               var width, deg, offsetleft, offsetright, css;
-               if (forward) {
-                       width = 148 - (148*secleft/30);
-                       deg = 360 - (360*secleft/30);
-                       offsetleft = 44 - 24*secleft/30;
-                       offsetright = 130 - 24*secleft/30;
-                       DOM.progress.width(width);
-                       css = {
-                               '-moz-transform' : 'rotate('+deg+'deg)',
-                               '-webkit-transform' : 'rotate('+deg+'deg)',
-                               '-o-transform' : 'rotate('+deg+'deg)',
-                               '-ms-transform' : 'rotate('+deg+'deg)',
-                               'transform' : 'rotate('+deg+'deg)'
-                       };
-                       DOM.cassettewheels.css(css);
-                       DOM.tapeleft.css('left', offsetleft+'px');
-                       DOM.taperight.css('left', offsetright+'px');
-               }
-               else {
-                       width = 148*secleft/5;
-                       deg = 360*secleft/5;
-                       offsetleft = 20 + 24*secleft/5;
-                       offsetright = 106 + 24*secleft/5;
-                       DOM.progress.width(width);
-                       css = {
-                               '-moz-transform' : 'rotate('+deg+'deg)',
-                               '-webkit-transform' : 'rotate('+deg+'deg)',
-                               '-o-transform' : 'rotate('+deg+'deg)',
-                               '-ms-transform' : 'rotate('+deg+'deg)',
-                               'transform' : 'rotate('+deg+'deg)'
-                       };
-                       DOM.cassettewheels.css(css);
-                       DOM.tapeleft.css('left', offsetleft+'px');
-                       DOM.taperight.css('left', offsetright+'px');
-               }
-               if (forward) {
-                       DOM.countdown.text(secleft.toFixed(1));
-                       if (touchplay) {elapsedtime = 30 - Math.round(secleft);}
-               }
-               else {
-                       DOM.countdown.text(Math.round(secleft));
-               }
-               if (stopanimation || millisleft < 50) {
-                       return;
-               }
-               setTimeout(function() {cassetteAnimation(endtime, forward);}, 50);
-       };
-
-       // Add track info
-       var addTrackInfo = function(data) {
-               if (touchplay) {
-                       touchplay.removeClass("btn-success").addClass("btn-danger disabled");
-                       touchplay.html('<i class="icon-play icon-white"></i> Wait');
-               }
-               cassetteAnimation(Date.now()+5000, false);
-               var html = '<li class="bordered"><img class="artwork" src="'+data.artworkUrl+'"/>';
-               html += '<div class="info"><div class="artist">'+data.artistName+'</div>';
-               var titleattr = '';
-               var trackname = data.trackName;
-               if (data.trackName.length > 45) {
-                       titleattr = data.trackName.replace(/"/g, "&quot;");
-                       trackname = data.trackName.substring(0,42) + '...';
-               }
-               html += '<div class="title" title="'+titleattr+'">'+trackname+'</div></div>';
-               var attrs = '';
-               var rp = '';
-               if (roundpoints > 0) {
-                       rp = '+'+roundpoints;
-                       if (roundpoints > 3) {
-                               var stand = 7 - roundpoints;
-                               attrs += 'class="round-rank stand'+stand+'"';
-                       }
-               }
-               html += '<div '+attrs+'></div><div class="round-points">'+rp+'</div>';
-               html += '<a target="_blank" href="'+data.trackViewUrl+'">';
-               html += '<img src="/static/images/itunes.png"/></a></li>';
-               DOM.tracks.prepend($(html));
-       };
-
-       // Game over countdown
-       var countDown = function(endtime) {
-               var millisleft = endtime - Date.now();
-               var secleft = millisleft / 1000;
-               $('.modal-footer span').text(Math.round(secleft));
-               if (millisleft < 200) {
-                       return;
-               }
-               setTimeout(function() {countDown(endtime);}, 200);
-       };
-
-       var gameOver = function(data) {
-               var html = '<div class="modal-header"><h3>Game Over</h3></div>';
-               html += '<div class="modal-body"><table class="table table-striped scoreboard">';
-               html += '<thead><tr><th>#</th><th>Name</th><th>Points</th>';
-               html += '<th><div class="cups stand1"></div></th><th><div class="cups stand2"></div></th>';
-               html += '<th><div class="cups stand3"></div></th><th>Guessed</th><th>Mean time</th>';
-               html += '</thead><tbody>';
-               for(var i=0;i<3;i++) {
-                       if (data.users[i]) {
-                               var playername = data.users[i].nickname.encodeEntities();
-                               html += '<tr><td><div class="medals rank'+(i+1)+'"></div></td>';
-                               html += '<td class="name">'+playername+'</td>';
-                               html += '<td>'+data.users[i].points+'</td>';
-                               html += '<td>'+data.users[i].golds+'</td><td>'+data.users[i].silvers+'</td>';
-                               html += '<td>'+data.users[i].bronzes+'</td><td>'+data.users[i].guessed+'</td>';
-                               var meantime = "N/A";
-                               if (data.users[i].guessed !== 0) {
-                                       meantime = data.users[i].totguesstime / data.users[i].guessed;
-                                       meantime = (meantime / 1000).toFixed(1)+" s";
-                               }
-                               html += '<td>'+meantime+'</td></tr>';
-                       }
-               }
-               html +='</tbody></table></div>';
-               html += '<div class="modal-footer">A new game will start in <span></span> second/s</div>';
-               DOM.modal.append($(html));
-               DOM.modal.modal('show');
-               countDown(Date.now()+10000);
-       };
-
-       // Let the user know when he / she has disconnected
-       var disconnect = function() {
-               stopanimation = true;
-               jplayer.jPlayer("stop");
-               var errormsg = "ERROR: You have disconnected.";
-               var errorspan = $("<span class='error'></span>");
-               errorspan.text(errormsg);
-               addChatEntry(errorspan);
-               addFeedback('Something wrong happened');
-               DOM.users.empty();
-       };
-
-       // Add a chat entry, whether message, notification, etc.
-       var addChatEntry = function(childNode) {
-               var li = $("<li class='entry'></li>");
-               li.append(childNode);
-               DOM.chat.append(li);
-               DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
-       };
-
-       var hideChat = function() {
-               DOM.togglechat.text("Show chat").unbind('click');
-               DOM.chatwrapper.toggle(300);
-               DOM.tracks.animate({maxHeight:"434px"}, 300);
-               DOM.togglechat.click(showChat);
-       };
-
-       var showChat = function() {
-               DOM.togglechat.text("Hide chat").unbind('click');
-               DOM.chatwrapper.toggle(300);
-               DOM.tracks.animate({maxHeight:"240px"}, 300, function() {
-                       DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
-               });
-               DOM.togglechat.click(hideChat);
-       };
-
-       var addFeedback = function(txt, style) {
-               if (typeof style === 'string') {
-                       var fbspan = $('<span class="'+style+'"></span>');
-                       fbspan.text(txt);
-                       DOM.feedback.html(fbspan);
-                       DOM.guessbox.addClass(style);
-                       setTimeout(function() {DOM.guessbox.removeClass(style);}, 350); 
-                       return;
-               }
-               DOM.feedback.text(txt);
-       };
-
-       var addVolumeControl = function() {
-               var volumebutton = $('<div id="volume-button">'+
-                                                               '<a class="button"><div id="icon" class="volume-high"></div></a>'+
-                                                               '<div id="volume-slider">'+ // Outer background
-                                                                       '<div id="volume-total"></div>'+ // Rail
-                                                                       '<div id="volume-current"></div>'+ // Current volume
-                                                                       '<div id="volume-handle"></div>'+ // Handle
-                                                               '</div>'+
-                                                       '</div>').appendTo("#volume");
-               var icon = volumebutton.find('#icon');
-               var volumeslider = volumebutton.find('#volume-slider');
-               var volumetotal = volumebutton.find('#volume-total');
-               var volumecurrent = volumebutton.find('#volume-current');
-               var volumehandle = volumebutton.find('#volume-handle');
-               var mouseisdown = false;
-               var mouseisover = false;
-               var oldvalue = 1;
-               var clicked = false;
-
-               var positionVolumeHandle = function(volume) {
-                       if (!volumeslider.is(':visible')) {
-                               volumeslider.show();
-                               positionVolumeHandle(volume);
-                               volumeslider.hide();
-                               return;
-                       }
-                       var totalheight = volumetotal.height();
-                       var totalposition = volumetotal.position();
-                       var newtop = totalheight - (totalheight * volume);
-                       volumehandle.css('top', totalposition.top + newtop - (volumehandle.height() / 2));
-                       volumecurrent.height(totalheight - newtop );
-                       volumecurrent.css('top', totalposition.top + newtop);
-               };
-
-               var handleIcon = function (volume) {
-                       if (volume === 0) {
-                               icon.removeClass().addClass('volume-none');
-                       }
-                       else if (volume <= 0.33) {
-                               icon.removeClass().addClass('volume-low');
-                       }
-                       else if (volume <= 0.66) {
-                               icon.removeClass().addClass('volume-medium');
-                       }
-                       else {
-                               icon.removeClass().addClass('volume-high');
-                       }
-               };
-
-               var setVolume = function(volume) {
-                       handleIcon(volume);
-                       oldvalue = volume;
-                       jplayer.jPlayer("volume", volume);
-               };
-
-               var handleVolumeMove = function(e) {
-                       var totaloffset = volumetotal.offset();
-                       var newy = e.pageY - totaloffset.top;
-                       var railheight = volumetotal.height();
-                       var totalTop = parseInt(volumetotal.css('top').replace(/px/,''),10);
-                       var volume = (railheight - newy) / railheight;          
-
-                       if (newy < 0) {
-                               newy = 0;
-                       }
-                       else if (newy > railheight) {
-                               newy = railheight;
-                       }
-
-                       volumehandle.css('top', totalTop + newy - (volumehandle.height() / 2));
-                       volumecurrent.height(railheight - newy);
-                       volumecurrent.css('top',newy+totalTop);
-
-                       volume = Math.max(0,volume);
-                       volume = Math.min(volume,1);
-
-                       setVolume(volume);
-                       
-                       var d = new Date();
-                       d.setTime(d.getTime() + 31536000000); // One year in milliseconds
-                       document.cookie = "volume="+volume+";path=/;expires="+d.toGMTString()+";";
-               };
-
-               var loadFromCookie = function() {
-                       if (/volume\s*\=/.test(document.cookie)) {
-                               var value = document.cookie.replace(/.*volume\s*\=\s*([^;]*);?.*/, "$1");
-                               value = parseFloat(value);
-                               positionVolumeHandle(value);
-                               setVolume(value);
-                       }
-                       else {
-                               positionVolumeHandle(1);
-                       }
-               };
-
-               volumebutton.hover(function() {
-                       volumeslider.show();
-                       mouseisover = true;
-               }, function() {
-                       mouseisover = false;
-                       if (!mouseisdown) {
-                               volumeslider.hide();
-                       }
-               });
-               
-               volumeslider.on('mouseover', function() {
-                       mouseisover = true;
-               }).on('mousedown', function (e) {
-                       handleVolumeMove(e);
-                       mouseisdown = true;
-                       return false;
-               });
-
-               $(document).on('mouseup', function (e) {
-                       mouseisdown = false;
-                       if (!mouseisover) {
-                               volumeslider.hide();
-                       }
-               }).on('mousemove', function (e) {
-                       if (mouseisdown) {
-                               handleVolumeMove(e);
-                       }
-               });
-
-               volumebutton.find('.button').click(function() {
-                       if (!clicked) {
-                               clicked = true;
-                               if (oldvalue !== 0) {
-                                       jplayer.jPlayer("volume", 0);
-                                       positionVolumeHandle(0);
-                                       handleIcon(0);
-                               }
-                       }
-                       else {
-                               clicked = false;
-                               if (oldvalue !== 0) {
-                                       jplayer.jPlayer("volume", oldvalue);
-                                       positionVolumeHandle(oldvalue);
-                                       handleIcon(oldvalue);
-                               }
-                       }
-               });
-               loadFromCookie();
-       };
-       
-       var setVariables = function() {
-               DOM.modal = $('#modal');
-               DOM.guessbox = $('#guess');
-               DOM.users = $('#users');
-               DOM.rank = $('#summary .rank');
-               DOM.points = $('#summary .points');
-               DOM.messagebox = $('#message');
-               DOM.recipient = $('#recipient');
-               DOM.tracks = $('#tracks');
-               DOM.track = $('#summary .track');
-               DOM.progress = $('#progress');
-               DOM.cassettewheels = $('#cassette .wheel');
-               DOM.tapeleft = $('#tape-left');
-               DOM.taperight = $('#tape-right');
-               DOM.countdown = $('#countdown');
-               DOM.chat = $('#chat');
-               DOM.feedback = $('#feedback');
-               DOM.togglechat = $('#toggle-chat');
-               DOM.chatwrapper = $('#chat-outer-wrapper');
-       };
-
-       // Set up the room.
-       $(function() {
-               setVariables();
-               DOM.modal.modal({keyboard:false,show:false,backdrop:"static"});
-               DOM.togglechat.click(hideChat);
-               if ($.browser.mozilla) {
-                       // Block ESC button in firefox (it breaks all socket connection).
-                       $(document).keypress(function(event) {
-                               if(event.keyCode === 27) {
-                                       return false;
-                               }
-                       });
-               }
-               socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
-               socket.on("connect", function() {
-                       jplayer = $("#player").jPlayer({
-                               ready: function() {
-                                       socket.emit('loggedin', function(data) {
-                                               if (data) {
-                                                       nickname = data;
-                                                       socket.emit('joinroom', roomname);
-                                               }
-                                               else {
-                                                       joinAnonymously();
-                                               }
-                                       });
-                                       if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) {
-                                               addVolumeControl();
-                                       }
-                                       else {
-                                               var touchbackdrop = $('<div id="touch-backdrop">'+
-                                                       '<button id="touch-play" class="btn btn-danger disabled">'+
-                                                               '<i class="icon-play icon-white"></i> Wait'+
-                                                       '</button></div>').appendTo("#cassette");
-                                               touchplay = $('#touch-play');
-                                               touchplay.click(function() {
-                                                       if (!$(this).hasClass("btn-danger")) {
-                                                               touchplay = null;
-                                                               jplayer.jPlayer('play', elapsedtime);
-                                                               touchbackdrop.remove();
-                                                       }
-                                               });
-                                       }
-                               },
-                               swfPath: "/static/swf/",
-                               //solution: "flash, html",
-                               supplied: "m4a",
-                               preload: "auto",
-                               volume: 1
-                       });
-               });
-               socket.on('alreadyinaroom', alreadyInARoom);
-               socket.on('invalidnickname', invalidNickName);
-               socket.on('ready', ready);
-               socket.on("disconnect", disconnect);
-       });
+
+    var touchplay = null;
+    var elapsedtime = 0;
+    var jplayer = null;
+    var nickname = null;
+    var socket = null;
+    var pvtmsgto = null;
+    var roundpoints = 0;
+    var stopanimation = false;
+    var states = ['A song is already playing, please wait for the next one...',
+            'Game is about to start...', 'Game is over', 'New game will start soon...'];
+    var tmstrings = ['Yes, you guessed the title. Who is the artist?', 'Now tell me the artist!',
+                    'Correct, do you also know the artist?'];
+    var amstrings = ['Yes, that\'s the artist. What about the title?', 'Exactly, now tell me the title!',
+                    'Do you also know the title?'];
+    var bmstrings = ['Yeah true! do you like this track?', 'Good job!', 'Great!',
+                    'Very well done!', 'Exactly!', 'Excellent!', 'Woohoo!'];
+    var nmstrings = ['Nope, sorry!', 'No way!', 'Fail', 'Nope', 'No', 'That\'s wrong', 'What?!',
+                    'Wrong', 'Haha, what?!', 'You kidding?', 'Don\'t make me laugh', 'You mad?',
+                    'Try again'];
+    var historyvalues = [];
+    var historycursor = 0;
+    var DOM = {};
+
+    String.prototype.encodeEntities = function() {
+        return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+    };
+
+    // Exact match version of jQuery :contains selector
+    $.expr[":"].econtains = function(obj, index, meta, stack) {
+        return $(obj).html() === meta[0].replace(/^[\s\S]+:econtains\(([\s\S]+)\)$/, "$1");
+    };
+
+    // Prompt for name and send it.
+    var joinAnonymously = function(msg) {
+        if (/nickname\s*\=/.test(document.cookie) && !msg) {
+            nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1"));
+            socket.emit('joinanonymously', nickname, roomname);
+        }
+        else {
+            if (!$('body').hasClass('modal-open')) {
+                var html = '<div class="modal-header"><h3>You are joining the '+roomname+' room</h3></div>';
+                html += '<div class="modal-body"><p>'+(msg || "What's your name?")+'</p></div>';
+                html += '<div class="modal-footer">';
+                html += '<input id="login" class="" type="text" name="nickname" />';
+                html += '<button id="join" class="btn btn-primary">';
+                html += '<i class="icon-user icon-white"></i> Join the game</button></div>';
+
+                $(html).appendTo(DOM.modal);
+                var login = $('#login');
+                var button = $('#join');
+                button.click(function() {
+                    var val = $.trim(login.val());
+                    if (val !== "") {
+                        nickname = val;
+                        socket.emit('joinanonymously', nickname, roomname);
+                    }
+                    else {
+                        var txt = "Nickname can't be empty.";
+                        invalidNickName('<span class="label label-important">'+txt+'</span>');
+                    }
+                    login.val("");
+                });
+                login.keyup(function(event) {
+                    if (event.keyCode === 13) {
+                        button.click();
+                    }
+                });
+                DOM.modal.modal('show');
+                DOM.modal.on('shown', function() {
+                    login.focus();
+                });
+            }
+            else {
+                $('.modal-body p').html(msg);
+                $('#login').focus();
+            }
+        }
+    };
+
+    // Your submitted name was invalid
+    var invalidNickName = function(feedback) {
+        joinAnonymously(feedback+"<br/>Try with another one:");
+    };
+
+    /* Triggered when a logged user tries to join a room from another tab or another browser
+        and he is already in a room */
+    var alreadyInARoom = function() {
+        var html = '<div class="modal-header"><h3>Already in a room</h3></div>';
+        html += '<div class="modal-body"><div class="alert alert-error alert-block">';
+        html += '<h4 class="alert-heading">Warning!</h4>You are already in a room.<br/>';
+        html += 'Leave the other room and refresh this page or close this one.</div></div>';
+        $(html).appendTo(DOM.modal);
+        DOM.modal.modal('show');
+    };
+
+    // You joined the room
+    var ready = function(usersData, trackscount, loggedin) {
+        if (!loggedin && !/nickname\s*\=/.test(document.cookie)) {
+            document.cookie = "nickname="+escape(nickname)+";path=/;";
+        }
+        DOM.modal.modal('hide').empty();
+        $('#total-tracks span').text(trackscount);
+        var msg = nickname+" joined the game";
+        var joinspan = $("<span class='join'></span>");
+        joinspan.text(msg);
+        addChatEntry(joinspan);
+        updateUsers(usersData);
+
+        DOM.messagebox.keydown(function(event) {
+            if (event.keyCode === 13) {
+                var val = $.trim(DOM.messagebox.val());
+                if (val !== "") {
+                    if (pvtmsgto) {
+                        socket.emit('sendchatmsg', val, pvtmsgto);
+                    }
+                    else {
+                        socket.emit('sendchatmsg', val);
+                    }
+                }
+                DOM.messagebox.val("");
+            }
+        });
+
+        DOM.guessbox.keydown(function(event) {
+            switch (event.keyCode) {
+                case 13: // return
+                    var guess = $.trim(DOM.guessbox.val());
+                    if (guess !== "") {
+                        socket.emit('guess', guess.toLowerCase());
+                        historyvalues.push(guess);
+                        if (historyvalues.length > 20) {
+                            historyvalues.splice(0, 1);
+                        }
+                        historycursor = historyvalues.length;
+                    }
+                    DOM.guessbox.val("");
+                    break;
+                case 38: // up-arrow
+                    if (historycursor > 0) {
+                        DOM.guessbox.val(historyvalues[--historycursor]);
+                    }
+                    break;
+                case 40: // down-arrow
+                    if (historycursor < historyvalues.length - 1) {
+                        DOM.guessbox.val(historyvalues[++historycursor]);
+                    }
+                    else {
+                        historycursor = historyvalues.length;
+                        DOM.guessbox.val("");
+                    }
+            }
+        });
+
+        DOM.guessbox.focus();
+
+        socket.on('newuser', userJoin);
+        socket.on('userleft', userLeft);
+        socket.on('updateusers', updateUsers);
+        socket.on('chatmsg', getChatMessage);
+        socket.on('loadtrack', loadTrack);
+        socket.on('playtrack', playTrack);
+        socket.on('trackinfo', addTrackInfo);
+        socket.on('artistmatched', function() {
+            var feedback = amstrings[Math.floor(Math.random()*amstrings.length)];
+            addFeedback(feedback, "correct");
+        });
+        socket.on('titlematched', function() {
+            var feedback = tmstrings[Math.floor(Math.random()*tmstrings.length)];
+            addFeedback(feedback, "correct");
+        });
+        socket.on('bothmatched', function() {
+            var feedback = bmstrings[Math.floor(Math.random()*bmstrings.length)];
+            addFeedback(feedback, "correct");
+        });
+        socket.on('nomatch', function() {
+            var feedback = nmstrings[Math.floor(Math.random()*nmstrings.length)];
+            addFeedback(feedback, "wrong");
+        });
+        socket.on('stoptrying', function() {
+            addFeedback('You guessed both artist and title. Please wait...');
+        });
+        socket.on('noguesstime', function() {
+            addFeedback('You have to wait the next song...');
+        });
+        socket.on('gameover', gameOver);
+        socket.on('status', setStatus);
+        socket.emit('getstatus');
+    };
+
+    var setStatus = function(data) {
+        if (data.status === 0) {
+            cassetteAnimation(Date.now()+data.timeleft, true);
+        }
+        if (data.status === 1) {
+            loadTrack(data.previewUrl);
+        }
+        addFeedback(states[data.status]);
+    };
+
+    // A new player joined the game
+    var userJoin = function(username, usersData) {
+        var msg = username+" joined the game";
+        var joinspan = $("<span class='join'></span>");
+        joinspan.text(msg);
+        addChatEntry(joinspan);
+        updateUsers(usersData);
+    };
+
+    // A user left the game
+    var userLeft = function(username, usersData) {
+        var leftmsg = username+" left the game";
+        var leftspan = $("<span class='left'></span>");
+        leftspan.text(leftmsg);
+        addChatEntry(leftspan);
+        updateUsers(usersData);
+    };
+
+    // Update the list of users
+    var updateUsers = function(usersData) {
+        DOM.users.empty();
+        var users = [];
+        for (var key in usersData) {
+            users.push(usersData[key]);
+        }
+        users.sort(function(a, b) {return b.points - a.points;});
+        // Flag to test if our private recipient is in the list of active users
+        var found = false;
+        for (var i=0; i<users.length; i++) {
+            var user = users[i];
+            var li = $('<li></li>');
+            var pvt = $('<span class="private label label-info">P</span>');
+            var username = $('<span class="name"></span>').text(user.nickname);
+            var points = $('<span class="points">('+user.points+')</span>');
+            var roundrank = $('<span></span>');
+            var roundpointsel = $('<span class="round-points"></span>');
+            var guesstime = $('<span class="guess-time"></span>');
+            li.append(pvt, username, points, roundrank, roundpointsel, guesstime);
+            if (user.registered) {
+                var href = 'href="/user/'+encodeURI(username.html())+'"';
+                pvt.after('<a class="registered" target="_blank" '+href+'></a>');
+            }
+            DOM.users.append(li);
+            if (pvtmsgto === user.nickname) {
+                pvt.show();
+                username.click(clearPrivate);
+                found = true;
+            }
+            else {
+                username.click(function() {
+                    addPrivate($(this).text());
+                });
+            }
+            if (nickname === user.nickname) {
+                username.addClass("you");
+                roundpoints = user.roundpoints;
+                DOM.rank.text(i+1);
+                DOM.points.text(user.points);
+            }
+            if (user.roundpoints > 0) {
+                roundpointsel.text('+'+user.roundpoints);
+                if (user.roundpoints === 1) {
+                    username.addClass("matched");
+                }
+                else {
+                    if (user.roundpoints > 3) {
+                        var stand = 7 - user.roundpoints;
+                        roundrank.addClass("round-rank stand"+stand);
+                        var gtime = (user.guesstime / 1000).toFixed(1);
+                        guesstime.text(gtime+" s");
+                    }
+                    username.addClass("correct");
+                }
+            }
+        }
+        if (!found && pvtmsgto) {
+            var width = DOM.recipient.outerWidth(true) + 1;
+            DOM.recipient.css('margin-right','0');
+            DOM.recipient.text("");
+            DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
+            pvtmsgto = null;
+            DOM.messagebox.focus();
+        }
+    };
+
+    var addPrivate = function(usrname) {
+        if (pvtmsgto) {
+            clearPrivate();
+        }
+        if (nickname === usrname) {
+            return;
+        }
+        DOM.recipient.css('margin-right','4px');
+        DOM.recipient.text("To "+usrname+":");
+        var width = DOM.recipient.outerWidth(true) + 1;
+        DOM.recipient.hide();
+        DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();});
+        var el = $("span.name:econtains("+usrname.encodeEntities()+")");
+        el.prevAll(".private").show();
+        el.unbind('click');
+        el.click(clearPrivate);
+        pvtmsgto = usrname;
+        DOM.messagebox.focus();
+    };
+
+    var clearPrivate = function() {
+        var width = DOM.recipient.outerWidth(true) + 1;
+        DOM.recipient.css('margin-right','0');
+        DOM.recipient.text("");
+        DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
+        var el = $("span.name:econtains("+pvtmsgto.encodeEntities()+")");
+        el.prevAll(".private").hide();
+        el.unbind("click");
+        el.click(function() {
+            addPrivate($(this).text());
+        });
+        pvtmsgto = null;
+        DOM.messagebox.focus();
+    };
+
+    // Receive a chat message
+    var getChatMessage = function(chatmsg, from, to) {
+        var prefix = from;
+        var msgspan = $("<span class='message'></span>");
+        if (to) {
+            // Private Message
+            prefix = (nickname === from) ? '(To '+to+')' : '(From '+prefix+')';
+            msgspan.addClass("private");
+        }
+        var msg = prefix+": "+chatmsg;
+        msgspan.text(msg);
+        addChatEntry(msgspan);
+    };
+
+    var loadTrack = function(previewUrl) {
+        jplayer.jPlayer("mute");
+        jplayer.jPlayer("setMedia", {m4a: previewUrl});
+    };
+
+    // Play a track
+    var playTrack = function(data) {
+        if (touchplay) {
+            touchplay.removeClass("btn-danger disabled").addClass("btn-success");
+            touchplay.html('<i class="icon-play icon-white"></i> Play');
+        }
+        jplayer.jPlayer("unmute");
+        jplayer.jPlayer("play");
+        updateUsers(data.users);
+        cassetteAnimation(Date.now()+30000, true);
+        if (data.counter === 1) {
+            DOM.modal.modal('hide').empty();
+            DOM.tracks.empty();
+        }
+        DOM.track.text(data.counter+'/'+data.tot);
+        addFeedback('What is this song?');
+    };
+
+    // Start cassette animation
+    var cassetteAnimation = function(endtime, forward) {
+        var millisleft = endtime - Date.now();
+        var secleft = millisleft / 1000;
+        var width, deg, offsetleft, offsetright, css;
+        if (forward) {
+            width = 148 - (148*secleft/30);
+            deg = 360 - (360*secleft/30);
+            offsetleft = 44 - 24*secleft/30;
+            offsetright = 130 - 24*secleft/30;
+            DOM.progress.width(width);
+            css = {
+                '-moz-transform' : 'rotate('+deg+'deg)',
+                '-webkit-transform' : 'rotate('+deg+'deg)',
+                '-o-transform' : 'rotate('+deg+'deg)',
+                '-ms-transform' : 'rotate('+deg+'deg)',
+                'transform' : 'rotate('+deg+'deg)'
+            };
+            DOM.cassettewheels.css(css);
+            DOM.tapeleft.css('left', offsetleft+'px');
+            DOM.taperight.css('left', offsetright+'px');
+        }
+        else {
+            width = 148*secleft/5;
+            deg = 360*secleft/5;
+            offsetleft = 20 + 24*secleft/5;
+            offsetright = 106 + 24*secleft/5;
+            DOM.progress.width(width);
+            css = {
+                '-moz-transform' : 'rotate('+deg+'deg)',
+                '-webkit-transform' : 'rotate('+deg+'deg)',
+                '-o-transform' : 'rotate('+deg+'deg)',
+                '-ms-transform' : 'rotate('+deg+'deg)',
+                'transform' : 'rotate('+deg+'deg)'
+            };
+            DOM.cassettewheels.css(css);
+            DOM.tapeleft.css('left', offsetleft+'px');
+            DOM.taperight.css('left', offsetright+'px');
+        }
+        if (forward) {
+            DOM.countdown.text(secleft.toFixed(1));
+            if (touchplay) {elapsedtime = 30 - Math.round(secleft);}
+        }
+        else {
+            DOM.countdown.text(Math.round(secleft));
+        }
+        if (stopanimation || millisleft < 50) {
+            return;
+        }
+        setTimeout(function() {cassetteAnimation(endtime, forward);}, 50);
+    };
+
+    // Add track info
+    var addTrackInfo = function(data) {
+        if (touchplay) {
+            touchplay.removeClass("btn-success").addClass("btn-danger disabled");
+            touchplay.html('<i class="icon-play icon-white"></i> Wait');
+        }
+        cassetteAnimation(Date.now()+5000, false);
+        var html = '<li class="bordered"><img class="artwork" src="'+data.artworkUrl+'"/>';
+        html += '<div class="info"><div class="artist">'+data.artistName+'</div>';
+        var titleattr = '';
+        var trackname = data.trackName;
+        if (data.trackName.length > 45) {
+            titleattr = data.trackName.replace(/"/g, "&quot;");
+            trackname = data.trackName.substring(0,42) + '...';
+        }
+        html += '<div class="title" title="'+titleattr+'">'+trackname+'</div></div>';
+        var attrs = '';
+        var rp = '';
+        if (roundpoints > 0) {
+            rp = '+'+roundpoints;
+            if (roundpoints > 3) {
+                var stand = 7 - roundpoints;
+                attrs += 'class="round-rank stand'+stand+'"';
+            }
+        }
+        html += '<div '+attrs+'></div><div class="round-points">'+rp+'</div>';
+        html += '<a target="_blank" href="'+data.trackViewUrl+'">';
+        html += '<img src="/static/images/itunes.png"/></a></li>';
+        DOM.tracks.prepend($(html));
+    };
+
+    // Game over countdown
+    var countDown = function(endtime) {
+        var millisleft = endtime - Date.now();
+        var secleft = millisleft / 1000;
+        $('.modal-footer span').text(Math.round(secleft));
+        if (millisleft < 200) {
+            return;
+        }
+        setTimeout(function() {countDown(endtime);}, 200);
+    };
+
+    var gameOver = function(podium) {
+        var html = '<div class="modal-header"><h3>Game Over</h3></div>';
+        html += '<div class="modal-body"><table class="table table-striped scoreboard">';
+        html += '<thead><tr><th>#</th><th>Name</th><th>Points</th>';
+        html += '<th><div class="cups stand1"></div></th><th><div class="cups stand2"></div></th>';
+        html += '<th><div class="cups stand3"></div></th><th>Guessed</th><th>Mean time</th>';
+        html += '</thead><tbody>';
+        for(var i=0;i<3;i++) {
+            if (podium[i]) {
+                var playername = podium[i].nickname.encodeEntities();
+                html += '<tr><td><div class="medals rank'+(i+1)+'"></div></td>';
+                html += '<td class="name">'+playername+'</td>';
+                html += '<td>'+podium[i].points+'</td>';
+                html += '<td>'+podium[i].golds+'</td><td>'+podium[i].silvers+'</td>';
+                html += '<td>'+podium[i].bronzes+'</td><td>'+podium[i].guessed+'</td>';
+                var meantime = "N/A";
+                if (podium[i].guessed !== 0) {
+                    meantime = podium[i].totguesstime / podium[i].guessed;
+                    meantime = (meantime / 1000).toFixed(1)+" s";
+                }
+                html += '<td>'+meantime+'</td></tr>';
+            }
+        }
+        html +='</tbody></table></div>';
+        html += '<div class="modal-footer">A new game will start in <span></span> second/s</div>';
+        DOM.modal.append($(html));
+        DOM.modal.modal('show');
+        countDown(Date.now()+10000);
+    };
+
+    // Let the user know when he / she has disconnected
+    var disconnect = function() {
+        stopanimation = true;
+        jplayer.jPlayer("stop");
+        var errormsg = "ERROR: You have disconnected.";
+        var errorspan = $("<span class='error'></span>");
+        errorspan.text(errormsg);
+        addChatEntry(errorspan);
+        addFeedback('Something wrong happened');
+        DOM.users.empty();
+    };
+
+    // Add a chat entry, whether message, notification, etc.
+    var addChatEntry = function(childNode) {
+        var li = $("<li class='entry'></li>");
+        li.append(childNode);
+        DOM.chat.append(li);
+        DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
+    };
+
+    var hideChat = function() {
+        DOM.togglechat.text("Show chat").unbind('click');
+        DOM.chatwrapper.toggle(300);
+        DOM.tracks.animate({maxHeight:"434px"}, 300);
+        DOM.togglechat.click(showChat);
+    };
+
+    var showChat = function() {
+        DOM.togglechat.text("Hide chat").unbind('click');
+        DOM.chatwrapper.toggle(300);
+        DOM.tracks.animate({maxHeight:"240px"}, 300, function() {
+            DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
+        });
+        DOM.togglechat.click(hideChat);
+    };
+
+    var addFeedback = function(txt, style) {
+        if (typeof style === 'string') {
+            var fbspan = $('<span class="'+style+'"></span>');
+            fbspan.text(txt);
+            DOM.feedback.html(fbspan);
+            DOM.guessbox.addClass(style);
+            setTimeout(function() {DOM.guessbox.removeClass(style);}, 350);
+            return;
+        }
+        DOM.feedback.text(txt);
+    };
+
+    var addVolumeControl = function() {
+        var volumebutton = $('<div id="volume-button">'+
+                                '<a class="button"><div id="icon" class="volume-high"></div></a>'+
+                                '<div id="volume-slider">'+ // Outer background
+                                    '<div id="volume-total"></div>'+ // Rail
+                                    '<div id="volume-current"></div>'+ // Current volume
+                                    '<div id="volume-handle"></div>'+ // Handle
+                                '</div>'+
+                            '</div>').appendTo("#volume");
+        var icon = volumebutton.find('#icon');
+        var volumeslider = volumebutton.find('#volume-slider');
+        var volumetotal = volumebutton.find('#volume-total');
+        var volumecurrent = volumebutton.find('#volume-current');
+        var volumehandle = volumebutton.find('#volume-handle');
+        var mouseisdown = false;
+        var mouseisover = false;
+        var oldvalue = 1;
+        var clicked = false;
+
+        var positionVolumeHandle = function(volume) {
+            if (!volumeslider.is(':visible')) {
+                volumeslider.show();
+                positionVolumeHandle(volume);
+                volumeslider.hide();
+                return;
+            }
+            var totalheight = volumetotal.height();
+            var totalposition = volumetotal.position();
+            var newtop = totalheight - (totalheight * volume);
+            volumehandle.css('top', totalposition.top + newtop - (volumehandle.height() / 2));
+            volumecurrent.height(totalheight - newtop );
+            volumecurrent.css('top', totalposition.top + newtop);
+        };
+
+        var handleIcon = function (volume) {
+            if (volume === 0) {
+                icon.removeClass().addClass('volume-none');
+            }
+            else if (volume <= 0.33) {
+                icon.removeClass().addClass('volume-low');
+            }
+            else if (volume <= 0.66) {
+                icon.removeClass().addClass('volume-medium');
+            }
+            else {
+                icon.removeClass().addClass('volume-high');
+            }
+        };
+
+        var setVolume = function(volume) {
+            handleIcon(volume);
+            oldvalue = volume;
+            jplayer.jPlayer("volume", volume);
+        };
+
+        var handleVolumeMove = function(e) {
+            var totaloffset = volumetotal.offset();
+            var newy = e.pageY - totaloffset.top;
+            var railheight = volumetotal.height();
+            var totalTop = parseInt(volumetotal.css('top').replace(/px/,''),10);
+            var volume = (railheight - newy) / railheight;
+
+            if (newy < 0) {
+                newy = 0;
+            }
+            else if (newy > railheight) {
+                newy = railheight;
+            }
+
+            volumehandle.css('top', totalTop + newy - (volumehandle.height() / 2));
+            volumecurrent.height(railheight - newy);
+            volumecurrent.css('top',newy+totalTop);
+
+            volume = Math.max(0,volume);
+            volume = Math.min(volume,1);
+
+            setVolume(volume);
+
+            var d = new Date();
+            d.setTime(d.getTime() + 31536000000); // One year in milliseconds
+            document.cookie = "volume="+volume+";path=/;expires="+d.toGMTString()+";";
+        };
+
+        var loadFromCookie = function() {
+            if (/volume\s*\=/.test(document.cookie)) {
+                var value = document.cookie.replace(/.*volume\s*\=\s*([^;]*);?.*/, "$1");
+                value = parseFloat(value);
+                positionVolumeHandle(value);
+                setVolume(value);
+            }
+            else {
+                positionVolumeHandle(1);
+            }
+        };
+
+        volumebutton.hover(function() {
+            volumeslider.show();
+            mouseisover = true;
+        }, function() {
+            mouseisover = false;
+            if (!mouseisdown) {
+                volumeslider.hide();
+            }
+        });
+
+        volumeslider.on('mouseover', function() {
+            mouseisover = true;
+        }).on('mousedown', function (e) {
+            handleVolumeMove(e);
+            mouseisdown = true;
+            return false;
+        });
+
+        $(document).on('mouseup', function (e) {
+            mouseisdown = false;
+            if (!mouseisover) {
+                volumeslider.hide();
+            }
+        }).on('mousemove', function (e) {
+            if (mouseisdown) {
+                handleVolumeMove(e);
+            }
+        });
+
+        volumebutton.find('.button').click(function() {
+            if (!clicked) {
+                clicked = true;
+                if (oldvalue !== 0) {
+                    jplayer.jPlayer("volume", 0);
+                    positionVolumeHandle(0);
+                    handleIcon(0);
+                }
+            }
+            else {
+                clicked = false;
+                if (oldvalue !== 0) {
+                    jplayer.jPlayer("volume", oldvalue);
+                    positionVolumeHandle(oldvalue);
+                    handleIcon(oldvalue);
+                }
+            }
+        });
+        loadFromCookie();
+    };
+
+    var setVariables = function() {
+        DOM.modal = $('#modal');
+        DOM.guessbox = $('#guess');
+        DOM.users = $('#users');
+        DOM.rank = $('#summary .rank');
+        DOM.points = $('#summary .points');
+        DOM.messagebox = $('#message');
+        DOM.recipient = $('#recipient');
+        DOM.tracks = $('#tracks');
+        DOM.track = $('#summary .track');
+        DOM.progress = $('#progress');
+        DOM.cassettewheels = $('#cassette .wheel');
+        DOM.tapeleft = $('#tape-left');
+        DOM.taperight = $('#tape-right');
+        DOM.countdown = $('#countdown');
+        DOM.chat = $('#chat');
+        DOM.feedback = $('#feedback');
+        DOM.togglechat = $('#toggle-chat');
+        DOM.chatwrapper = $('#chat-outer-wrapper');
+    };
+
+    // Set up the room.
+    $(function() {
+        setVariables();
+        DOM.modal.modal({keyboard:false,show:false,backdrop:"static"});
+        DOM.togglechat.click(hideChat);
+        if ($.browser.mozilla) {
+            // Block ESC button in firefox (it breaks all socket connection).
+            $(document).keypress(function(event) {
+                if(event.keyCode === 27) {
+                    return false;
+                }
+            });
+        }
+        socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
+        socket.on("connect", function() {
+            jplayer = $("#player").jPlayer({
+                ready: function() {
+                    socket.emit('loggedin', function(data) {
+                        if (data) {
+                            nickname = data;
+                            socket.emit('joinroom', roomname);
+                        }
+                        else {
+                            joinAnonymously();
+                        }
+                    });
+                    if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) {
+                        addVolumeControl();
+                    }
+                    else {
+                        var touchbackdrop = $('<div id="touch-backdrop">'+
+                            '<button id="touch-play" class="btn btn-danger disabled">'+
+                                '<i class="icon-play icon-white"></i> Wait'+
+                            '</button></div>').appendTo("#cassette");
+                        touchplay = $('#touch-play');
+                        touchplay.click(function() {
+                            if (!$(this).hasClass("btn-danger")) {
+                                touchplay = null;
+                                jplayer.jPlayer('play', elapsedtime);
+                                touchbackdrop.remove();
+                            }
+                        });
+                    }
+                },
+                swfPath: "/static/swf/",
+                //solution: "flash, html",
+                supplied: "m4a",
+                preload: "auto",
+                volume: 1
+            });
+        });
+        socket.on('alreadyinaroom', alreadyInARoom);
+        socket.on('invalidnickname', invalidNickName);
+        socket.on('ready', ready);
+        socket.on("disconnect", disconnect);
+    });
 })();
diff --git a/routes/site.js b/routes/site.js
new file mode 100644 (file)
index 0000000..5e09ea1
--- /dev/null
@@ -0,0 +1,78 @@
+/**
+ * Module dependencies.
+ */
+
+var async = require('async')
+    , Captcha = require('../lib/captcha')
+    , db
+    , rooms;
+
+/**
+ * Generate a task for async.
+ */
+
+var task = function(genre) {
+    return function(callback) {
+        db.srandmember(genre, function(err, res) {
+            db.hget(res, "artworkUrl100", callback);
+        });
+    };
+};
+
+/**
+ * Initialize dependencies.
+ */
+
+exports.use = function(options) {
+    db = options.db;
+    rooms = options.rooms;
+};
+
+exports.index = function(req, res) {
+    if (req.session.user) {
+        res.local('loggedin', req.session.user.replace(/&/g, "&amp;"));
+    }
+    res.render("index", {rooms:rooms});
+};
+
+exports.signup = function(req, res) {
+    var captcha = new Captcha();
+    req.session.captchacode = captcha.getCode();
+    res.render("signup", {captchaurl:captcha.toDataURL()});
+};
+
+exports.login = function(req, res) {
+    res.render("login");
+};
+
+/**
+ * Extract at random in each room, some album covers and return the result as a JSON.
+ */
+
+exports.artworks = function(req, res) {
+    var tasks = [];
+    for (var i=0; i<rooms.length; i++) {
+        for (var j=0; j<6; j++) {
+            tasks.push(task(rooms[i]));
+        }
+    }
+    async.parallel(tasks, function(err, results) {
+        var obj = {
+            resultCount: results.length,
+            results: results
+        };
+        res.json(obj);
+    });
+};
+
+exports.room = function(req, res) {
+    if (rooms.indexOf(req.params.room) !== -1) {
+        if (req.session.user) {
+            res.local('loggedin', req.session.user.replace(/&/g, "&amp;"));
+        }
+        res.render("room", {roomname:req.params.room,rooms:rooms});
+    }
+    else {
+        res.send(404);
+    }
+};
diff --git a/routes/user.js b/routes/user.js
new file mode 100644 (file)
index 0000000..77178b8
--- /dev/null
@@ -0,0 +1,211 @@
+/**
+ * Module dependencies.
+ */
+
+var crypto = require('crypto')
+    , db
+    , User = require('../lib/user');
+    
+/**
+ * Extend String with custom methods for input validation.
+ */
+
+String.prototype.trim = function() {
+    return this.replace(/^[\r\n\t\s]+|[\r\n\t\s]+$/g, '');
+};
+
+String.prototype.isEmail = function() {
+    return this.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
+};
+
+/**
+ * Initialize dependencies.
+ */
+
+exports.use = function(options) {
+    db = options.db;
+};
+
+/**
+ * Sign up middlewares.
+ */
+
+exports.validateSignUp = function(req, res, next) {
+    var errors = {};
+    
+    req.body.username = req.body.username.trim(); // Username sanitization
+    if (req.body.username === 'binb') {
+        errors.username = 'is reserved';
+    }
+    else if (!req.body.username.match(/^[^\x00-\x1F\x7F]{1,15}$/)) {
+        errors.username = '1 to 15 characters required';
+    }
+    if (!req.body.email.isEmail()) {
+        errors.email = 'is not an email address';
+    }
+    if (!req.body.password.match(/^[A-Za-z0-9]{6,15}$/)) {
+        errors.password = '6 to 15 alphanumeric characters required';
+    }
+    if (req.body.captcha !== req.session.captchacode) {
+        errors.captcha = 'no match';
+    }
+    
+    // Save old values to repopulate the fields in case of future errors
+    req.session.oldvalues = {
+        username: req.body.username,
+        email: req.body.email
+    };
+    
+    if (errors.username || errors.email || errors.password || errors.captcha) {
+        req.session.errors = errors;
+        return res.redirect('/signup');
+    }
+    
+    next();
+};
+
+exports.userExists = function(req, res, next) {
+    var key = 'user:'+req.body.username;
+    db.exists(key, function(err, data) {
+        if (data === 1) { 
+            // User already exists
+            req.session.errors = {alert: 'A user with that name already exists.'};
+            return res.redirect('/signup');
+        }
+        next();
+    });
+};
+
+exports.emailExists = function(req, res, next) {
+    var key = 'email:'+req.body.email;
+    db.exists(key, function(err, data) {
+        if (data === 1) { 
+            // Email already exists
+            req.session.errors = {alert: 'A user with that email already exists.'};
+            return res.redirect('/signup');
+        }
+        next();
+    });
+};
+
+exports.createAccount = function(req, res) {
+    var userkey = 'user:'+req.body.username
+        , mailkey = 'email:'+req.body.email
+        , salt = crypto.randomBytes(6).toString('base64')
+        , hash = crypto.createHash('sha256').update(salt+req.body.password).digest('hex')
+        , date = new Date()
+        , day = date.getDate()
+        , month = date.getMonth() + 1
+        , year = date.getFullYear();
+
+    if (day < 10) {
+        day = '0' + day;
+    }
+    if (month < 10) {
+        month = '0' + month;
+    }
+    var joindate = day+'/'+month+'/'+year;
+    var user = new User(req.body.username, req.body.email, salt, hash, joindate);
+    // Add new user in the db
+    db.hmset(userkey, user);
+    db.set(mailkey, userkey);
+    db.sadd('users', userkey);
+    db.sadd('emails', mailkey);
+    // Delete old fields values (we don't want these to be available in login view)
+    delete req.session.oldvalues;
+    var msg = 'You successfully created your account. You are now ready to login.';
+    res.render('login', {success:msg});
+};
+
+/**
+ * Login middlewares.
+ */
+
+exports.validateLogin = function(req, res, next) {
+    var errors = {};
+    
+    req.body.username = req.body.username.trim(); // Username sanitization
+    req.body.password = req.body.password.trim(); // Password sanitization
+    if (req.body.username === '') {
+        errors.username = "can't be empty";
+    }
+    if (req.body.password === '') {
+        errors.password = "can't be empty";
+    }
+    
+    req.session.oldvalues = {username: req.body.username};
+    if (errors.username || errors.password) {
+        req.session.errors = errors;
+        return res.redirect('/login');
+    }
+    
+    next();
+};
+
+exports.checkUser = function(req, res, next) {
+    var key = 'user:'+req.body.username;
+    db.exists(key, function(err, data) {
+        if (data === 1) {
+            // User exists, proceed with authentication
+            return next();
+        }
+        req.session.errors = {alert: 'The username you specified does not exists.'};
+        res.redirect('/login');
+    });
+};
+
+exports.authenticate = function(req, res) {
+    var key = 'user:'+req.body.username;
+    db.hmget(key, 'salt', 'password', function(err, data) {
+        var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
+        if (hash === data[1]) {
+            // Authentication succeeded, regenerate the session
+            req.session.regenerate(function() {
+                req.session.cookie.maxAge = 604800000; // One week
+                req.session.user = req.body.username;
+                res.redirect('/');
+            });
+            return;
+        }
+        req.session.errors = {alert: 'The password you specified is not correct.'};
+        res.redirect('/login');
+    });
+};
+
+/**
+ * Logout the user.
+ */
+
+exports.logout = function(req, res) {
+    // Destroy the session
+    req.session.destroy(function() {
+        res.redirect('/');
+    });
+};
+
+/**
+ * Show user profile.
+ */
+
+exports.profile = function(req, res) {
+    var key = 'user:'+req.params[0];
+    db.exists(key, function(err, data) {
+        if (data === 1) {
+            db.hgetall(key, function(e, obj) {
+                obj.username = obj.username.replace(/&/g, '&amp;');
+                obj.bestguesstime = (obj.bestguesstime/1000).toFixed(1);
+                obj.worstguesstime = (obj.worstguesstime/1000).toFixed(1);
+                if (obj.guessed !== '0') {
+                    obj.meanguesstime = ((obj.totguesstime/obj.guessed)/1000).toFixed(1);
+                }
+                delete obj.email;
+                delete obj.password;
+                delete obj.salt;
+                delete obj.totguesstime;
+                res.render('user', obj);
+            });
+            return;
+        }
+        res.send(404);
+    });
+};
diff --git a/server.js b/server.js
deleted file mode 100644 (file)
index 2b9a85b..0000000
--- a/server.js
+++ /dev/null
@@ -1,357 +0,0 @@
-var async = require('async');
-var characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-var Captcha = require('./lib/captcha.js')(characters);
-var config = require('./config.js').configure();
-var crypto = require('crypto');
-var express = require('express');
-var form = require('express-form');
-var parseCookie = require('connect').utils.parseCookie;
-var redisstore = require('connect-redis')(express);
-var rooms = Object.create(null); // The Object that contains all the room instances
-var sockets = Object.create(null); // Sockets of all rooms
-
-// Setting up Redis
-var songsdb = require("redis-url").createClient(config.songsdburl);
-var usersdb = require("redis-url").createClient(config.usersdburl);
-
-songsdb.on('error', function(err) {
-       console.log("Error: "+err);
-});
-
-usersdb.on('error', function(err) {
-       console.log("Error: "+err);
-});
-
-// Setting up Express
-var sessionstore = new redisstore({client:usersdb});
-var app = express.createServer();
-
-// Configuration
-app.use(express.static(__dirname + '/public'), {maxAge: 2592000000});
-app.use(express.favicon(__dirname + '/public/static/images/favicon.ico', {maxAge: 2592000000}));
-app.use(express.bodyParser());
-app.use(express.cookieParser());
-app.use(express.session({secret:config.sessionsecret,store:sessionstore}));
-app.set("view options", {layout:false});
-app.set('view engine', 'jade');
-
-// Routes
-app.get("/", function(req, res) {
-       if (req.session.user) {
-               res.local('loggedin', req.session.user.replace(/&/g, "&amp;"));
-       }
-       res.render("index", {rooms:config.rooms});
-});
-
-app.get("/signup", function(req, res) {
-       var captcha = new Captcha();
-       req.session.captchacode = captcha.getCode();
-       res.render("signup", {captchaurl:captcha.toDataURL()});
-});
-
-// Sign up route middlewares
-var checkCaptcha = function(req, res, next) {
-       if (req.form.isValid) {
-               if (req.session.captchacode !== req.form.captcha) {
-                       var errors = {captcha:['no match']};
-                       var captcha = new Captcha();
-                       req.session.captchacode = captcha.getCode();
-                       return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
-               }
-               next();
-       }
-       else {
-               var captcha = new Captcha();
-               req.session.captchacode = captcha.getCode();
-               res.render("signup", {errors:req.form.getErrors(),captchaurl:captcha.toDataURL()});
-       }
-};
-
-var checkUserExists = function(req, res, next) {
-       var userkey = "user:"+req.form.username;
-       usersdb.exists(userkey, function(err, data) {
-               if (data === 1) { // User already exists
-                       var errors = {alert: "A user with name "+req.form.username+" already exists."};
-                       var captcha = new Captcha();
-                       req.session.captchacode = captcha.getCode();
-                       return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
-               }
-               next();
-       });
-};
-
-var checkEmailExists = function(req, res, next) {
-       var mailkey = "email:"+req.form.email;
-       usersdb.exists(mailkey, function(err, data) {
-               if (data === 1) { // Email already exists
-                       var errors = {alert: "A user with that email already exists."};
-                       var captcha = new Captcha();
-                       req.session.captchacode = captcha.getCode();
-                       return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
-               }
-               next();
-       });
-};
-
-app.post("/signup", 
-       form(
-               form.filter("username").trim().required().not(/binb/, "is reserved")
-                       .is(/^[^\x00-\x1F\x7F]{1,15}$/, "1 to 15 characters required"),
-               form.filter("email").required().isEmail("is not an email address"),
-               form.filter("password").required()
-                       .is(/^[A-Za-z0-9]{6,15}$/, "6 to 15 alphanumeric characters required"),
-               form.filter("captcha").required()
-       ),
-       checkCaptcha,
-       checkUserExists,
-       checkEmailExists,
-       function (req, res) { // Set up the account
-               var userkey = "user:"+req.form.username;
-               var mailkey = "email:"+req.form.email;
-               var salt = "";
-               while (salt.length < 8) {
-                       salt += characters[Math.floor(Math.random() * characters.length)];
-               }
-               var hash = crypto.createHash('sha256').update(salt+req.form.password).digest('hex');
-               var date = new Date();
-               var joindate = date.getDate()+"/"+(date.getMonth()+1)+"/"+date.getFullYear();
-               usersdb.hmset(
-                       userkey,
-                       "username", req.form.username,
-                       "email", req.form.email,
-                       "password", hash,
-                       "salt", salt,
-                       "joindate", joindate,
-                       "totpoints", 0,
-                       "bestscore", 0,
-                       "golds", 0,
-                       "silvers", 0,
-                       "bronzes", 0,
-                       "bestguesstime", 30000,
-                       "worstguesstime", 0,
-                       "totguesstime", 0,
-                       "guessed", 0,
-                       "victories", 0,
-                       "secondplaces", 0,
-                       "thirdplaces", 0
-               );
-               usersdb.set(mailkey, userkey);
-               usersdb.sadd("users", userkey);
-               usersdb.sadd("emails", mailkey);
-               var msg = "You successfully created your account. You are now ready to login.";
-               res.render("login", {success:msg});
-       }
-);
-
-app.get("/login", function(req, res) {
-       res.render("login");
-});
-
-app.post("/login", 
-       form(
-               form.filter("username").trim().required(),
-               form.filter("password").trim().required()
-       ),
-       function(req, res, next) {
-               if (req.form.isValid) {
-                       usersdb.exists("user:"+req.form.username, function(err, data) {
-                               if (data === 1) { // User exists
-                                       next();
-                               }
-                               else {
-                                       var errors = {alert: "The username you specified does not exists."};
-                                       res.render("login", {errors:errors});
-                               }
-                       });
-               }
-               else {
-                       res.render("login", {errors:req.form.getErrors()});
-               }
-       },
-       function(req, res) { // Authenticate User
-               usersdb.hmget("user:"+req.form.username, "salt", "password", function(err, data) {
-                       var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
-                       if (hash === data[1]) {
-                               req.session.regenerate(function() {
-                                       req.session.cookie.maxAge = 604800000; // One week
-                                       req.session.user = req.form.username;
-                                       res.redirect('/');
-                               });
-                       }
-                       else {
-                               var errors = {alert: "The password you specified is not correct."};
-                               res.render("login", {errors:errors});
-                       }
-               });
-       }
-);
-
-app.get("/logout", function(req, res) {
-       req.session.destroy(function() {
-               res.redirect("/");
-       });
-});
-
-var makeCallBack = function(genre) {
-       return function(callback) {
-               songsdb.srandmember(genre, function(err, res) {
-                       songsdb.hget(res, "artworkUrl100", callback);
-               });
-       };
-};
-
-app.get("/artworks", function(req, res) {
-       var callitems = [];
-       for (var i=0; i<config.rooms.length; i++) {
-               for (var j=0; j<6; j++) {
-                       callitems.push(makeCallBack(config.rooms[i]));
-               }
-       }
-       async.parallel(callitems, function(err, results) {
-               var obj = {
-                       resultCount: results.length,
-                       results: results
-               };
-               res.writeHead(200, {"Content-Type": "application/json"});
-               res.end(JSON.stringify(obj));
-       });
-});
-
-app.get("/:room", function(req, res) {
-       if (config.rooms.indexOf(req.params.room) !== -1) {
-               if (req.session.user) {
-                       res.local('loggedin', req.session.user.replace(/&/g, "&amp;"));
-               }
-               res.render("room", {roomname:req.params.room,rooms:config.rooms});
-       }
-       else {
-               res.send(404);
-       }
-});
-
-app.get("/user/*", function(req, res) {
-       var key = "user:"+req.params[0];
-       usersdb.exists(key, function(err, data) {
-               if (data === 1) {
-                       usersdb.hgetall(key, function(e, obj) {
-                               obj.username = obj.username.replace(/&/g, "&amp;");
-                               obj.bestguesstime = (obj.bestguesstime/1000).toFixed(1);
-                               obj.worstguesstime = (obj.worstguesstime/1000).toFixed(1);
-                               if (obj.guessed !== "0") {
-                                       obj.meanguesstime = ((obj.totguesstime/obj.guessed)/1000).toFixed(1);
-                               }
-                               delete obj.email;
-                               delete obj.password;
-                               delete obj.salt;
-                               delete obj.totguesstime;
-                               res.render("user", obj);
-                       });
-               }
-               else {
-                       res.send(404);
-               }
-       });
-});
-
-// Starting HTTP server
-app.listen(config.port);
-
-// Setting up Socket.IO
-var io = require("socket.io").listen(app);
-
-io.enable('browser client minification');      // send minified client
-io.enable('browser client etag');                      // apply etag caching logic based on version number
-io.enable('browser client gzip');                      // gzip the file
-io.set('log level', 1);                                                // reduce logging
-// enable transports
-io.set('transports', ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);
-
-io.set('authorization', function(data, accept) {
-       if(data.headers.cookie) {
-               var cookie = parseCookie(data.headers.cookie);
-               sessionstore.get(cookie['connect.sid'], function(err, session) {
-                       if (err || !session) {
-                               accept('Error', false);
-                       }
-                       else {
-                               data.session = session;
-                               accept(null, true);
-                       }
-               });
-       }
-       else {
-               return accept('No cookie transmitted.', false);
-       }
-});
-
-io.sockets.on('connection', function(socket) {
-       var session = socket.handshake.session;
-       socket.on('getoverview', function() {
-               var data = Object.create(null);
-               for (var prop in rooms) {
-                       data[prop] = rooms[prop].getPopulation();
-               }
-               socket.join('home');
-               socket.emit('overview', data);
-       });
-       socket.on('loggedin', function(fn) {
-               return (session.user) ? fn(session.user) : fn(false);
-       });
-       socket.on('joinroom', function(data) {
-               if (session.user && typeof data === "string" && config.rooms.indexOf(data) !== -1) {
-                       if (sockets[session.user]) { // User already in a room
-                               socket.emit('alreadyinaroom');
-                               return;
-                       }
-                       socket.nickname = session.user;
-                       rooms[data].joinRoom(socket);
-               }
-       });
-       socket.on('joinanonymously', function(data) {
-               if (!socket.nickname && typeof data === "object" && typeof data.nickname === "string" &&
-                       data.nickname !== "" && typeof data.roomname === "string" && 
-                       config.rooms.indexOf(data.roomname) !== -1) {
-                       rooms[data.roomname].setNickName(socket, data);
-               }
-       });
-       socket.on('getstatus', function() {
-               if (socket.roomname) {
-                       rooms[socket.roomname].sendStatus(socket);
-               }
-       });
-       socket.on('sendchatmsg', function(data) {
-               if (socket.roomname) {
-                       rooms[socket.roomname].sendChatMessage(socket, data);
-               }
-       });
-       socket.on('guess', function(data) {
-               if (socket.roomname && typeof data === "string") {
-                       rooms[socket.roomname].guess(socket, data);
-               }
-       });
-       socket.on("disconnect", function() {
-               if (socket.roomname) {
-                       rooms[socket.roomname].userLeft(socket.nickname);
-               }
-       });
-});
-
-// Setting up the rooms
-var roomoptions = {
-       songsdb: songsdb,
-       usersdb: usersdb,
-       io: io,
-       sockets: sockets,
-       songsinarun: config.songsinarun,
-       fifolength: config.fifolength,
-       threshold: config.threshold
-};
-
-var Room = require('./lib/room.js')(roomoptions);
-
-for (var i=0; i<config.rooms.length; i++) {
-       rooms[config.rooms[i]] = new Room(config.rooms[i]);
-       rooms[config.rooms[i]].start();
-}
-
-console.log("binb started and listening on port "+config.port);
index f881d3ede66292b574ce99cf205331d7831f8992..afa53718fff62518e895918ca2c327e5968782ea 100644 (file)
@@ -1,18 +1,18 @@
 footer
-       #footer-inner
-               #copy &copy; 2012 Luigi Pinca
-               iframe#facebook-button(allowTransparency="true", frameborder="0",
-                       scrolling="no", src="//www.facebook.com/plugins/like.php?href=http%3A%2F%2Fbinb.nodejitsu.com&amp;send=false&amp;layout=button_count&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;locale=en_US")
-               iframe#twitter-button(allowtransparency="true", frameborder="0",
-                       scrolling="no", src="//platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fbinb.nodejitsu.com")
-               iframe#github-button(allowtransparency="true", frameborder="0", scrolling="0", 
-                       src="http://markdotto.github.com/github-buttons/github-btn.html?user=lpinca&repo=binb&type=watch&count=true")
-               #footer-right Made with 
-                       a(target="_blank", href="http://nodejs.org/") node.js
-                       | , 
-                       a(target="_blank", href="http://socket.io/") socket.io
-                       | ,
-                       a(target="_blank", href="http://redis.io/") redis.io
-                       | ,
-                       a(target="_blank", href="http://nodejitsu.com/") nodejitsu
-                       | . Optimized for Google Chrome.
+    #footer-inner
+        #copy &copy; 2012 Luigi Pinca
+        iframe#facebook-button(allowTransparency="true", frameborder="0",
+            scrolling="no", src="//www.facebook.com/plugins/like.php?href=http%3A%2F%2Fbinb.nodejitsu.com&amp;send=false&amp;layout=button_count&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;locale=en_US")
+        iframe#twitter-button(allowtransparency="true", frameborder="0",
+            scrolling="no", src="//platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fbinb.nodejitsu.com")
+        iframe#github-button(allowtransparency="true", frameborder="0", scrolling="0", 
+            src="http://markdotto.github.com/github-buttons/github-btn.html?user=lpinca&repo=binb&type=watch&count=true")
+        #footer-right Made with 
+            a(target="_blank", href="http://nodejs.org/") node.js
+            | , 
+            a(target="_blank", href="http://socket.io/") socket.io
+            | ,
+            a(target="_blank", href="http://redis.io/") redis.io
+            | ,
+            a(target="_blank", href="http://nodejitsu.com/") nodejitsu
+            | . Optimized for Google Chrome.
index 228891db6c222c7921e54fdf3e1318cad5547038..d7d9d3dcd47030fa47984171e99f5cf76c71852b 100644 (file)
@@ -1,21 +1,21 @@
 mottos = ['guess the song.', 'name that tune.', 'i know this track.']
 motto = mottos[Math.floor(Math.random()*mottos.length)]
 head
-       link(href="/static/css/bootstrap.min.css", rel="stylesheet")
-       link(href="/static/css/style.css", rel="stylesheet")
-       meta(charset="UTF-8")
-       meta(name="keywords", content="iTunes, music, quiz, binb, beatquest, realtime, multiplayer, listening, game")
-       meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
-       meta(name="author", content="Luigi Pinca")
-       script(src="/static/js/jquery.min.js")
-       script
-               var _gaq = _gaq || [];
-               _gaq.push(['_setAccount', 'UA-29865853-1']);
-               _gaq.push(['_trackPageview']);
-               (function() {
-                       var ga = document.createElement('script');
-                       ga.type = 'text/javascript'; ga.async = true;
-                       ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
-                               '.google-analytics.com/ga.js';
-                       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-               })();
+    link(href="/static/css/bootstrap.min.css", rel="stylesheet")
+    link(href="/static/css/style.css", rel="stylesheet")
+    meta(charset="UTF-8")
+    meta(name="keywords", content="iTunes, music, quiz, binb, beatquest, realtime, multiplayer, listening, game")
+    meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
+    meta(name="author", content="Luigi Pinca")
+    script(src="/static/js/jquery.min.js")
+    script
+        var _gaq = _gaq || [];
+        _gaq.push(['_setAccount', 'UA-29865853-1']);
+        _gaq.push(['_trackPageview']);
+        (function() {
+            var ga = document.createElement('script');
+            ga.type = 'text/javascript'; ga.async = true;
+            ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
+                '.google-analytics.com/ga.js';
+            var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+        })();
index 62c44b0d004658d9898bd84801ee0a7920b6bee2..1a214b6de77f569f0f03357656b13538c94a5d0a 100644 (file)
@@ -1,53 +1,53 @@
 doctype html
 html
-       include header
-               title binb
-               script(src="/socket.io/socket.io.js")
-               script(src="/static/js/bootstrap.min.js")
-               script(src="/static/js/home.js")
-       body
-               include uv.jade
-               .navbar.navbar-fixed-top
-                       .navbar-inner
-                               .container
-                                       a.brand(href="/")
-                                               .logo #{motto}
-                                       ul.nav.pull-right
-                                               li.active
-                                                       a(href="/") Home
-                                               if (typeof(loggedin) !== "undefined")
-                                                       li
-                                                               p.navbar-text Logged in as 
-                                                                       a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
-                                                       li
-                                                               a(href="/logout") Logout
-                                               else
-                                                       li
-                                                               a(href="/signup") Sign up
-                                                       li
-                                                               a(href="/login") Login
-               .container
-                       section
-                               .row
-                                       .span7
-                                               h3 What's this?
-                                               p binb is a realtime, multiplayer, competitive music listening game.
-                                       .span9
-                                               h3 How to play?
-                                               p All you have to do is to guess the song that is playing. A fixed number 
-                                                       | of songs will run and for each one correctly guessed you will earn an 
-                                                       | amount of points. That amount depends on the number of correct guesses 
-                                                       | (artist and/or title of the song) and on how fast you will be on 
-                                                       | answering compared to other players. At the end a scoreboard will 
-                                                       | report the best three players of the match. If you have read this 
-                                                       | far, what are you waiting? Click on a room below and prove your 
-                                                       | music knowledge!
-                       section
-                               .row
-                                       .span16
-                                               ul.thumbnails
-                                                       each item in rooms
-                                                               li.span4
-                                                                       a.thumbnail.relative(href="/#{item}")
-                                                                               .room #{item} - <span id="#{item}"></span> Players
-                       include footer
+    include header
+        title binb
+        script(src="/socket.io/socket.io.js")
+        script(src="/static/js/bootstrap.min.js")
+        script(src="/static/js/home.js")
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    a.brand(href="/")
+                        .logo #{motto}
+                    ul.nav.pull-right
+                        li.active
+                            a(href="/") Home
+                        if (typeof(loggedin) !== "undefined")
+                            li
+                                p.navbar-text Logged in as 
+                                    a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
+                            li
+                                a(href="/logout") Logout
+                        else
+                            li
+                                a(href="/signup") Sign up
+                            li
+                                a(href="/login") Login
+        .container
+            section
+                .row
+                    .span7
+                        h3 What's this?
+                        p binb is a realtime, multiplayer, competitive music listening game.
+                    .span9
+                        h3 How to play?
+                        p All you have to do is to guess the song that is playing. A fixed number 
+                            | of songs will run and for each one correctly guessed you will earn an 
+                            | amount of points. That amount depends on the number of correct guesses 
+                            | (artist and/or title of the song) and on how fast you will be on 
+                            | answering compared to other players. At the end a scoreboard will 
+                            | report the best three players of the match. If you have read this 
+                            | far, what are you waiting? Click on a room below and prove your 
+                            | music knowledge!
+            section
+                .row
+                    .span16
+                        ul.thumbnails
+                            each item in rooms
+                                li.span4
+                                    a.thumbnail.relative(href="/#{item}")
+                                        .room #{item} - <span id="#{item}"></span> Players
+            include footer
index 9cca2c45a7cd7cef276a21c60574f685c1a91ece..ca0ee1bb9593c356eb18b8e18ead725f4189bfde 100644 (file)
@@ -1,78 +1,79 @@
 doctype html
-html   
-       include header
-               title binb :: login
-               script(src="/static/js/bootstrap.min.js")
-       body
-               include uv.jade
-               .navbar.navbar-fixed-top
-                       .navbar-inner
-                               .container
-                                       a.brand(href="/")
-                                               .logo #{motto}
-                                       ul.nav.pull-right
-                                               li
-                                                       a(href="/") Home
-                                               li
-                                                       a(href="/signup") Sign up
-                                               li.active
-                                                       a(href="/login") Login
-               .container
-                       section
-                               .row
-                                       .span3
-                                               h3 New user?
-                                               a(href="/signup") Click here to create an account.
-                                       .span13
-                                               if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
-                                                       .alert.alert-error
-                                                               a.close(data-dismiss="alert") &times;
-                                                               strong Oh snap!
-                                                               |  #{errors.alert}
-                                               else if (typeof(success) !== "undefined")
-                                                       .alert.alert-success
-                                                               a.close(data-dismiss="alert") &times;
-                                                               strong Well done!
-                                                               |  #{success}
-                                               form.form-horizontal.well(method="post",action="/login")
-                                                       fieldset
-                                                               if (typeof(errors) !== "undefined")
-                                                                       if (typeof(errors.username) !== "undefined")
-                                                                               .control-group.error
-                                                                                       label.control-label(for="username") Name
-                                                                                       .controls
-                                                                                               input#username(type="text",name="username",
-                                                                                                       value="#{username}")
-                                                                                               span.help-inline #{errors.username[0]}
-                                                                       else
-                                                                               .control-group
-                                                                                       label.control-label(for="username") Name
-                                                                                       .controls
-                                                                                               input#username(type="text",name="username",
-                                                                                                       value="#{username}")
-                                                                       if (typeof(errors.password) !== 'undefined')
-                                                                               .control-group.error
-                                                                                       label.control-label(for="password") Password
-                                                                                       .controls
-                                                                                               input#password(type="password",name="password")
-                                                                                               span.help-inline #{errors.password[0]}
-                                                                       else
-                                                                               .control-group
-                                                                                       label.control-label(for="password") Password
-                                                                                       .controls
-                                                                                               input#password(type="password",name="password")
-                                                               else
-                                                                       .control-group
-                                                                               label.control-label(for="username") Name
-                                                                               .controls
-                                                                                       input#username(type="text",name="username",
-                                                                                               placeholder="enter your nickname...")
-                                                                       .control-group
-                                                                               label.control-label(for="password") Password
-                                                                               .controls
-                                                                                       input#password(type="password",name="password",
-                                                                                               placeholder="enter your password...")
-                                                               button#signup-button.btn.btn-primary(type="submit")
-                                                                       i.icon-user.icon-white
-                                                                       |  Login
-                       include footer
+html
+    include header
+        title binb :: login
+        script(src="/static/js/bootstrap.min.js")
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    a.brand(href="/")
+                        .logo #{motto}
+                    ul.nav.pull-right
+                        li
+                            a(href="/") Home
+                        li
+                            a(href="/signup") Sign up
+                        li.active
+                            a(href="/login") Login
+        .container
+            section
+                .row
+                    .span3
+                        h3 New user?
+                        a(href="/signup") Click here to create an account.
+                    .span13
+                        if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
+                            .alert.alert-error
+                                a.close(data-dismiss="alert") &times;
+                                strong Oh snap!
+                                |  #{errors.alert}
+                        else if (typeof(success) !== "undefined")
+                            .alert.alert-success
+                                a.close(data-dismiss="alert") &times;
+                                strong Well done!
+                                |  #{success}
+                        form.form-horizontal.well(method="post",action="/login")
+                            fieldset
+                                if (typeof(errors) !== "undefined")
+                                    if (typeof(errors.username) !== "undefined")
+                                        .control-group.error
+                                            label.control-label(for="username") Name
+                                            .controls
+                                                input#username(type="text",name="username",
+                                                    value="#{oldvalues.username}")
+                                                span.help-inline #{errors.username}
+                                    else
+                                        .control-group
+                                            label.control-label(for="username") Name
+                                            .controls
+                                                input#username(type="text",name="username",
+                                                    value="#{oldvalues.username}")
+                                    if (typeof(errors.password) !== 'undefined')
+                                        .control-group.error
+                                            label.control-label(for="password") Password
+                                            .controls
+                                                input#password(type="password",name="password")
+                                                span.help-inline #{errors.password}
+                                    else
+                                        .control-group
+                                            label.control-label(for="password") Password
+                                            .controls
+                                                input#password(type="password",name="password",
+                                                    placeholder="enter your password...")
+                                else
+                                    .control-group
+                                        label.control-label(for="username") Name
+                                        .controls
+                                            input#username(type="text",name="username",
+                                                placeholder="enter your nickname...")
+                                    .control-group
+                                        label.control-label(for="password") Password
+                                        .controls
+                                            input#password(type="password",name="password",
+                                                placeholder="enter your password...")
+                                button#signup-button.btn.btn-primary(type="submit")
+                                    i.icon-user.icon-white
+                                    |  Login
+            include footer
index 7c374c2f69dc749275aae5d2ce98c3ad623db403..0d8af23fef5af2d614ef8a3b8e28e13c997eed10 100644 (file)
@@ -1,90 +1,90 @@
 doctype html
 html
-       include header
-               title binb :: #{roomname}
-               script(src="/socket.io/socket.io.js")
-               script(src="/static/js/bootstrap.min.js")
-               script(src="/static/js/jquery.jplayer.min.js")
-               script(type='text/javascript')
-                       var roomname = "#{roomname}";
-               script(src="/static/js/room.js")
-       body
-               include uv.jade
-               .navbar.navbar-fixed-top
-                       .navbar-inner
-                               .container
-                                       ul.nav.pull-right
-                                               li
-                                                       a(href="/") Home
-                                               li.active.dropdown
-                                                       a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname} 
-                                                               b.caret
-                                                       ul.dropdown-menu
-                                                               each item in rooms
-                                                                       if item != roomname
-                                                                               li
-                                                                                       a(href="/#{item}") #{item}
-                                               if (typeof(loggedin) !== "undefined")
-                                                       li
-                                                               p.navbar-text Logged in as 
-                                                                       a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
-                                                       li
-                                                               a(href="/logout") Logout
-                                               else    
-                                                       li
-                                                               a(href="/signup") Sign up
-                                                       li
-                                                               a(href="/login") Login
-               #player
-               #modal.modal.fade
-               .container
-                       section
-                               .row
-                                       .span4.offset1
-                                               #cassette.relative
-                                                       #wheel-left.wheel
-                                                       #tape-left
-                                                       #tape-right
-                                                       #wheel-right.wheel
-                                                       #progress-bar
-                                                               #progress
-                                                       #countdown
-                                       .span2
-                                               #volume.relative
-                                       .span8
-                                               .page-header
-                                                       .logo #{motto}
-                                                       #total-tracks <span></span> tracks.
-                                               #summary.row
-                                                       .span2
-                                                               .title Rank
-                                                               .rank
-                                                       .span4
-                                                               .title Points
-                                                               .points
-                                                       .span2
-                                                               .title Track
-                                                               .track
-                                               p#feedback Waiting for connection...
-                                               input#guess.span8(type="text",tabindex="1",
-                                                       placeholder="guess the artist and/or title here")
-                       section.relative
-                               .row
-                                       #users-wrapper.span5.offset2
-                                               ul#users.unstyled
-                                       .span8
-                                               a#toggle-chat Hide chat
-                                               #chat-outer-wrapper
-                                                       #chat-wrapper.bordered
-                                                               ul#chat.unstyled
-                                                       #message-wrapper
-                                                               span#recipient
-                                                               input#message.span8(type="text",tabindex="2")
-                                               ul#tracks.unstyled
-                               #disclaimer
-                                       div I do not own any right on the songs that are played here.
-                                       div Tracks are played using iTunes api preview.
-                                       div Original idea from 
-                                               a(target="_blank", href="http://beatquest.fm/") beatquest.fm
-                                               |.
-                       include footer
+    include header
+        title binb :: #{roomname}
+        script(src="/socket.io/socket.io.js")
+        script(src="/static/js/bootstrap.min.js")
+        script(src="/static/js/jquery.jplayer.min.js")
+        script(type='text/javascript')
+            var roomname = "#{roomname}";
+        script(src="/static/js/room.js")
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    ul.nav.pull-right
+                        li
+                            a(href="/") Home
+                        li.active.dropdown
+                            a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname} 
+                                b.caret
+                            ul.dropdown-menu
+                                each item in rooms
+                                    if item != roomname
+                                        li
+                                            a(href="/#{item}") #{item}
+                        if (typeof(loggedin) !== "undefined")
+                            li
+                                p.navbar-text Logged in as 
+                                    a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
+                            li
+                                a(href="/logout") Logout
+                        else
+                            li
+                                a(href="/signup") Sign up
+                            li
+                                a(href="/login") Login
+        #player
+        #modal.modal.fade
+        .container
+            section
+                .row
+                    .span4.offset1
+                        #cassette.relative
+                            #wheel-left.wheel
+                            #tape-left
+                            #tape-right
+                            #wheel-right.wheel
+                            #progress-bar
+                                #progress
+                            #countdown
+                    .span2
+                        #volume.relative
+                    .span8
+                        .page-header
+                            .logo #{motto}
+                            #total-tracks <span></span> tracks.
+                        #summary.row
+                            .span2
+                                .title Rank
+                                .rank
+                            .span4
+                                .title Points
+                                .points
+                            .span2
+                                .title Track
+                                .track
+                        p#feedback Waiting for connection...
+                        input#guess.span8(type="text",tabindex="1",
+                            placeholder="guess the artist and/or title here")
+            section.relative
+                .row
+                    #users-wrapper.span5.offset2
+                        ul#users.unstyled
+                    .span8
+                        a#toggle-chat Hide chat
+                        #chat-outer-wrapper
+                            #chat-wrapper.bordered
+                                ul#chat.unstyled
+                            #message-wrapper
+                                span#recipient
+                                input#message.span8(type="text",tabindex="2")
+                        ul#tracks.unstyled
+                #disclaimer
+                    div I do not own any right on the songs that are played here.
+                    div Tracks are played using iTunes api preview.
+                    div Original idea from 
+                        a(target="_blank", href="http://beatquest.fm/") beatquest.fm
+                        |.
+            include footer
index 7303bb1355a53ebabe5ba2900a28ff7bbe2bc946..a32c652dacc698e5ffb106bdd4b7c90d75e7ec50 100644 (file)
 doctype html
-html   
-       include header
-               title binb :: sign up
-               script(src="/static/js/bootstrap.min.js")
-       body
-               include uv.jade
-               .navbar.navbar-fixed-top
-                       .navbar-inner
-                               .container
-                                       a.brand(href="/")
-                                               .logo #{motto}
-                                       ul.nav.pull-right
-                                               li
-                                                       a(href="/") Home
-                                               li.active
-                                                       a(href="/signup") Sign up
-                                               li
-                                                       a(href="/login") Login
-               .container
-                       section
-                               .row
-                                       .span3
-                                               h3 Not a new user?
-                                               a(href="/login") Click here to log in.
-                                       .span13
-                                               h3 Why sign up?
-                                               p Registration is optional, but if you are a regular user consider creating  
-                                                       | an account. This will provide the following advantages:
-                                               ul
-                                                       li You will be the one and only owner of your nickname (no one can use 
-                                                               | your nickname in your place).
-                                                       li Some simple stats related to your account will be collected.
-                                               if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
-                                                       .alert.alert-error
-                                                               a.close(data-dismiss="alert") &times;
-                                                               strong Oh snap!
-                                                               |  #{errors.alert}
-                                               form.form-horizontal.well(method="post",action="/signup")
-                                                       fieldset
-                                                               if (typeof(errors) !== "undefined")
-                                                                       if (typeof(errors.username) !== "undefined")
-                                                                               .control-group.error
-                                                                                       label.control-label(for="username") Name
-                                                                                       .controls
-                                                                                               input#username(type="text",name="username",
-                                                                                                       value="#{username}")
-                                                                                               span.help-inline #{errors.username[0]}
-                                                                       else
-                                                                               .control-group
-                                                                                       label.control-label(for="username") Name
-                                                                                       .controls
-                                                                                               input#username(type="text",name="username",
-                                                                                                       value="#{username}")
-                                                                       if (typeof(errors.email) !== 'undefined')
-                                                                               .control-group.error
-                                                                                       label.control-label(for="email") Email
-                                                                                       .controls
-                                                                                               input#email(type="text",name="email",
-                                                                                                       value="#{email}")
-                                                                                               span.help-inline #{errors.email[0]}
-                                                                       else
-                                                                               .control-group
-                                                                                       label.control-label(for="email") Email
-                                                                                       .controls
-                                                                                               input#email(type="text",name="email",
-                                                                                                       value="#{email}")
-                                                                       if (typeof(errors.password) !== 'undefined')
-                                                                               .control-group.error
-                                                                                       label.control-label(for="password") Password
-                                                                                       .controls
-                                                                                               input#password(type="password",name="password")
-                                                                                               span.help-inline #{errors.password[0]}
-                                                                       else
-                                                                               .control-group
-                                                                                       label.control-label(for="password") Password
-                                                                                       .controls
-                                                                                               input#password(type="password",name="password")
-                                                                       if (typeof(errors.captcha) !== 'undefined')
-                                                                               .control-group.error
-                                                                                       label.control-label(for="captcha-input") Are you human?
-                                                                                       .controls
-                                                                                               img#captcha(src="#{captchaurl}")
-                                                                                               input#captcha-input(type="text",name="captcha")
-                                                                                               span.help-inline #{errors.captcha[0]}
-                                                                       else
-                                                                               .control-group
-                                                                                       label.control-label(for="captcha-input") Are you human?
-                                                                                       .controls
-                                                                                               img#captcha(src="#{captchaurl}")
-                                                                                               input#captcha-input(type="text",name="captcha")
-                                                               else
-                                                                       .control-group
-                                                                               label.control-label(for="username") Name
-                                                                               .controls
-                                                                                       input#username(type="text",name="username",
-                                                                                               placeholder="enter a nickname...")
-                                                                       .control-group
-                                                                               label.control-label(for="email") Email
-                                                                               .controls
-                                                                                       input#email(type="text",name="email",
-                                                                                               placeholder="enter a valid email...")
-                                                                       .control-group
-                                                                               label.control-label(for="password") Password
-                                                                               .controls
-                                                                                       input#password(type="password",name="password",
-                                                                                               placeholder="enter a password...")
-                                                                       .control-group
-                                                                               label.control-label(for="captcha-input") Are you human?
-                                                                               .controls
-                                                                                       img#captcha(src="#{captchaurl}")
-                                                                                       input#captcha-input(type="text",name="captcha",
-                                                                                               placeholder="type what you see...")
-                                                               button#signup-button.btn.btn-success(type="submit")
-                                                                       i.icon-user.icon-white
-                                                                       |  Sign up!
-                       include footer
+html
+    include header
+        title binb :: sign up
+        script(src="/static/js/bootstrap.min.js")
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    a.brand(href="/")
+                        .logo #{motto}
+                    ul.nav.pull-right
+                        li
+                            a(href="/") Home
+                        li.active
+                            a(href="/signup") Sign up
+                        li
+                            a(href="/login") Login
+        .container
+            section
+                .row
+                    .span3
+                        h3 Not a new user?
+                        a(href="/login") Click here to log in.
+                    .span13
+                        h3 Why sign up?
+                        p Registration is optional, but if you are a regular user consider creating 
+                            | an account. This will provide the following advantages:
+                        ul
+                            li You will be the one and only owner of your nickname (no one can use 
+                                | your nickname in your place).
+                            li Some simple stats related to your account will be collected.
+                        if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
+                            .alert.alert-error
+                                a.close(data-dismiss="alert") &times;
+                                strong Oh snap!
+                                |  #{errors.alert}
+                        form.form-horizontal.well(method="post",action="/signup")
+                            fieldset
+                                if (typeof(errors) !== "undefined")
+                                    if (typeof(errors.username) !== "undefined")
+                                        .control-group.error
+                                            label.control-label(for="username") Name
+                                            .controls
+                                                input#username(type="text",name="username",
+                                                    value="#{oldvalues.username}")
+                                                span.help-inline #{errors.username}
+                                    else
+                                        .control-group
+                                            label.control-label(for="username") Name
+                                            .controls
+                                                input#username(type="text",name="username",
+                                                    value="#{oldvalues.username}")
+                                    if (typeof(errors.email) !== 'undefined')
+                                        .control-group.error
+                                            label.control-label(for="email") Email
+                                            .controls
+                                                input#email(type="text",name="email",
+                                                    value="#{oldvalues.email}")
+                                                span.help-inline #{errors.email}
+                                    else
+                                        .control-group
+                                            label.control-label(for="email") Email
+                                            .controls
+                                                input#email(type="text",name="email",
+                                                    value="#{oldvalues.email}")
+                                    if (typeof(errors.password) !== 'undefined')
+                                        .control-group.error
+                                            label.control-label(for="password") Password
+                                            .controls
+                                                input#password(type="password",name="password")
+                                                span.help-inline #{errors.password}
+                                    else
+                                        .control-group
+                                            label.control-label(for="password") Password
+                                            .controls
+                                                input#password(type="password",name="password",
+                                                    placeholder="enter a password...")
+                                    if (typeof(errors.captcha) !== 'undefined')
+                                        .control-group.error
+                                            label.control-label(for="captcha-input") Are you human?
+                                            .controls
+                                                img#captcha(src="#{captchaurl}")
+                                                input#captcha-input(type="text",name="captcha")
+                                                span.help-inline #{errors.captcha}
+                                    else
+                                        .control-group
+                                            label.control-label(for="captcha-input") Are you human?
+                                            .controls
+                                                img#captcha(src="#{captchaurl}")
+                                                input#captcha-input(type="text",name="captcha",
+                                                    placeholder="type what you see...")
+                                else
+                                    .control-group
+                                        label.control-label(for="username") Name
+                                        .controls
+                                            input#username(type="text",name="username",
+                                                placeholder="enter a nickname...")
+                                    .control-group
+                                        label.control-label(for="email") Email
+                                        .controls
+                                            input#email(type="text",name="email",
+                                                placeholder="enter a valid email...")
+                                    .control-group
+                                        label.control-label(for="password") Password
+                                        .controls
+                                            input#password(type="password",name="password",
+                                                placeholder="enter a password...")
+                                    .control-group
+                                        label.control-label(for="captcha-input") Are you human?
+                                        .controls
+                                            img#captcha(src="#{captchaurl}")
+                                            input#captcha-input(type="text",name="captcha",
+                                                placeholder="type what you see...")
+                                button#signup-button.btn.btn-success(type="submit")
+                                    i.icon-user.icon-white
+                                    |  Sign up!
+            include footer
index 5942e3b5a11904bf1afcd3d0c8ad447fda757753..c4c91a54f22d3ea9292ec043c1d1b6884bdb9246 100644 (file)
@@ -1,89 +1,89 @@
 doctype html
 html
-       include header
-               title binb :: #{username} info
-       body
-               include uv.jade
-               .navbar.navbar-fixed-top
-                       .navbar-inner
-                               .container
-                                       a.brand(href="#")
-                                               .logo #{motto}
-               .container
-                       section
-                               .row
-                                       .span7.offset1
-                                               .profile #{username}
-                                                       .img
-                                               div member since #{joindate}
-                       section
-                               .row
-                                       .span7.offset1
-                                               h4 Points
-                                               table.table.table-striped.table-bordered.stats
-                                                       tbody
-                                                               tr
-                                                                       td Total
-                                                                       td #{totpoints}
-                                                               tr
-                                                                       td Best score
-                                                                       td #{bestscore}
-                                                               tr
-                                                                       td Guessed songs
-                                                                       td #{guessed}
-                                               h4 Times
-                                               table.table.table-striped.table-bordered.stats
-                                                       tbody
-                                                               tr
-                                                                       td Best guess time
-                                                                       if (bestguesstime !== "30.0")
-                                                                               td #{bestguesstime} sec
-                                                                       else
-                                                                               td N/A
-                                                               tr
-                                                                       td Worst guess time
-                                                                       if (worstguesstime !== "0.0")
-                                                                               td #{worstguesstime} sec
-                                                                       else
-                                                                               td N/A
-                                                               tr
-                                                                       td Mean guess time
-                                                                       if (typeof meanguesstime !== "undefined")
-                                                                               td #{meanguesstime} sec
-                                                                       else
-                                                                               td N/A
-                                       .span7
-                                               h4 Awards
-                                               table.table.table-striped.table-bordered.stats
-                                                       tbody
-                                                               tr
-                                                                       td Gold cups
-                                                                       td
-                                                                               .cups.stand1
-                                                                       td #{golds}
-                                                               tr
-                                                                       td Silver cups
-                                                                       td
-                                                                               .cups.stand2
-                                                                       td #{silvers}
-                                                               tr
-                                                                       td Bronze cups
-                                                                       td
-                                                                               .cups.stand3
-                                                                       td #{bronzes}
-                                                               tr
-                                                                       td Victories
-                                                                       td
-                                                                               .medals.rank1
-                                                                       td #{victories}
-                                                               tr
-                                                                       td Second places
-                                                                       td
-                                                                               .medals.rank2
-                                                                       td #{secondplaces}
-                                                               tr
-                                                                       td Third places
-                                                                       td
-                                                                               .medals.rank3
-                                                                       td #{thirdplaces}
-                       include footer
+    include header
+        title binb :: #{username} info
+    body
+        include uv.jade
+        .navbar.navbar-fixed-top
+            .navbar-inner
+                .container
+                    a.brand(href="#")
+                        .logo #{motto}
+        .container
+            section
+                .row
+                    .span7.offset1
+                        .profile #{username}
+                            .img
+                        div member since #{joindate}
+            section
+                .row
+                    .span7.offset1
+                        h4 Points
+                        table.table.table-striped.table-bordered.stats
+                            tbody
+                                tr
+                                    td Total
+                                    td #{totpoints}
+                                tr
+                                    td Best score
+                                    td #{bestscore}
+                                tr
+                                    td Guessed songs
+                                    td #{guessed}
+                        h4 Times
+                        table.table.table-striped.table-bordered.stats
+                            tbody
+                                tr
+                                    td Best guess time
+                                    if (bestguesstime !== "30.0")
+                                        td #{bestguesstime} sec
+                                    else
+                                        td N/A
+                                tr
+                                    td Worst guess time
+                                    if (worstguesstime !== "0.0")
+                                        td #{worstguesstime} sec
+                                    else
+                                        td N/A
+                                tr
+                                    td Mean guess time
+                                    if (typeof meanguesstime !== "undefined")
+                                        td #{meanguesstime} sec
+                                    else
+                                        td N/A
+                    .span7
+                        h4 Awards
+                        table.table.table-striped.table-bordered.stats
+                            tbody
+                                tr
+                                    td Gold cups
+                                    td
+                                        .cups.stand1
+                                    td #{golds}
+                                tr
+                                    td Silver cups
+                                    td
+                                        .cups.stand2
+                                    td #{silvers}
+                                tr
+                                    td Bronze cups
+                                    td
+                                        .cups.stand3
+                                    td #{bronzes}
+                                tr
+                                    td Victories
+                                    td
+                                        .medals.rank1
+                                    td #{victories}
+                                tr
+                                    td Second places
+                                    td
+                                        .medals.rank2
+                                    td #{secondplaces}
+                                tr
+                                    td Third places
+                                    td
+                                        .medals.rank3
+                                    td #{thirdplaces}
+            include footer
index 9bb615adefc320efc799de9251f320e23cb33bb3..4b29b3430c7eb6f9de8a5574d5c5be4ca8138bc3 100644 (file)
@@ -1,11 +1,11 @@
 script
-       var uvOptions = {};
-       (function() {
-               var uv = document.createElement('script');
-               uv.type = 'text/javascript';
-               uv.async = true;
-               uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 
-                       'widget.uservoice.com/LSMjFAQRifhD6BjOG2KWw.js';
-               var s = document.getElementsByTagName('script')[0];
-               s.parentNode.insertBefore(uv, s);
-       })();
+    var uvOptions = {};
+    (function() {
+        var uv = document.createElement('script');
+        uv.type = 'text/javascript';
+        uv.async = true;
+        uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 
+            'widget.uservoice.com/LSMjFAQRifhD6BjOG2KWw.js';
+        var s = document.getElementsByTagName('script')[0];
+        s.parentNode.insertBefore(uv, s);
+    })();