, parseCookie = require('express/node_modules/cookie').parse
, parseSignedCookies = require('express/node_modules/connect').utils.parseSignedCookies
, redisstore = require('connect-redis')(express)
- , redisurl = require('redis-url')
, site = require('./routes/site')
- , user = require('./routes/user');
-
-/**
- * Setting up redis.
- */
-
-var songsdb = redisurl.createClient(process.env.SONGS_DB_URL)
- , usersdb = redisurl.createClient(process.env.USERS_DB_URL);
-
-songsdb.on('error', function(err) {
- console.log(err.message);
-});
-
-usersdb.on('error', function(err) {
- console.log(err.message);
-});
+ , user = require('./routes/user')
+ , usersdb = require('./lib/redis-clients').users;
/**
* Setting up Express.
*/
var app = express()
- , sessionstore = new redisstore({client:usersdb});
+ , pub = __dirname + '/public' // Path to public directory
+ , sessionstore = new redisstore({client: usersdb});
// Configuration
-app.use(express.static(__dirname + '/public'), {maxAge: 2592000000});
-app.use(express.favicon(__dirname + '/public/static/images/favicon.ico', {maxAge: 2592000000}));
+app.set('view engine', 'jade');
+app.use(express.compress());
+app.use('/static', express.static(pub, {maxAge: 2419200000})); // 4 weeks = 2419200000 ms
+app.use(express.favicon(pub + '/img/favicon.ico', {maxAge: 2419200000}));
app.use(express.bodyParser());
app.use(express.cookieParser(process.env.SITE_SECRET));
-app.use(express.session({store:sessionstore}));
-app.set('view engine', 'jade');
-
-// Middleware to report errors during form submission
-app.use(function(req, res, next) {
- if (req.session.errors) {
- res.locals.errors = req.session.errors;
- delete req.session.errors;
- }
- if (req.session.oldvalues) {
- res.locals.oldvalues = req.session.oldvalues;
- delete req.session.oldvalues;
- }
- next();
-});
+app.use(express.session({store: sessionstore}));
// Routes
-site.use({db:songsdb,rooms:config.rooms});
-user.use({db:usersdb,rooms:config.rooms});
-
-app.get('/', site.index);
+app.get('/', site.home);
app.get('/artworks', site.artworks);
-app.get('/changepasswd', site.changePasswd);
+app.get('/changepasswd', site.validationErrors, site.changePasswd);
app.post('/changepasswd', user.validateChangePasswd, user.checkOldPasswd, user.changePasswd);
app.get('/leaderboards', user.leaderboards);
-app.get('/login', site.login);
+app.get('/login', site.validationErrors, site.login);
app.post('/login', user.validateLogin, user.checkUser, user.authenticate);
app.get('/logout', user.logout);
-app.get('/signup', site.signup);
+app.get('/signup', site.validationErrors, site.signup);
app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
-app.get('/recoverpasswd', site.recoverPasswd);
+app.get('/recoverpasswd', site.validationErrors, site.recoverPasswd);
app.post('/recoverpasswd', user.validateRecoverPasswd, user.sendEmail);
-app.get('/resetpasswd', site.resetPasswd);
+app.get('/resetpasswd', site.validationErrors, site.resetPasswd);
app.post('/resetpasswd', user.resetPasswd);
app.get('/:room', site.room);
app.get('/user/*', user.profile);
if(!data.headers.cookie) {
return accept('no cookie transmitted', false);
}
- var signedcookie = parseCookie(decodeURIComponent(data.headers.cookie));
+ var signedcookie = parseCookie(data.headers.cookie);
var cookie = parseSignedCookies(signedcookie, process.env.SITE_SECRET);
sessionstore.get(cookie['connect.sid'], function(err, session) {
if (err) {
});
socket.on('joinanonymously', function(nickname, roomname) {
if (!socket.nickname && typeof nickname === 'string' && nickname !== '' &&
- config.rooms.indexOf(roomname) !== -1) {
+ ~config.rooms.indexOf(roomname)) {
rooms[roomname].setNickName(socket, nickname);
}
});
socket.on('joinroom', function(room) {
- if (session.user && config.rooms.indexOf(room) !== -1) {
+ if (session.user && ~config.rooms.indexOf(room)) {
if (sockets[session.user]) { // User already in a room
socket.emit('alreadyinaroom');
return;
* 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)
+var Room = require('./lib/room')({io: io, sockets: sockets})
, rooms = Object.create(null); // The Object that contains all the room instances
for (var i=0; i<config.rooms.length; i++) {
, characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
/**
- * Export the class.
+ * Expose the constructor.
*/
module.exports = function() {
+/**
+ * Module dependencies.
+ */
+
+var threshold = require('../config').allowederrors;
+
/**
* 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
};
/**
- * Expose `amatch` function.
+ * Expose a function to check if the user answer is acceptable.
*/
-module.exports = function(allowederrors) {
-
- /**
- * Edit distance threshold.
- */
-
- var threshold = allowederrors;
+module.exports = function(subject, guess, enableartistrules) {
+ if (checkDistance(subject, guess, threshold)) {
+ return true;
+ }
- var amatch = function(subject, guess, enableartistrules) {
- if (checkDistance(subject, guess, threshold)) {
- return true;
- }
+ // Ignore dots
+ if (/\./.test(subject) &&
+ checkDistance(subject.replace(/\./g, ''), guess, threshold)) {
+ return true;
+ }
+ // Ignore dashes
+ if (/\-/.test(subject) &&
+ checkDistance(subject.replace(/\-/g, ''), guess, threshold)) {
+ return true;
+ }
+ // Allow to write "and" in place of "+"
+ if (/\+/.test(subject) &&
+ checkDistance(subject.replace(/\+/, 'and'), guess, threshold)) {
+ return true;
+ }
+ // Allow to write "and" in place of " & "
+ if (/ & /.test(subject) && !/\(/.test(subject) &&
+ checkDistance(subject.replace(/ & /, ' and '), guess, threshold)) {
+ return true;
+ }
- // Ignore dots
- if (/\./.test(subject) &&
- checkDistance(subject.replace(/\./g, ''), guess, threshold)) {
- return true;
- }
- // Ignore dashes
- if (/\-/.test(subject) &&
- checkDistance(subject.replace(/\-/g, ''), guess, threshold)) {
- return true;
- }
- // Allow to write "and" in place of "+"
- if (/\+/.test(subject) &&
- checkDistance(subject.replace(/\+/, 'and'), guess, threshold)) {
- return true;
- }
- // Allow to write "and" in place of " & "
- if (/ & /.test(subject) && !/\(/.test(subject) &&
- checkDistance(subject.replace(/ & /, ' and '), guess, threshold)) {
- return true;
+ if (enableartistrules) {
+ // Ignore "the" at the beginning of artist name
+ if (/^the /.test(subject)) {
+ var nothe = subject.replace(/^the /, '');
+ if (checkDistance(nothe, guess, threshold)) {
+ return true;
+ }
+ if (/jimi hendrix experience/.test(nothe) &&
+ checkDistance(nothe.replace(/ experience/, ''), guess, threshold)) {
+ return true;
+ }
}
-
- if (enableartistrules) {
- // Ignore "the" at the beginning of artist name
- if (/^the /.test(subject)) {
- var nothe = subject.replace(/^the /, '');
- if (checkDistance(nothe, guess, threshold)) {
+ // Split artist name on " & " and ", " (artist name can be composed by more names)
+ var splitted = subject.split(/ & |, /);
+ if (splitted.length !== 1) {
+ for (var i=0; i<splitted.length; i++) {
+ if (checkDistance(splitted[i], guess, threshold)) {
return true;
}
- if (/jimi hendrix experience/.test(nothe) &&
- checkDistance(nothe.replace(/ experience/, ''), guess, threshold)) {
+ if (/^the /.test(splitted[i]) &&
+ checkDistance(splitted[i].replace(/^the /, ''), guess, threshold)) {
return true;
}
}
- // Split artist name on " & " and ", " (artist name can be composed by more names)
- var splitted = subject.split(/ & |, /);
- if (splitted.length !== 1) {
- for (var i=0; i<splitted.length; i++) {
- if (checkDistance(splitted[i], guess, threshold)) {
- return true;
- }
- if (/^the /.test(splitted[i]) &&
- checkDistance(splitted[i].replace(/^the /, ''), guess, threshold)) {
- return true;
- }
- }
- }
}
- else {
- // Ignore commas
- if (/,/.test(subject) &&
- checkDistance(subject.replace(/,/g, ''), guess, threshold)) {
+ }
+ else {
+ // Ignore commas
+ if (/,/.test(subject) &&
+ checkDistance(subject.replace(/,/g, ''), guess, threshold)) {
+ return true;
+ }
+ // Ignore additional info e.g. "(Love Theme from Titanic)"
+ if (/\(.+\)\??(?: \[.+\])?/.test(subject)) {
+ var normalized = subject.replace(/\(.+\)\??(?: \[.+\])?/, '').trim();
+ if (checkDistance(normalized, guess, threshold)) {
return true;
}
- // Ignore additional info e.g. "(Love Theme from Titanic)"
- if (/\(.+\)\??(?: \[.+\])?/.test(subject)) {
- var normalized = subject.replace(/\(.+\)\??(?: \[.+\])?/, '')
- .replace(/^ +/, '').replace(/ +$/, '');
- if (checkDistance(normalized, guess, threshold)) {
- return true;
- }
- if (/ & /.test(normalized) &&
- checkDistance(normalized.replace(/ & /, ' and '), guess, threshold)) {
- return true;
- }
- }
- if (/, [pP]t\. [0-9]$/.test(subject) &&
- checkDistance(subject.replace(/, [pP]t\. [0-9]$/, ''), guess, threshold)) {
+ if (/ & /.test(normalized) &&
+ checkDistance(normalized.replace(/ & /, ' and '), guess, threshold)) {
return true;
}
}
+ if (/, [pP]t\. [0-9]$/.test(subject) &&
+ checkDistance(subject.replace(/, [pP]t\. [0-9]$/, ''), guess, threshold)) {
+ return true;
+ }
+ }
- return false;
- };
-
- return amatch;
+ return false;
};
--- /dev/null
+/**
+ * Module dependencies.
+ */
+
+var redisurl = require('redis-url');
+
+/**
+ * Setting up redis clients.
+ */
+
+var songsclient = redisurl.createClient(process.env.SONGS_DB_URL)
+ , usersclient = redisurl.createClient(process.env.USERS_DB_URL);
+
+songsclient.on('error', function(err) {
+ console.log(err.message);
+});
+
+usersclient.on('error', function(err) {
+ console.log(err.message);
+});
+
+/**
+ * Expose the clients
+ */
+
+exports.songs = songsclient;
+exports.users = usersclient;
/**
- * Expose the `Room` class.
+ * Module dependencies.
*/
-module.exports = function(params) {
-
- /**
- * 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
- , artist // Artists in lowercase
- , artistName
- , artworkUrl
- , collectionName
- , feat // Featured artists
- , finishline = 1
- , playedtracks = [] // The list of already played songs
- , previewUrl
- , songcounter = 0
- , songtimeleft // Milliseconds
- , status
- , title // Title in lowercase
- , trackName
- , trackscount = 0
- , trackViewUrl
- , totusers = 0
- , usersData = Object.create(null);
-
- // 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);
- }
- };
-
- // 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.emit('updateoverview', roomname, totusers);
- socket.emit('ready', usersData, trackscount, loggedin);
- socket.broadcast.to(roomname).emit('newuser', socket.nickname, usersData);
- };
-
- var gameOver = function() {
- status = 3; // Game over
+var amatch = require('./match')
+ , clients = require('./redis-clients')
+ , collectStats = require('./stats')
+ , config = require('../config')
+ , fifolength = config.songsinarun * config.gameswithnorepeats
+ , io
+ , sockets
+ , songsdb = clients.songs
+ , usersdb = clients.users;
- // 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});
- }
+/**
+ * Expose the constructor.
+ */
- resetPoints(false);
- songcounter = 0;
- // Check if FIFO is full
- if (playedtracks.length === fifolength) {
- playedtracks.splice(0, songsinarun);
- }
+module.exports = function(options) {
+ io = options.io;
+ sockets = options.sockets;
+ return Room;
+};
- // Start a new game
- setTimeout(sendLoadTrack, 5000);
- };
+/**
+ * Room constructor.
+ */
- // Return the number of users in the room
- this.getPopulation = function() {
- return totusers;
+function Room(roomname) {
+
+ var allowedguess = false
+ , artist // Artists in lowercase
+ , artistName
+ , artworkUrl
+ , collectionName
+ , feat // Featured artists
+ , finishline = 1
+ , playedtracks = [] // The list of already played songs
+ , previewUrl
+ , songcounter = 0
+ , songtimeleft // Milliseconds
+ , status
+ , title // Title in lowercase
+ , trackName
+ , trackscount = 0
+ , trackViewUrl
+ , totusers = 0
+ , usersData = Object.create(null);
+
+ // 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);
+ }
+ };
+
+ // 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
};
-
- // A user is sending a guess
- this.guess = function(socket, guess) {
- if (allowedguess) {
- if (!usersData[socket.nickname].matched) { // No track no artist
- if ((artist === title) && amatch(title, guess, true)) {
- addPointsAndStats(socket.nickname, true);
- socket.emit('bothmatched');
- io.sockets.in(roomname).emit('updateusers', usersData);
+ totusers++;
+ // Broadcast new user event
+ io.sockets.emit('updateoverview', roomname, totusers);
+ socket.emit('ready', usersData, trackscount, loggedin);
+ socket.broadcast.to(roomname).emit('newuser', socket.nickname, usersData);
+ };
+
+ 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, config.songsinarun);
+ }
+
+ // Start a new game
+ setTimeout(sendLoadTrack, 5000);
+ };
+
+ // Return the number of users in the room
+ this.getPopulation = function() {
+ return totusers;
+ };
+
+ // A user is sending a guess
+ this.guess = function(socket, guess) {
+ if (allowedguess) {
+ if (!usersData[socket.nickname].matched) { // No track no artist
+ if ((artist === title) && amatch(title, guess, true)) {
+ addPointsAndStats(socket.nickname, true);
+ socket.emit('bothmatched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
+ }
+ else if (amatch(artist, guess, true) || (feat && amatch(feat, 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(artist, guess, true) || (feat && amatch(feat, 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(title, 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 if (amatch(title, guess)) {
- usersData[socket.nickname].roundpoints++;
- usersData[socket.nickname].points++;
- usersData[socket.nickname].matched = 'title';
- socket.emit('titlematched');
+ }
+ else {
+ socket.emit('nomatch');
+ }
+ }
+ else if (usersData[socket.nickname].matched !== 'both') { // Track or artist
+ if (usersData[socket.nickname].matched === 'artist') {
+ if (amatch(title, guess)) {
+ addPointsAndStats(socket.nickname, false);
+ socket.emit('bothmatched');
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(title, guess)) {
- addPointsAndStats(socket.nickname, false);
- socket.emit('bothmatched');
- io.sockets.in(roomname).emit('updateusers', usersData);
- }
- else {
- socket.emit('nomatch');
- }
+ else {
+ if (amatch(artist, guess, true) || (feat && amatch(feat, guess, true))) {
+ addPointsAndStats(socket.nickname, false);
+ socket.emit('bothmatched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
}
else {
- if (amatch(artist, guess, true) || (feat && amatch(feat, 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');
- }
- };
-
- this.ignore = function(baduser, executor, callback) {
- // Check if the player to be ignored is in the room
- if (usersData[baduser]) {
- // Inform the bad player that he/she is being ignored
- var recipient = sockets[baduser];
- recipient.emit('chatmsg', executor+' is ignoring you.', 'binb', baduser);
- return callback(baduser);
- }
- callback(false);
- };
-
- this.joinRoom = function(socket) {
- socket.roomname = roomname;
- socket.join(roomname);
- addUser(socket, true);
- };
-
- // Kick a user
- this.kick = function(baduser, executor, callback) {
- usersdb.hget('user:'+executor, 'role', function (err, role) {
- if (role > 0) { // Check role
- if (usersData[baduser]) {
- var notice = 'you have been kicked by '+executor+'.';
- var recipient = sockets[baduser];
- recipient.emit('chatmsg', notice, 'binb', baduser);
- recipient.disconnect();
+ socket.emit('nomatch');
}
- return;
- }
- callback();
- });
- };
-
- // 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.emit('updateoverview', roomname, totusers);
- io.sockets.in(roomname).emit('userleft', nickname, usersData);
- };
-
- 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;
- }
- };
-
- // 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(artist, msglcase, true) ||
- (feat && amatch(feat, msglcase, true)) || amatch(title, msglcase))) {
- var notice = 'You are probably right, but you have to use the box above.';
- socket.emit('chatmsg', notice, 'binb', socket.nickname);
- return;
+ else { // The user has guessed both track and artist
+ socket.emit('stoptrying');
}
- io.sockets.in(roomname).emit('chatmsg', msg, socket.nickname);
- };
-
- // Extract a random track from the database and send the load event
- var sendLoadTrack = function() {
- songsdb.srandmember(roomname, function(err, res) {
- // Check if extracted track is in the list of already played tracks
- if (playedtracks.indexOf(res) !== -1) {
- return sendLoadTrack();
+ }
+ else {
+ socket.emit('noguesstime');
+ }
+ };
+
+ this.ignore = function(baduser, executor, callback) {
+ // Check if the player to be ignored is in the room
+ if (usersData[baduser]) {
+ // Inform the bad player that he/she is being ignored
+ var recipient = sockets[baduser];
+ recipient.emit('chatmsg', executor+' is ignoring you.', 'binb', baduser);
+ return callback(baduser);
+ }
+ callback(false);
+ };
+
+ this.joinRoom = function(socket) {
+ socket.roomname = roomname;
+ socket.join(roomname);
+ addUser(socket, true);
+ };
+
+ // Kick a user
+ this.kick = function(baduser, executor, callback) {
+ usersdb.hget('user:'+executor, 'role', function (err, role) {
+ if (role > 0) { // Check role
+ if (usersData[baduser]) {
+ var notice = 'you have been kicked by '+executor+'.';
+ var recipient = sockets[baduser];
+ recipient.emit('chatmsg', notice, 'binb', baduser);
+ recipient.disconnect();
}
- playedtracks.push(res);
- songsdb.hmget('song:'+res, 'artistName', 'trackName', 'collectionName',
- 'previewUrl', 'artworkUrl60', 'trackViewUrl', function(e, replies) {
- artistName = replies[0];
- artist = artistName.toLowerCase();
- trackName = replies[1];
- title = trackName.toLowerCase();
- feat = /feat\. (.+?)[)\]]/.test(title) ? RegExp.$1 : null;
- 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
- };
-
- 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);
- };
-
- // Send the room status
- this.sendStatus = function(callback) {
- var data = {
- status: status,
- timeleft: songtimeleft,
- previewUrl: previewUrl
- };
- callback(data);
- };
-
- 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);
- };
-
- // 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>';
+ callback();
+ });
+ };
+
+ // 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.emit('updateoverview', roomname, totusers);
+ io.sockets.in(roomname).emit('userleft', nickname, usersData);
+ };
+
+ 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;
}
- else if (sockets[nickname]) {
- feedback = '<span class="label label-important">Name already taken.</span>';
+ usersData[key].roundpoints = 0;
+ usersData[key].matched = null;
+ usersData[key].guesstime = null;
+ }
+ };
+
+ // 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);
}
-
- if (feedback) {
- return socket.emit('invalidnickname', feedback);
+ return;
+ }
+ // Censor answers from chat
+ var msglcase = msg.toLowerCase();
+ if (allowedguess && (amatch(artist, msglcase, true) ||
+ (feat && amatch(feat, msglcase, true)) || amatch(title, 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);
+ };
+
+ // Extract a random track from the database and send the load event
+ var sendLoadTrack = function() {
+ songsdb.srandmember(roomname, function(err, res) {
+ // Check if extracted track is in the list of already played tracks
+ if (~playedtracks.indexOf(res)) {
+ return sendLoadTrack();
}
-
- // 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 socket.emit('invalidnickname', feedback);
- }
- socket.nickname = nickname;
- socket.roomname = roomname;
- socket.join(roomname);
- addUser(socket, false);
+ playedtracks.push(res);
+ songsdb.hmget('song:'+res, 'artistName', 'trackName', 'collectionName',
+ 'previewUrl', 'artworkUrl60', 'trackViewUrl', function(e, replies) {
+ artistName = replies[0];
+ artist = artistName.toLowerCase();
+ trackName = replies[1];
+ title = trackName.toLowerCase();
+ feat = /feat\. (.+?)[)\]]/.test(title) ? RegExp.$1 : null;
+ 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
+ };
+
+ var sendPlayTrack = function() {
+ songcounter++;
+ status = 0; // Playing track
+ var data = {
+ counter: songcounter,
+ tot: config.songsinarun,
+ users: usersData
};
-
- // Timer for the playing song
- var songTimeLeft = function(end, delay) {
- songtimeleft = end - Date.now();
- if (songtimeleft < delay) {
- return;
- }
- setTimeout(songTimeLeft, delay, end, delay);
+ io.sockets.in(roomname).emit('playtrack', data);
+ songTimeLeft(Date.now() + 30000, 50);
+ allowedguess = true;
+ setTimeout(sendTrackInfo, 30000);
+ };
+
+ // Send the room status
+ this.sendStatus = function(callback) {
+ var data = {
+ status: status,
+ timeleft: songtimeleft,
+ previewUrl: previewUrl
};
-
- // Start the room
- this.start = function() {
- songsdb.scard(roomname, function(err, res) {
- trackscount = res;
- });
- sendLoadTrack();
+ callback(data);
+ };
+
+ 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;
- this.unignore = function(baduser, executor) {
- if (usersData[baduser]) {
- // Inform the bad player that he/she is no longer ignored
- var notice = executor+' has stopped ignoring you.';
- var recipient = sockets[baduser];
- recipient.emit('chatmsg', notice, 'binb', baduser);
+ if (songcounter < config.songsinarun) {
+ resetPoints(true);
+ sendLoadTrack();
+ return;
+ }
+
+ status = 2; // Sending last track info
+ setTimeout(gameOver, 5000);
+ };
+
+ // 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 socket.emit('invalidnickname', 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 socket.emit('invalidnickname', feedback);
}
- };
- }
-
- return Room;
-};
+ socket.nickname = nickname;
+ socket.roomname = roomname;
+ socket.join(roomname);
+ addUser(socket, false);
+ });
+ };
+
+ // Timer for the playing song
+ var songTimeLeft = function(end, delay) {
+ songtimeleft = end - Date.now();
+ if (songtimeleft < delay) {
+ return;
+ }
+ setTimeout(songTimeLeft, delay, end, delay);
+ };
+
+ // Start the room
+ this.start = function() {
+ songsdb.scard(roomname, function(err, res) {
+ trackscount = res;
+ });
+ sendLoadTrack();
+ };
+
+ this.unignore = function(baduser, executor) {
+ if (usersData[baduser]) {
+ // Inform the bad player that he/she is no longer ignored
+ var notice = executor+' has stopped ignoring you.';
+ var recipient = sockets[baduser];
+ recipient.emit('chatmsg', notice, 'binb', baduser);
+ }
+ };
+}
/**
- * Return `collectStats` function.
+ * Module dependencies.
*/
-module.exports = function(db) {
+var db = require('./redis-clients').users;
- var collectStats = function(username, stats) {
- var key = 'user:'+username;
- if (stats.points) {
- // Update total points
- db.hincrby(key, 'totpoints', stats.points);
- // Update the score of the member in the sorted set
- db.zincrby('users', stats.points, username);
- }
- 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);
- }
- };
+/**
+ * Expose a function to collect user statistics.
+ */
- return collectStats;
+module.exports = function(username, stats) {
+ var key = 'user:'+username;
+ if (stats.points) {
+ // Update total points
+ db.hincrby(key, 'totpoints', stats.points);
+ // Update the score of the member in the sorted set
+ db.zincrby('users', stats.points, username);
+ }
+ 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);
+ }
};
/**
- * Export the class.
+ * Expose the constructor.
*/
module.exports = function(username, email, salt, hash, joindate) {
--- /dev/null
+/**
+ * Get a random slogan.
+ */
+
+exports.randomSlogan = function() {
+ var slogans = [
+ 'guess the song.'
+ , 'name that tune.'
+ , 'i know this track.'
+ ];
+ return slogans[Math.floor(Math.random() * slogans.length)];
+};
+
+/**
+ * Check if the provided string is a valid email address.
+ */
+
+exports.isEmail = function(str) {
+ // Simple filter, but it covers most of the use cases.
+ var filter = /^[+a-zA-Z0-9_.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z0-9]{2,6}$/;
+ return filter.test(str);
+};
+
+/**
+ * Helper function used to build leaderboards.
+ * Rearrange database results in an object.
+ */
+
+exports.buildLeaderboards = function(pointsresults, timesresults) {
+ var obj = {
+ pointsleaderboard: [],
+ timesleaderboard: []
+ };
+ for (var i=0; i<pointsresults.length; i+=2) {
+ obj.pointsleaderboard.push({
+ username: pointsresults[i],
+ totpoints: pointsresults[i+1]
+ });
+ obj.timesleaderboard.push({
+ username: timesresults[i],
+ bestguesstime: (timesresults[i+1] / 1000).toFixed(2)
+ });
+ }
+ return obj;
+};
"start": "app.js"
},
"subdomain": "binb",
- "version": "0.3.4-3"
+ "version": "0.3.4-6"
}
\ No newline at end of file
.btn-link,.btn-link:active{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
.btn-link{border-color:transparent;cursor:pointer;color:#0088cc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent;}
-[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../images/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px;}
-.icon-white,.nav>.active>a>[class^="icon-"],.nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"]{background-image:url("../images/glyphicons-halflings-white.png");}
+[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px;}
+.icon-white,.nav>.active>a>[class^="icon-"],.nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png");}
.icon-glass{background-position:0 0;}
.icon-music{background-position:-24px 0;}
.icon-search{background-position:-48px 0;}
@font-face {
font-family: 'ChristopherhandRegular';
- src: url('comesinhandy-webfont.eot');
- src: url('comesinhandy-webfont.eot?#iefix') format('embedded-opentype'),
- url('comesinhandy-webfont.woff') format('woff'),
- url('comesinhandy-webfont.ttf') format('truetype');
+ src: url('/static/fonts/comesinhandy-webfont.eot');
+ src: url('/static/fonts/comesinhandy-webfont.eot?#iefix') format('embedded-opentype'),
+ url('/static/fonts/comesinhandy-webfont.woff') format('woff'),
+ url('/static/fonts/comesinhandy-webfont.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
body {
- background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
+ background: url('/static/img/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
padding-top: 45px;
}
section {
text-transform: uppercase;
}
.icons {
- background: url('/static/images/sprites.png') no-repeat;
+ background: url('/static/img/sprites.png') no-repeat;
}
.align-left {
text-align: left;
#cassette {
margin-top: 22px;
height: 137px;
- background: url('/static/images/cassette.png') no-repeat 0 0;
+ background: url('/static/img/cassette.png') no-repeat 0 0;
}
#countdown {
position: absolute;
(function() {
-
+
var elapsedtime = 0
, DOM = {}
, historycursor = 0
, pvtmsgto
, subscriber = false
, roundpoints = 0
+ , roomname = window.location.pathname.replace('/', '')
, socket
, stopanimation = false
, touchplay
- , urlregex = /(https?:\/\/[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])/;
+ , urlregex = /(https?:\/\/[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])/
+ , uri = window.location.protocol+'//'+window.location.host; // Socket.IO server URI
var amstrings = [
'Yes, that\'s the artist. What about the title?'
updateUsers(usersData);
};
- // 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 (breaks socket connections).
- $(document).keypress(function(event) {
- if(event.keyCode === 27) {
- return false;
- }
- });
- }
- var uri = window.location.protocol+'//'+window.location.host;
- socket = io.connect(uri, {'reconnect':false});
- socket.on('connect', function() {
- jplayer = $('#player').jPlayer({
- ready: jplayerReady,
- swfPath: '/static/swf/',
- supplied: 'm4a',
- preload: 'auto',
- volume: 1
- });
- socket.on('alreadyinaroom', alreadyInARoom);
- socket.on('disconnect', disconnect);
- socket.on('invalidnickname', invalidNickName);
- socket.on('ready', ready);
- socket.on('updateoverview', updateRoomsOverview);
- socket.emit('getoverview', roomsOverview);
+ // Set up the app.
+ setVariables();
+ DOM.modal.modal({keyboard:false, show:false, backdrop:'static'});
+ DOM.togglechat.click(hideChat);
+ if ($.browser.mozilla) {
+ // Block ESC button in firefox (breaks socket connections).
+ $(document).keypress(function(event) {
+ if(event.keyCode === 27) {
+ return false;
+ }
+ });
+ }
+ socket = io.connect(uri, {'reconnect':false});
+ socket.on('connect', function() {
+ jplayer = $('#player').jPlayer({
+ ready: jplayerReady,
+ swfPath: '/static/swf/',
+ supplied: 'm4a',
+ preload: 'auto',
+ volume: 1
});
+ socket.on('alreadyinaroom', alreadyInARoom);
+ socket.on('disconnect', disconnect);
+ socket.on('invalidnickname', invalidNickName);
+ socket.on('ready', ready);
+ socket.on('updateoverview', updateRoomsOverview);
+ socket.emit('getoverview', roomsOverview);
});
})();
-$(function() {
+(function() {
if ($.browser.mozilla) {
// Block ESC button in firefox (breaks socket connections).
$(document).keypress(function(event) {
$('#'+room).text(players);
});
});
-});
+})();
var async = require('async')
, Captcha = require('../lib/captcha')
- , db
- , rooms;
+ , db = require('../lib/redis-clients').songs
+ , randomSlogan = require('../lib/utils').randomSlogan
+ , rooms = require('../config').rooms;
/**
- * Generate a task for async.
+ * Generate a task.
*/
var task = function(genre) {
};
};
-/**
- * Initialize dependencies.
- */
-
-exports.use = function(options) {
- db = options.db;
- rooms = options.rooms;
-};
-
/**
* Extract at random in each room, some album covers and return the result as a JSON.
*/
if (!req.session.user) {
return res.redirect('/login?followup=/changepasswd');
}
- res.render('changepasswd', {followup:req.query.followup,loggedin:req.session.user});
+ res.render('changepasswd', {
+ followup: req.query.followup || '/',
+ loggedin: req.session.user,
+ slogan: randomSlogan()
+ });
};
-exports.index = function(req, res) {
- res.render('index', {loggedin:req.session.user,rooms:rooms});
+exports.home = function(req, res) {
+ res.render('home', {
+ loggedin: req.session.user,
+ rooms: rooms,
+ slogan: randomSlogan()
+ });
};
exports.login = function(req, res) {
- res.render('login', {followup:req.query.followup});
+ res.render('login', {
+ followup: req.query.followup || '/',
+ slogan: randomSlogan()
+ });
};
exports.recoverPasswd = function(req, res) {
var captcha = new Captcha();
req.session.captchacode = captcha.getCode();
- res.render('recoverpasswd', {captchaurl:captcha.toDataURL(),followup:req.query.followup});
+ res.render('recoverpasswd', {
+ captchaurl: captcha.toDataURL(),
+ followup: req.query.followup || '/',
+ slogan: randomSlogan()
+ });
};
exports.resetPasswd = function(req, res) {
- res.render('resetpasswd', {token:req.query.token});
+ res.render('resetpasswd', {
+ slogan: randomSlogan(),
+ token: req.query.token || ''
+ });
};
exports.room = function(req, res) {
- if (rooms.indexOf(req.params.room) !== -1) {
- res.render('room', {loggedin:req.session.user,roomname:req.params.room,rooms:rooms});
- }
- else {
- res.send(404);
+ if (~rooms.indexOf(req.params.room)) {
+ return res.render('room', {
+ loggedin: req.session.user,
+ roomname: req.params.room,
+ rooms: rooms,
+ slogan: randomSlogan()
+ });
}
+ res.send(404);
};
exports.signup = function(req, res) {
var captcha = new Captcha();
req.session.captchacode = captcha.getCode();
- res.render('signup', {captchaurl:captcha.toDataURL(),followup:req.query.followup});
+ res.render('signup', {
+ captchaurl: captcha.toDataURL(),
+ followup: req.query.followup || '/',
+ slogan: randomSlogan()
+ });
+};
+
+/**
+ * Report errors during form submission.
+ */
+
+exports.validationErrors = function(req, res, next) {
+ res.locals.errors = req.session.errors;
+ res.locals.oldvalues = req.session.oldvalues;
+ delete req.session.errors;
+ delete req.session.oldvalues;
+ next();
};
*/
var crypto = require('crypto')
- , db
- , followupurls = []
+ , db = require('../lib/redis-clients').users
, mailer = require('../lib/email/mailer')
- , 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])\]))$/);
-};
-
-/**
- * Check if a URL is in the whitelist of follow-up URLs.
- */
-
-var safeFollowup = function(url) {
- if (followupurls.indexOf(url) !== -1) {
- return true;
- }
- return false;
-};
+ , rooms = require('../config').rooms
+ , User = require('../lib/user')
+ , utils = require('../lib/utils');
/**
- * Parameters to get users ordered by best guess time.
+ * Populate the whitelist of follow-up URLs.
*/
-var sortparams = [
- 'users'
- , 'by'
- , 'user:*->bestguesstime'
- , 'get'
- , '#'
- , 'get'
- , 'user:*->bestguesstime'
- , 'limit'
- , '0'
- , '30'
-];
-
-/**
- * Helper function used to build leaderboards.
- * Rearrange database results in an object.
- */
-
-var buildLeaderboards = function(pointsresults, timesresults) {
- var obj = {
- pointsleaderboard: [],
- timesleaderboard: []
- };
- for (var i=0; i<pointsresults.length; i+=2) {
- obj.pointsleaderboard.push({
- username: pointsresults[i],
- totpoints: pointsresults[i+1]
- });
- obj.timesleaderboard.push({
- username: timesresults[i],
- bestguesstime: (timesresults[i+1] / 1000).toFixed(2)
- });
- }
- return obj;
-};
-
-/**
- * Initialize dependencies.
- */
-
-exports.use = function(options) {
- db = options.db;
- var rooms = options.rooms;
- // Populate the whitelist of follow-up URLs
- followupurls.push('/');
- followupurls.push('/changepasswd');
- for (var i=0; i<rooms.length; i++) {
- followupurls.push('/'+rooms[i]);
- }
-};
+var safeurls = ['/', '/changepasswd'];
+for (var i=0; i<rooms.length; i++) {
+ safeurls.push('/'+rooms[i]);
+}
/**
* Show two lists of users, one ordered by points and one by best guess time (limit set to 30).
exports.leaderboards = function(req, res) {
db.zrevrange('users', 0, 29, 'withscores', function(err, pointsresults) {
+ var sortparams = [
+ 'users'
+ , 'by'
+ , 'user:*->bestguesstime'
+ , 'get'
+ , '#'
+ , 'get'
+ , 'user:*->bestguesstime'
+ , 'limit'
+ , '0'
+ , '30'
+ ];
db.sort(sortparams, function (e, timesresults) {
- var leaderboards = buildLeaderboards(pointsresults, timesresults);
+ var leaderboards = utils.buildLeaderboards(pointsresults, timesresults);
+ res.locals.slogan = utils.randomSlogan();
res.render('leaderboards', leaderboards);
});
});
};
exports.changePasswd = function(req, res) {
- var followup = (safeFollowup(req.query.followup)) ? req.query.followup : '/'
+ var followup = ~safeurls.indexOf(req.query.followup) ? req.query.followup : '/'
, user = req.session.user
, key = 'user:'+user
, salt = crypto.randomBytes(6).toString('base64')
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]) {
- var followup = (safeFollowup(req.query.followup)) ? req.query.followup : '/';
+ var followup = ~safeurls.indexOf(req.query.followup) ? req.query.followup : '/';
// Authentication succeeded, regenerate the session
req.session.regenerate(function() {
req.session.cookie.maxAge = 604800000; // One week
else if (!req.body.username.match(/^[^\x00-\x1F\x7F]{1,15}$/)) {
errors.username = '1 to 15 characters required';
}
- if (!req.body.email.isEmail()) {
+ if (!utils.isEmail(req.body.email)) {
errors.email = 'is not an email address';
}
if (!req.body.password.match(/^[A-Za-z0-9]{6,15}$/)) {
db.sadd('emails', req.body.email);
// Delete old fields values
delete req.session.oldvalues;
- var msg = 'You successfully created your account. You are now ready to login.';
- res.render('login', {followup:req.query.followup,success:msg});
+ res.render('login', {
+ followup: req.query.followup || '/',
+ slogan: utils.randomSlogan(),
+ success: 'You successfully created your account. You are now ready to login.'
+ });
};
/**
var errors = {};
- if (!req.body.email.isEmail()) {
+ if (!utils.isEmail(req.body.email)) {
errors.email = 'is not an email address';
}
if (req.body.captcha !== req.session.captchacode) {
db.setex('token:'+token, 14400, data, function(err, reply) {
mailer.sendEmail(req.body.email, token, function(err, response) {
if (err) {
- console.log('reset password error: '+err.message);
+ console.log('error sending email: '+err.message);
}
});
});
delete req.session.oldvalues;
- return res.render('recoverpasswd', {followup:req.query.followup,success:true});
+ return res.render('recoverpasswd', {
+ followup: req.query.followup || '/',
+ slogan: utils.randomSlogan(),
+ success: true
+ });
}
req.session.errors = {alert: 'The email address you specified could not be found'};
res.redirect(req.url);
var salt = crypto.randomBytes(6).toString('base64');
var password = crypto.createHash('sha256').update(salt+req.body.password).digest('hex');
db.hmset(user, 'salt', salt, 'password', password, function(err, data) {
- res.render('login', {success:'You can now login with your new password.'});
+ res.render('login', {
+ followup: '/',
+ slogan: utils.randomSlogan(),
+ success: 'You can now login with your new password.'
+ });
});
return;
}
delete obj.password;
delete obj.salt;
delete obj.totguesstime;
+ res.locals.slogan = utils.randomSlogan();
res.render('user', obj);
});
return;
-followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
-doctype html
-html
- include header
- title binb :: Change password
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .icons.logo #{motto}
- ul.nav.pull-right
- li
- a(href="/") Home
- li.dropdown
- a.dropdown-toggle(data-toggle="dropdown",
- href="#") Logged in as #{loggedin.replace(/&/g, '&')}
- span.caret
- ul.dropdown-menu
- li
- a(href="/user/#{encodeURIComponent(loggedin)}",
- target="_blank") Profile
- li
- a(href="/logout") Logout
- .container
- section
- .row
- .span12.offset2
- form.form-horizontal.well(method="post",action="/changepasswd#{followup}")
- fieldset
- if (typeof(errors) !== "undefined")
- if (typeof(errors.oldpassword) !== "undefined")
- .control-group.error
- label.control-label(for="oldpassword") Old password
- .controls
- input#oldpassword(type="password",name="oldpassword")
- span.help-inline #{errors.oldpassword}
- else
- .control-group
- label.control-label(for="oldpassword") Old password
- .controls
- input#username(type="password",name="oldpassword",
- placeholder="enter your current password...")
- if (typeof(errors.newpassword) !== 'undefined')
- .control-group.error
- label.control-label(for="newpassword") New password
- .controls
- input#password(type="password",name="newpassword")
- span.help-inline #{errors.newpassword}
- else
- .control-group
- label.control-label(for="newpassword") New password
- .controls
- input#password(type="password",name="newpassword",
- placeholder="enter your new password...")
- else
- .control-group
- label.control-label(for="oldpassword") Old password
- .controls
- input#username(type="password",name="oldpassword",
- placeholder="enter your current password...")
- .control-group
- label.control-label(for="password") New password
- .controls
- input#password(type="password",name="newpassword",
- placeholder="enter your new password...")
- button.submit-button.btn.btn-primary(type="submit")
- i.icon-edit.icon-white
- | Update
- include footer
+extends layout
+
+block title
+ title binb :: Change password
+
+block nav
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li.dropdown
+ a.dropdown-toggle(data-toggle="dropdown", href="#")
+ | Logged in as #{loggedin.replace(/&/g, '&')}
+ span.caret
+ ul.dropdown-menu
+ li
+ a(href="/user/#{encodeURIComponent(loggedin)}", target="_blank") Profile
+ li
+ a(href="/logout") Logout
+
+block sections
+ section
+ .row
+ .span12.offset2
+ form.form-horizontal.well(method="post",
+ action="/changepasswd?followup=#{followup}")
+ fieldset
+ - if (locals.errors)
+ - if (errors.oldpassword)
+ .control-group.error
+ label.control-label(for="oldpassword") Old password
+ .controls
+ input#oldpassword(type="password", name="oldpassword")
+ span.help-inline #{errors.oldpassword}
+ - else
+ .control-group
+ label.control-label(for="oldpassword") Old password
+ .controls
+ input#username(type="password", name="oldpassword",
+ placeholder="enter your current password...")
+ - if (errors.newpassword)
+ .control-group.error
+ label.control-label(for="newpassword") New password
+ .controls
+ input#password(type="password", name="newpassword")
+ span.help-inline #{errors.newpassword}
+ - else
+ .control-group
+ label.control-label(for="newpassword") New password
+ .controls
+ input#password(type="password", name="newpassword",
+ placeholder="enter your new password...")
+ - else
+ .control-group
+ label.control-label(for="oldpassword") Old password
+ .controls
+ input#username(type="password", name="oldpassword",
+ placeholder="enter your current password...")
+ .control-group
+ label.control-label(for="password") New password
+ .controls
+ input#password(type="password", name="newpassword",
+ placeholder="enter your new password...")
+ button.submit-button.btn.btn-primary(type="submit")
+ i.icon-edit.icon-white
+ | Update
+++ /dev/null
-footer
- #footer-inner
- #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&send=false&layout=button_count&show_faces=false&action=like&colorscheme=light&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://ghbtns.com/github-btn.html?user=lpinca&repo=binb&type=watch&count=true")
- span.footer-info . Optimized for Google Chrome.
- a#nodejitsu-logo.icons(target="_blank", href="http://nodejitsu.com/")
- span.footer-info Powered by
+++ /dev/null
-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="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/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);
- })();
--- /dev/null
+extends layout
+
+block title
+ title binb
+
+block nav
+ ul.nav.pull-right
+ li.active
+ a(href="/") Home
+ li
+ a(target="_blank", href="/leaderboards")
+ i.icon-list-alt.icon-white
+ | Leaderboards
+ - if (locals.loggedin)
+ li.dropdown
+ a.dropdown-toggle(data-toggle="dropdown", href="#")
+ | Logged in as #{loggedin.replace(/&/g, '&')}
+ span.caret
+ ul.dropdown-menu
+ li
+ a(href="/user/#{encodeURIComponent(loggedin)}", target="_blank") Profile
+ li
+ a(href="/changepasswd") Change password
+ li
+ a(href="/logout") Logout
+ - else
+ li
+ a(href="/signup") Sign up
+ li
+ a(href="/login") Login
+
+block sections
+ 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)
+ | Players
+
+append scripts
+ script(src="/socket.io/socket.io.js")
+ script(src="/static/js/home.js")
+++ /dev/null
-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-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .icons.logo #{motto}
- ul.nav.pull-right
- li.active
- a(href="/") Home
- li
- a(target="_blank", href="/leaderboards")
- i.icon-list-alt.icon-white
- | Leaderboards
- if (typeof(loggedin) !== "undefined")
- li.dropdown
- a.dropdown-toggle(data-toggle="dropdown",
- href="#") Logged in as #{loggedin.replace(/&/g, '&')}
- span.caret
- ul.dropdown-menu
- li
- a(href="/user/#{encodeURIComponent(loggedin)}",
- target="_blank") Profile
- li
- a(href="/changepasswd") Change password
- 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)
- | Players
- include footer
--- /dev/null
+doctype html
+html
+ head
+ meta(charset="utf-8")
+ block title
+ meta(name="author", content="Luigi Pinca")
+ meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
+ link(href="/static/css/bootstrap.min.css", rel="stylesheet")
+ link(href="/static/css/style.css", rel="stylesheet")
+ 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);
+ })();
+ body
+ .navbar.navbar-inverse.navbar-fixed-top
+ .navbar-inner
+ .container
+ block brand
+ a.brand(href="/")
+ .icons.logo #{slogan}
+ block nav
+ .container
+ block sections
+ footer
+ #footer-inner
+ #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&send=false&layout=button_count&show_faces=false&action=like&colorscheme=light&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://ghbtns.com/github-btn.html?user=lpinca&repo=binb&type=watch&count=true")
+ span.footer-info . Optimized for Google Chrome.
+ a#nodejitsu-logo.icons(target="_blank", href="http://nodejitsu.com/")
+ span.footer-info Powered by
+ block media
+ block scripts
+ script(src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js")
+ script(src="/static/js/bootstrap.min.js")
+ 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);
+ })();
-doctype html
-html
- include header
- title binb :: Leaderboards
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="#")
- .icons.logo #{motto}
- .container
- section
- .row
- .span7.offset1
- .highscores High Scores
- .icons.img
- section
- .row
- .span7.offset1
- h4 Points
- div.leaderboard-wrapper
- table.table.table-striped.table-bordered.leaderboard
- tbody
- each user, i in pointsleaderboard
- tr
- td #{i+1}
- td
- a(href="/user/#{encodeURIComponent(user.username)}")
- | #{user.username.replace(/&/g, '&')}
- td #{user.totpoints}
- .span7
- h4 Times
- div.leaderboard-wrapper
- table.table.table-striped.table-bordered.leaderboard
- tbody
- each user, i in timesleaderboard
- tr
- td #{i+1}
- td
- a(href="/user/#{encodeURIComponent(user.username)}")
- | #{user.username.replace(/&/g, '&')}
- td
- i.icon-time
- | #{user.bestguesstime} sec
- include footer
+extends layout
+
+block title
+ title binb :: Leaderboards
+
+block brand
+ a.brand(href="#")
+ .icons.logo #{slogan}
+
+block sections
+ section
+ .row
+ .span7.offset1
+ .highscores High Scores
+ .icons.img
+ section
+ .row
+ .span7.offset1
+ h4 Points
+ div.leaderboard-wrapper
+ table.table.table-striped.table-bordered.leaderboard
+ tbody
+ - each user, i in pointsleaderboard
+ tr
+ td #{i+1}
+ td
+ a(href="/user/#{encodeURIComponent(user.username)}")
+ | #{user.username.replace(/&/g, '&')}
+ td #{user.totpoints}
+ .span7
+ h4 Times
+ div.leaderboard-wrapper
+ table.table.table-striped.table-bordered.leaderboard
+ tbody
+ - each user, i in timesleaderboard
+ tr
+ td #{i+1}
+ td
+ a(href="/user/#{encodeURIComponent(user.username)}")
+ | #{user.username.replace(/&/g, '&')}
+ td
+ i.icon-time
+ | #{user.bestguesstime} sec
+
+block scripts
-followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
-doctype html
-html
- include header
- title binb :: login
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .icons.logo #{motto}
- ul.nav.pull-right
- li
- a(href="/") Home
- li
- a(href="/signup#{followup}") Sign up
- li.active
- a(href="/login#{followup}") Login
- .container
- section
- .row
- .span3
- h3 New user?
- a(href="/signup#{followup}") Click here to create an account.
- .span13
- if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
- .alert.alert-error
- a.close(data-dismiss="alert") ×
- strong Oh snap!
- | #{errors.alert}
- else if (typeof(success) !== "undefined")
- .alert.alert-success
- a.close(data-dismiss="alert") ×
- strong Well done!
- | #{success}
- form.form-horizontal.well(method="post",action="/login#{followup}")
- 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.submit-button.btn.btn-primary(type="submit")
- i.icon-lock.icon-white
- | Login
- a.forgot-passwd(href="/recoverpasswd#{followup}")
- | Forgot your password?
- include footer
+extends layout
+
+block title
+ title binb :: login
+
+block nav
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li
+ a(href="/signup?followup=#{followup}") Sign up
+ li.active
+ a(href="/login?followup=#{followup}") Login
+
+block sections
+ section
+ .row
+ .span3
+ h3 New user?
+ a(href="/signup?followup=#{followup}") Click here to create an account.
+ .span13
+ - if (locals.errors && errors.alert)
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ - else if (locals.success)
+ .alert.alert-success
+ a.close(data-dismiss="alert") ×
+ strong Well done!
+ | #{success}
+ form.form-horizontal.well(method="post", action="/login?followup=#{followup}")
+ fieldset
+ - if (locals.errors)
+ - if (errors.username)
+ .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 (errors.password)
+ .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.submit-button.btn.btn-primary(type="submit")
+ i.icon-lock.icon-white
+ | Login
+ a.forgot-passwd(href="/recoverpasswd?followup=#{followup}")
+ | Forgot your password?
-followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
-doctype html
-html
- include header
- title binb :: Recover password
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .icons.logo #{motto}
- ul.nav.pull-right
- li
- a(href="/") Home
- li
- a(href="/signup#{followup}") Sign up
- li
- a(href="/login#{followup}") Login
- .container
- section
- .row
- .span12.offset2
- if (typeof(success) !== "undefined")
- .alert.alert-block.alert-success
- h4.alert-heading Success!
- | An email has been sent to you.<br>To start the password reset
- | process, open this email and follow the given instructions.<br>
- | If you don't receive it in a reasonable amount of time, please
- | use the support form on the left.
- else
- if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
- .alert.alert-error
- a.close(data-dismiss="alert") ×
- strong Oh snap!
- | #{errors.alert}
- form.form-horizontal.well(method="post",
- action="/recoverpasswd#{followup}")
- fieldset
- if (typeof(errors) !== "undefined")
- if (typeof(errors.email) !== "undefined")
- .control-group.error
- label.control-label(for="email") Email
- .controls
- input#oldpassword(type="text",name="email",
- value=oldvalues.email)
- span.help-inline #{errors.email}
- else
- .control-group
- label.control-label(for="email") Email
- .controls
- input#username(type="text",name="email",
- value=oldvalues.email)
- 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="email") Email
- .controls
- input#email(type="text",name="email",
- placeholder="type the email you used to sign up...")
- .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.submit-button.btn.btn-primary(type="submit")
- i.icon-envelope.icon-white
- | Send password reset link
- include footer
+extends layout
+
+block title
+ title binb :: Recover password
+
+block nav
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li
+ a(href="/signup?followup=#{followup}") Sign up
+ li
+ a(href="/login?followup=#{followup}") Login
+
+block sections
+ section
+ .row
+ .span12.offset2
+ - if (locals.success)
+ .alert.alert-block.alert-success
+ h4.alert-heading Success!
+ | An email has been sent to you.<br>To start the password reset
+ | process, open this email and follow the given instructions.<br>
+ | If you don't receive it in a reasonable amount of time, please
+ | use the support form on the left.
+ - else
+ - if (locals.errors && errors.alert)
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ form.form-horizontal.well(method="post",
+ action="/recoverpasswd?followup=#{followup}")
+ fieldset
+ - if (locals.errors)
+ - if (errors.email)
+ .control-group.error
+ label.control-label(for="email") Email
+ .controls
+ input#oldpassword(type="text", name="email",
+ value=oldvalues.email)
+ span.help-inline #{errors.email}
+ - else
+ .control-group
+ label.control-label(for="email") Email
+ .controls
+ input#username(type="text", name="email",
+ value=oldvalues.email)
+ - if (errors.captcha)
+ .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="email") Email
+ .controls
+ input#email(type="text", name="email",
+ placeholder="type the email you used to sign up...")
+ .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.submit-button.btn.btn-primary(type="submit")
+ i.icon-envelope.icon-white
+ | Send password reset link
-token = (typeof(token) !== "undefined") ? '?token='+token : ''
-doctype html
-html
- include header
- title binb :: Reset password
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="#")
- .icons.logo #{motto}
- .container
- section
- .row
- .span12.offset2
- if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
- .alert.alert-error
- a.close(data-dismiss="alert") ×
- strong Oh snap!
- | #{errors.alert}
- form.form-horizontal.well(method="post",action="/resetpasswd#{token}")
- fieldset
- if (typeof(errors) !== "undefined")
- if (typeof(errors.password) !== "undefined")
- .control-group.error
- label.control-label(for="password") New password
- .controls
- input#oldpassword(type="password",name="password")
- span.help-inline #{errors.password}
- else
- .control-group
- label.control-label(for="password") New password
- .controls
- input#username(type="password",name="password",
- placeholder="enter your new password...")
- else
- .control-group
- label.control-label(for="oldpassword") New password
- .controls
- input#username(type="password",name="password",
- placeholder="enter your new password...")
- button.submit-button.btn.btn-primary(type="submit")
- i.icon-edit.icon-white
- | Update
- include footer
+extends layout
+
+block title
+ title binb :: Reset password
+
+block brand
+ a.brand(href="#")
+ .icons.logo #{slogan}
+
+block sections
+ section
+ .row
+ .span12.offset2
+ - if (locals.errors && errors.alert)
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ form.form-horizontal.well(method="post", action="/resetpasswd?token=#{token}")
+ fieldset
+ - if (locals.errors)
+ - if (errors.password)
+ .control-group.error
+ label.control-label(for="password") New password
+ .controls
+ input#oldpassword(type="password", name="password")
+ span.help-inline #{errors.password}
+ - else
+ .control-group
+ label.control-label(for="password") New password
+ .controls
+ input#username(type="password", name="password",
+ placeholder="enter your new password...")
+ - else
+ .control-group
+ label.control-label(for="oldpassword") New password
+ .controls
+ input#username(type="password", name="password",
+ placeholder="enter your new password...")
+ button.submit-button.btn.btn-primary(type="submit")
+ i.icon-edit.icon-white
+ | Update
-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-inverse.navbar-fixed-top
- .navbar-inner
- .container
- ul.nav.pull-right
+extends layout
+
+block title
+ title binb :: #{roomname}
+
+block nav
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li
+ a(target="_blank", href="/leaderboards")
+ i.icon-list-alt.icon-white
+ | Leaderboards
+ 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="/") Home
- li
- a(target="_blank", href="/leaderboards")
- i.icon-list-alt.icon-white
- | Leaderboards
- 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)
- span.room-name #{item}
- i.icon-user.pull-right
- span.users-counter
- if (typeof(loggedin) !== "undefined")
- li.dropdown
- a.dropdown-toggle(data-toggle="dropdown",
- href="#") Logged in as #{loggedin.replace(/&/g, '&')}
- span.caret
- ul.dropdown-menu
- li
- a(href="/user/#{encodeURIComponent(loggedin)}",
- target="_blank") Profile
- li
- a(href="/changepasswd?followup=/#{roomname}")
- | Change password
- li
- a(href="/logout") Logout
- else
- li
- a(href="/signup?followup=/#{roomname}") Sign up
- li
- a(href="/login?followup=/#{roomname}") Login
- #player
- #modal.modal.fade
- .container
- section
- .row
- .span4.offset1
- #cassette.relative
- #wheel-left.icons.wheel
- #tape-left
- #tape-right
- #wheel-right.icons.wheel
- #progress-bar
- #progress
- #countdown
+ a(href=item)
+ span.room-name #{item}
+ i.icon-user.pull-right
+ span.users-counter
+ - if (locals.loggedin)
+ li.dropdown
+ a.dropdown-toggle(data-toggle="dropdown", href="#")
+ | Logged in as #{loggedin.replace(/&/g, '&')}
+ span.caret
+ ul.dropdown-menu
+ li
+ a(href="/user/#{encodeURIComponent(loggedin)}", target="_blank") Profile
+ li
+ a(href="/changepasswd?followup=/#{roomname}") Change password
+ li
+ a(href="/logout") Logout
+ - else
+ li
+ a(href="/signup?followup=/#{roomname}") Sign up
+ li
+ a(href="/login?followup=/#{roomname}") Login
+
+block sections
+ section
+ .row
+ .span4.offset1
+ #cassette.relative
+ #wheel-left.icons.wheel
+ #tape-left
+ #tape-right
+ #wheel-right.icons.wheel
+ #progress-bar
+ #progress
+ #countdown
+ .span2
+ #volume.relative
+ .span8
+ .page-header
+ .icons.logo #{slogan}
+ #total-tracks
+ span
+ | tracks.
+ #summary.row
+ .span2
+ .title Rank
+ .rank
+ .span4
+ .title Points
+ .points
.span2
- #volume.relative
- .span8
- .page-header
- .icons.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
+ .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
+ | .
+
+block media
+ #modal.modal.fade
+ #player
+
+append scripts
+ script(src="/static/js/jquery.jplayer.min.js")
+ script(src="/socket.io/socket.io.js")
+ script(src="/static/js/app.js")
-followup = (typeof(followup) !== "undefined") ? '?followup='+followup : '?followup=/'
-doctype html
-html
- include header
- title binb :: sign up
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .icons.logo #{motto}
- ul.nav.pull-right
- li
- a(href="/") Home
- li.active
- a(href="/signup#{followup}") Sign up
- li
- a(href="/login#{followup}") Login
- .container
- section
- .row
- .span3
- h3 Not a new user?
- a(href="/login#{followup}") 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") ×
- strong Oh snap!
- | #{errors.alert}
- form.form-horizontal.well(method="post",action="/signup#{followup}")
- 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.submit-button.btn.btn-success(type="submit")
- i.icon-user.icon-white
- | Sign up!
- include footer
+extends layout
+
+block title
+ title binb :: sign up
+
+block nav
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li.active
+ a(href="/signup?followup=#{followup}") Sign up
+ li
+ a(href="/login?followup=#{followup}") Login
+
+block sections
+ section
+ .row
+ .span3
+ h3 Not a new user?
+ a(href="/login?followup=#{followup}") 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 (locals.errors && errors.alert)
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ form.form-horizontal.well(method="post",action="/signup?followup=#{followup}")
+ fieldset
+ - if (locals.errors)
+ - if (errors.username)
+ .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 (errors.email)
+ .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 (errors.password)
+ .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 (errors.captcha)
+ .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.submit-button.btn.btn-success(type="submit")
+ i.icon-user.icon-white
+ | Sign up!
-doctype html
-html
- include header
- title binb :: #{username.replace(/&/g, '&')} info
- body
- include uv.jade
- .navbar.navbar-inverse.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="#")
- .icons.logo #{motto}
- .container
- section
- .row
- .span7.offset1
- .profile #{username.replace(/&/g, '&')}
- .icons.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
- .icons.cups.stand1
- td #{golds}
- tr
- td Silver cups
- td
- .icons.cups.stand2
- td #{silvers}
- tr
- td Bronze cups
- td
- .icons.cups.stand3
- td #{bronzes}
- tr
- td Victories
- td
- .icons.medals.rank1
- td #{victories}
- tr
- td Second places
- td
- .icons.medals.rank2
- td #{secondplaces}
- tr
- td Third places
- td
- .icons.medals.rank3
- td #{thirdplaces}
- include footer
+extends layout
+
+block title
+ title binb :: #{username.replace(/&/g, '&')} info
+
+block brand
+ a.brand(href="#")
+ .icons.logo #{slogan}
+
+block sections
+ section
+ .row
+ .span7.offset1
+ .profile #{username.replace(/&/g, '&')}
+ .icons.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
+ .icons.cups.stand1
+ td #{golds}
+ tr
+ td Silver cups
+ td
+ .icons.cups.stand2
+ td #{silvers}
+ tr
+ td Bronze cups
+ td
+ .icons.cups.stand3
+ td #{bronzes}
+ tr
+ td Victories
+ td
+ .icons.medals.rank1
+ td #{victories}
+ tr
+ td Second places
+ td
+ .icons.medals.rank2
+ td #{secondplaces}
+ tr
+ td Third places
+ td
+ .icons.medals.rank3
+ td #{thirdplaces}
+
+block scripts
+++ /dev/null
-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);
- })();