Merge pull request #263 from calzoneman/dev

v2.4 - run npm update
This commit is contained in:
Calvin Montgomery 2013-08-19 11:11:55 -07:00
commit 89f4da20c5
22 changed files with 2992 additions and 3100 deletions

169
acp.js
View file

@ -9,10 +9,11 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var Auth = require("./auth");
var ActionLog = require("./actionlog");
var Logger = require("./logger");
module.exports = function (Server) {
var db = Server.db;
var ActionLog = Server.actionlog;
return {
init: function(user) {
ActionLog.record(user.ip, user.name, "acp-init");
@ -28,118 +29,84 @@ module.exports = function (Server) {
});
user.socket.on("acp-global-ban", function(data) {
ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip);
Server.db.globalBanIP(data.ip, data.note);
user.socket.emit("acp-global-banlist", Server.db.refreshGlobalBans());
ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip);
db.setGlobalIPBan(data.ip, data.note, function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
});
});
user.socket.on("acp-global-unban", function(ip) {
ActionLog.record(user.ip, user.name, "acp-global-unban", ip);
Server.db.globalUnbanIP(ip);
user.socket.emit("acp-global-banlist", Server.db.refreshGlobalBans());
db.clearGlobalIPBan(ip, function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
});
});
user.socket.emit("acp-global-banlist", Server.db.refreshGlobalBans());
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
user.socket.on("acp-lookup-user", function(name) {
var db = Server.db.getConnection();
if(!db) {
return;
}
var query = Server.db.createQuery(
"SELECT id,uname,global_rank,profile_image,profile_text,email FROM registrations WHERE uname LIKE ?",
["%"+name+"%"]
);
var res = db.querySync(query);
if(!res)
return;
var rows = res.fetchAllSync();
user.socket.emit("acp-userdata", rows);
db.searchUser(name, function (err, res) {
res = res || [];
user.socket.emit("acp-userdata", res);
});
});
user.socket.on("acp-lookup-channel", function (data) {
var db = Server.db.getConnection();
if(!db) {
return;
}
var query;
if(data.field === "owner") {
query = Server.db.createQuery(
"SELECT * FROM channels WHERE owner LIKE ?",
["%" + data.value + "%"]
);
} else if (data.field === "name") {
query = Server.db.createQuery(
"SELECT * FROM channels WHERE name LIKE ?",
["%" + data.value + "%"]
);
} else {
return;
}
var results = db.querySync(query);
if(!results)
return;
var rows = results.fetchAllSync();
user.socket.emit("acp-channeldata", rows);
db.searchChannel(data.field, data.value, function (e, res) {
res = res || [];
user.socket.emit("acp-channeldata", res);
});
});
user.socket.on("acp-reset-password", function(data) {
if(Auth.getGlobalRank(data.name) >= user.global_rank)
return;
try {
var hash = Server.db.generatePasswordReset(user.ip, data.name, data.email);
ActionLog.record(user.ip, user.name, "acp-reset-password", data.name);
}
catch(e) {
user.socket.emit("acp-reset-password", {
success: false,
error: e
});
return;
}
if(hash) {
user.socket.emit("acp-reset-password", {
success: true,
hash: hash
});
}
else {
user.socket.emit("acp-reset-password", {
success: false,
error: "Reset failed"
});
}
db.getGlobalRank(data.name, function (err, rank) {
if(err || rank >= user.global_rank)
return;
db.genPasswordReset(user.ip, data.name, data.email,
function (err, hash) {
var pkt = {
success: !err
};
if(err) {
pkt.error = err;
} else {
pkt.hash = hash;
}
user.socket.emit("acp-reset-password", pkt);
ActionLog.record(user.ip, user.name,
"acp-reset-password", data.name);
});
});
});
user.socket.on("acp-set-rank", function(data) {
if(data.rank < 1 || data.rank >= user.global_rank)
return;
if(Auth.getGlobalRank(data.name) >= user.global_rank)
return;
db.getGlobalRank(data.name, function (err, rank) {
if(err || rank >= user.global_rank)
return;
var db = Server.db.getConnection();
if(!db)
return;
ActionLog.record(user.ip, user.name, "acp-set-rank", data);
var query = Server.db.createQuery(
"UPDATE registrations SET global_rank=? WHERE uname=?",
[data.rank, data.name]
);
var res = db.querySync(query);
if(!res)
return;
user.socket.emit("acp-set-rank", data);
db.setGlobalRank(data.name, data.rank,
function (err, res) {
ActionLog.record(user.ip, user.name, "acp-set-rank",
data);
if(!err)
user.socket.emit("acp-set-rank", data);
});
});
});
user.socket.on("acp-list-loaded", function() {
@ -180,9 +147,10 @@ module.exports = function (Server) {
});
user.socket.on("acp-actionlog-list", function () {
user.socket.emit("acp-actionlog-list",
ActionLog.getLogTypes()
);
ActionLog.listActionTypes(function (err, types) {
if(!err)
user.socket.emit("acp-actionlog-list", types);
});
});
user.socket.on("acp-actionlog-clear", function(data) {
@ -196,13 +164,10 @@ module.exports = function (Server) {
});
user.socket.on("acp-view-stats", function () {
var db = Server.db.getConnection();
if(!db)
return;
var query = "SELECT * FROM stats ORDER BY time ASC";
var results = db.querySync(query);
if(results)
user.socket.emit("acp-view-stats", results.fetchAllSync());
db.listStats(function (err, res) {
if(!err)
user.socket.emit("acp-view-stats", res);
});
});
}
}

View file

@ -9,143 +9,50 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var Database = require("./database");
var Logger = require("./logger");
exports.record = function(ip, name, action, args) {
if(typeof args === "undefined" || args === null) {
args = "";
} else {
try {
args = JSON.stringify(args);
} catch(e) {
args = "";
module.exports = function (Server) {
var db = Server.db;
return {
record: function (ip, name, action, args) {
if(!args)
args = "";
else {
try {
args = JSON.stringify(args);
} catch(e) {
args = "";
}
}
db.recordAction(ip, name, action, args);
},
clear: function (actions) {
db.clearActions(actions);
},
clearOne: function (item) {
db.clearSingleAction(item);
},
throttleRegistrations: function (ip, callback) {
db.recentRegistrationCount(ip, function (err, count) {
if(err) {
callback(err, null);
return;
}
callback(null, count > 4);
});
},
listActionTypes: function (callback) {
db.listActionTypes(callback);
},
listActions: function (types, callback) {
db.listActions(types, callback);
}
}
var db = Database.getConnection();
if(!db)
return false;
var query = Database.createQuery(
"INSERT INTO actionlog (ip, name, action, args, time) "+
"VALUES (?, ?, ?, ?, ?)",
[ip, name, action, args, Date.now()]
);
var result = db.querySync(query);
if(!result) {
Logger.errlog.log("! Failed to record action");
}
return result;
}
exports.clear = function(actions) {
var db = Database.getConnection();
if(!db)
return false;
var list = new Array(actions.length);
for(var i = 0; i < actions.length; i++)
list[i] = "?";
var query = Database.createQuery(
"DELETE FROM actionlog WHERE action IN ("+
list.join(",")+
")",
actions
);
var result = db.querySync(query);
if(!result) {
Logger.errlog.log("! Failed to clear action log");
}
return result;
}
exports.clearOne = function(e) {
var db = Database.getConnection();
if(!db)
return false;
var query = Database.createQuery(
"DELETE FROM actionlog WHERE ip=? AND time=?",
[e.ip, e.time]
);
var result = db.querySync(query);
if(!result) {
Logger.errlog.log("! Failed to clear action log");
}
return result;
}
exports.tooManyRegistrations = function (ip) {
var db = Database.getConnection();
if(!db)
return true;
var query = Database.createQuery(
"SELECT * FROM actionlog WHERE ip=? AND action='register-success'"+
"AND time > ?",
[ip, Date.now() - 48 * 3600 * 1000]
);
var results = db.querySync(query);
if(!results) {
Logger.errlog.log("! Failed to check tooManyRegistrations");
return true;
}
var rows = results.fetchAllSync();
// TODO Config value for this
return rows.length > 4;
}
exports.getLogTypes = function () {
var db = Database.getConnection();
if(!db)
return false;
var query = "SELECT DISTINCT action FROM actionlog";
var result = db.querySync(query);
if(!result) {
Logger.errlog.log("! Failed to read action log");
return [];
}
result = result.fetchAllSync();
var actions = [];
for(var i in result)
actions.push(result[i].action);
return actions;
}
exports.readLog = function (actions) {
var db = Database.getConnection();
if(!db)
return false;
var query = "SELECT * FROM actionlog";
if(actions !== undefined) {
var list = new Array(actions.length);
for(var i in actions)
list[i] = "?";
list = list.join(",");
query += Database.createQuery(
" WHERE action IN ("+list+")",
actions
);
}
var result = db.querySync(query);
if(!result) {
Logger.errlog.log("! Failed to read action log");
return [];
}
return result.fetchAllSync();
}
};
};

1089
api.js

File diff suppressed because it is too large Load diff

226
auth.js
View file

@ -1,226 +0,0 @@
/*
The MIT License (MIT)
Copyright (c) 2013 Calvin Montgomery
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var mysql = require("mysql-libmysqlclient");
var Database = require("./database.js");
var bcrypt = require("bcrypt");
var hashlib = require("node_hash");
var Logger = require("./logger.js");
// Check if a name is taken
exports.isRegistered = function(name) {
var db = Database.getConnection();
if(!db) {
throw "Database failure";
}
var query = Database.createQuery(
"SELECT * FROM `registrations` WHERE uname=?",
[name]
);
var results = db.querySync(query);
if(!results) {
return true;
}
var rows = results.fetchAllSync();
return rows.length > 0;
}
// Check if a name is valid
// Valid names are 1-20 characters, alphanumeric and underscores
exports.validateName = function(name) {
if(name.length > 20)
return false;
const VALID_REGEX = /^[a-zA-Z0-9_]+$/;
return name.match(VALID_REGEX) != null;
}
// Try to register a new account
exports.register = function(name, pw) {
if(!exports.validateName(name))
return false;
if(exports.isRegistered(name))
return false;
var db = Database.getConnection();
if(!db) {
return false;
}
var hash = bcrypt.hashSync(pw, 10);
var query = Database.createQuery(
["INSERT INTO `registrations` VALUES ",
"(NULL, ?, ?, 1, '', 0, '', '', '')"].join(""),
[name, hash]
);
var results = db.querySync(query);
if(results) {
return exports.createSession(name);
}
return false;
}
exports.login = function(name, pw, session) {
if(session) {
var res = exports.loginSession(name, session);
if(res) {
return res;
}
else if(!pw) {
return false;
}
}
var row = exports.loginPassword(name, pw);
if(row) {
var hash = exports.createSession(name);
row.session_hash = hash;
return row;
}
}
// Try to login
exports.loginPassword = function(name, pw) {
var db = Database.getConnection();
if(!db) {
throw "Database failure";
}
var query = Database.createQuery(
"SELECT * FROM `registrations` WHERE uname=?",
[name]
);
var results = db.querySync(query);
if(!results) {
return false;
}
var rows = results.fetchAllSync();
if(rows.length > 0) {
try {
if(bcrypt.compareSync(pw, rows[0].pw)) {
return rows[0];
}
else {
// Check if the sha256 is in the database
// If so, migrate to bcrypt
var sha256 = hashlib.sha256(pw);
if(sha256 == rows[0].pw) {
var newhash = bcrypt.hashSync(pw, 10);
var query = Database.createQuery(
["UPDATE `registrations` SET pw=?",
"WHERE uname=?"].join(""),
[newhash, name]
);
var results = db.querySync(query);
if(!results) {
Logger.errlog.log("Failed to migrate password! user=" + name);
return false;
}
return rows[0];
}
return false;
}
}
catch(e) {
Logger.errlog.log("Auth.login fail");
Logger.errlog.log(e);
}
}
return false;
}
exports.createSession = function(name) {
var salt = sessionSalt();
var hash = hashlib.sha256(salt + name);
var db = Database.getConnection();
if(!db) {
throw "Database failure";
}
var query = Database.createQuery(
["UPDATE `registrations` SET ",
"`session_hash`=?,",
"`expire`=? ",
"WHERE uname=?"].join(""),
[hash, Date.now() + 604800000, name]
);
var results = db.querySync(query);
return results ? hash : false;
}
exports.loginSession = function(name, hash) {
var db = Database.getConnection();
if(!db) {
throw "Database failure";
}
var query = Database.createQuery(
"SELECT * FROM `registrations` WHERE `uname`=?",
[name]
);
var results = db.querySync(query);
if(!results) {
return false;
}
var rows = results.fetchAllSync();
if(rows.length != 1) {
return false;
}
var dbhash = rows[0].session_hash;
if(hash != dbhash) {
return false;
}
var timeout = rows[0].expire;
if(timeout < new Date().getTime()) {
return false;
}
return rows[0];
}
function sessionSalt() {
var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789!@#$%^&*_+=~";
var salt = [];
for(var i = 0; i < 32; i++) {
salt.push(chars[parseInt(Math.random()*chars.length)]);
}
return salt.join('');
}
exports.setUserPassword = function(name, pw) {
var db = Database.getConnection();
if(!db) {
return false;
}
var hash = bcrypt.hashSync(pw, 10);
var query = Database.createQuery(
"UPDATE `registrations` SET `pw`=? WHERE `uname`=?",
[hash, name]
);
var result = db.querySync(query);
return result;
}
exports.getGlobalRank = function(name) {
var db = Database.getConnection();
if(!db) {
return false;
}
var query = Database.createQuery(
"SELECT * FROM `registrations` WHERE `uname`=?",
[name]
);
var results = db.querySync(query);
if(!results) {
return 0;
}
var rows = results.fetchAllSync();
if(rows.length > 0) {
return rows[0].global_rank;
}
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -145,10 +145,21 @@ function handleUnmute(chan, user, args) {
function handleKick(chan, user, args) {
if(chan.hasPermission(user, "kick") && args.length > 0) {
args[0] = args[0].toLowerCase();
if(args[0] == user.name.toLowerCase()) {
user.socket.emit("costanza", {
msg: "Kicking yourself?"
});
return;
}
var kickee;
for(var i = 0; i < chan.users.length; i++) {
if(chan.users[i].name.toLowerCase() == args[0] &&
chan.getRank(chan.users[i].name) < user.rank) {
if(chan.users[i].name.toLowerCase() == args[0]) {
if(chan.users[i].rank >= user.rank) {
user.socket.emit("errorMsg", {
msg: "You don't have permission to kick " + args[0]
});
return;
}
kickee = chan.users[i];
break;
}

View file

@ -42,17 +42,14 @@ var defaults = {
}
function save(cfg, file) {
if(!cfg.loaded)
return;
var x = {};
for(var k in cfg) {
if(k !== "nodemailer")
if(k !== "nodemailer" && k !== "loaded")
x[k] = cfg[k];
}
fs.writeFile(file, JSON.stringify(x, null, 4), function (err) {
if(err) {
Logger.errlog.log("Failed to save config");
Logger.errlog.log(err);
}
});
fs.writeFileSync(file, JSON.stringify(x, null, 4));
}
exports.load = function (Server, file, callback) {
@ -92,6 +89,8 @@ exports.load = function (Server, file, callback) {
);
}
cfg["loaded"] = true;
save(cfg, file);
Server.cfg = cfg;
callback();

File diff suppressed because it is too large Load diff

View file

@ -2,14 +2,14 @@
"author": "Calvin Montgomery",
"name": "CyTube",
"description": "Online media synchronizer and chat",
"version": "2.3.3",
"version": "2.4.0",
"repository": {
"url": "http://github.com/calzoneman/sync"
},
"dependencies": {
"socket.io": ">=0.9",
"express": ">=3.2",
"mysql-libmysqlclient": "*",
"mysql": "2.0.0-alpha8",
"node_hash": "*",
"bcrypt": "*",
"nodemailer": "*",

View file

@ -6,7 +6,7 @@ var Logger = require("./logger");
var Channel = require("./channel");
var User = require("./user");
const VERSION = "2.3.3";
const VERSION = "2.4.0";
function getIP(req) {
var raw = req.connection.remoteAddress;
@ -74,6 +74,7 @@ var Server = {
ips: {},
acp: null,
httpaccess: null,
actionlog: null,
logHTTP: function (req, status) {
if(status === undefined)
status = 200;
@ -89,8 +90,14 @@ var Server = {
this.httpaccess.log([ipstr, req.method, url, status, req.headers["user-agent"]].join(" "));
},
init: function () {
// init database
var Database = require("./database");
this.db = new Database(this.cfg);
this.db.init();
this.actionlog = require("./actionlog")(this);
this.httpaccess = new Logger.Logger("httpaccess.log");
this.app = express();
this.app.use(express.bodyParser());
// channel path
this.app.get("/r/:channel(*)", function (req, res, next) {
var c = req.params.channel;
@ -105,10 +112,12 @@ var Server = {
// api path
this.api = require("./api")(this);
/*
this.app.get("/api/:apireq(*)", function (req, res, next) {
this.logHTTP(req);
this.api.handle(req.url.substring(5), req, res);
}.bind(this));
*/
this.app.get("/", function (req, res, next) {
this.logHTTP(req);
@ -167,14 +176,15 @@ var Server = {
this.io.sockets.on("connection", function (socket) {
var ip = getSocketIP(socket);
socket._ip = ip;
if(this.db.checkGlobalBan(ip)) {
Logger.syslog.log("Disconnecting " + ip + " - gbanned");
socket.emit("kick", {
reason: "You're globally banned."
});
socket.disconnect(true);
return;
}
this.db.isGlobalIPBanned(ip, function (err, bant) {
if(bant) {
Logger.syslog.log("Disconnecting " + ip + " - gbanned");
socket.emit("kick", {
reason: "You're globally banned."
});
socket.disconnect(true);
}
});
socket.on("disconnect", function () {
this.ips[ip]--;
@ -197,10 +207,6 @@ var Server = {
new User(socket, this);
}.bind(this));
// init database
this.db = require("./database");
this.db.setup(Server.cfg);
this.db.init();
// init ACP
this.acp = require("./acp")(this);

View file

@ -15,6 +15,7 @@ const STAT_INTERVAL = 60 * 60 * 1000;
const STAT_EXPIRE = 24 * STAT_INTERVAL;
module.exports = function (Server) {
var db = Server.db;
setInterval(function () {
var chancount = Server.channels.length;
var usercount = 0;
@ -24,29 +25,8 @@ module.exports = function (Server) {
var mem = process.memoryUsage().rss;
var db = Server.db.getConnection();
if(!db)
return;
var query = Server.db.createQuery(
"INSERT INTO stats VALUES (?, ?, ?, ?)",
[Date.now(), usercount, chancount, mem]
);
if(!db.querySync(query)) {
Logger.errlog.log("! Failed to record stats");
Logger.errlog.log(query);
}
query = Server.db.createQuery(
"DELETE FROM stats WHERE time<?",
[Date.now() - STAT_EXPIRE]
);
if(!db.querySync(query)) {
Logger.errlog.log("! Failed to prune stats");
Logger.errlog.log(query);
}
db.addStatPoint(Date.now(), usercount, chancount, mem, function () {
db.pruneStats(Date.now() - STAT_EXPIRE);
});
}, STAT_INTERVAL);
}

803
user.js
View file

@ -10,11 +10,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
*/
var Rank = require("./rank.js");
var Auth = require("./auth.js");
var Channel = require("./channel.js").Channel;
var formatTime = require("./media.js").formatTime;
var Logger = require("./logger.js");
var ActionLog = require("./actionlog");
var $util = require("./utilities");
// Represents a client connected via socket.io
var User = function(socket, Server) {
@ -118,365 +117,350 @@ User.prototype.autoAFK = function () {
}
User.prototype.initCallbacks = function() {
this.socket.on("disconnect", function() {
this.awaytimer && clearTimeout(this.awaytimer);
if(this.channel != null)
this.channel.userLeave(this);
}.bind(this));
var self = this;
self.socket.on("disconnect", function() {
self.awaytimer && clearTimeout(self.awaytimer);
if(self.channel != null)
self.channel.userLeave(self);
});
this.socket.on("joinChannel", function(data) {
if(this.channel != null)
self.socket.on("joinChannel", function(data) {
if(self.channel != null)
return;
if(typeof data.name != "string")
return;
if(!data.name.match(/^[\w-_]{1,30}$/)) {
this.socket.emit("errorMsg", {
self.socket.emit("errorMsg", {
msg: "Invalid channel name. Channel names may consist of"+
" 1-30 characters in the set a-z, A-Z, 0-9, -, and _"
});
this.socket.emit("kick", {
self.socket.emit("kick", {
reason: "Bad channel name"
});
return;
}
data.name = data.name.toLowerCase();
this.channel = this.server.getChannel(data.name);
if(this.loggedIn) {
var chanrank = this.channel.getRank(this.name);
if(chanrank > this.rank) {
this.rank = chanrank;
}
self.channel = self.server.getChannel(data.name);
if(self.loggedIn) {
self.channel.getRank(self.name, function (err, rank) {
if(!err && rank > self.rank)
self.rank = rank;
});
}
this.channel.userJoin(this);
}.bind(this));
self.channel.userJoin(self);
});
this.socket.on("login", function(data) {
self.socket.on("login", function(data) {
var name = data.name || "";
var pw = data.pw || "";
var session = data.session || "";
if(pw.length > 100)
pw = pw.substring(0, 100);
if(this.name == "")
this.login(name, pw, session);
}.bind(this));
if(self.name == "")
self.login(name, pw, session);
});
this.socket.on("assignLeader", function(data) {
if(this.channel != null) {
this.channel.tryChangeLeader(this, data);
self.socket.on("assignLeader", function(data) {
if(self.channel != null) {
self.channel.tryChangeLeader(self, data);
}
}.bind(this));
});
this.socket.on("promote", function(data) {
if(this.channel != null) {
this.channel.tryPromoteUser(this, data);
self.socket.on("promote", function(data) {
if(self.channel != null) {
self.channel.tryPromoteUser(self, data);
}
}.bind(this));
});
this.socket.on("demote", function(data) {
if(this.channel != null) {
this.channel.tryDemoteUser(this, data);
self.socket.on("demote", function(data) {
if(self.channel != null) {
self.channel.tryDemoteUser(self, data);
}
}.bind(this));
});
this.socket.on("setChannelRank", function(data) {
if(this.channel != null) {
this.channel.trySetRank(this, data);
self.socket.on("setChannelRank", function(data) {
if(self.channel != null) {
self.channel.trySetRank(self, data);
}
}.bind(this));
});
this.socket.on("banName", function(data) {
if(this.channel != null) {
this.channel.banName(this, data.name || "");
self.socket.on("banName", function(data) {
if(self.channel != null) {
self.channel.banName(self, data.name || "");
}
}.bind(this));
});
this.socket.on("banIP", function(data) {
if(this.channel != null) {
this.channel.tryIPBan(this, data);
self.socket.on("banIP", function(data) {
if(self.channel != null) {
self.channel.tryIPBan(self, data);
}
}.bind(this));
});
this.socket.on("unban", function(data) {
if(this.channel != null) {
this.channel.tryUnban(this, data);
self.socket.on("unban", function(data) {
if(self.channel != null) {
self.channel.tryUnban(self, data);
}
}.bind(this));
});
this.socket.on("chatMsg", function(data) {
if(this.channel != null) {
self.socket.on("chatMsg", function(data) {
if(self.channel != null) {
if(data.msg.indexOf("/afk") != 0) {
this.setAFK(false);
this.autoAFK();
self.setAFK(false);
self.autoAFK();
}
this.channel.tryChat(this, data);
self.channel.tryChat(self, data);
}
}.bind(this));
});
this.socket.on("newPoll", function(data) {
if(this.channel != null) {
this.channel.tryOpenPoll(this, data);
self.socket.on("newPoll", function(data) {
if(self.channel != null) {
self.channel.tryOpenPoll(self, data);
}
}.bind(this));
});
this.socket.on("playerReady", function() {
if(this.channel != null) {
this.channel.sendMediaUpdate(this);
self.socket.on("playerReady", function() {
if(self.channel != null) {
self.channel.sendMediaUpdate(self);
}
}.bind(this));
});
this.socket.on("requestPlaylist", function() {
if(this.channel != null) {
this.channel.sendPlaylist(this);
self.socket.on("requestPlaylist", function() {
if(self.channel != null) {
self.channel.sendPlaylist(self);
}
}.bind(this));
});
this.socket.on("queue", function(data) {
if(this.channel != null) {
this.channel.tryQueue(this, data);
self.socket.on("queue", function(data) {
if(self.channel != null) {
self.channel.tryQueue(self, data);
}
}.bind(this));
});
this.socket.on("setTemp", function(data) {
if(this.channel != null) {
this.channel.trySetTemp(this, data);
self.socket.on("setTemp", function(data) {
if(self.channel != null) {
self.channel.trySetTemp(self, data);
}
}.bind(this));
});
this.socket.on("delete", function(data) {
if(this.channel != null) {
this.channel.tryDequeue(this, data);
self.socket.on("delete", function(data) {
if(self.channel != null) {
self.channel.tryDequeue(self, data);
}
}.bind(this));
});
this.socket.on("uncache", function(data) {
if(this.channel != null) {
this.channel.tryUncache(this, data);
self.socket.on("uncache", function(data) {
if(self.channel != null) {
self.channel.tryUncache(self, data);
}
}.bind(this));
});
this.socket.on("moveMedia", function(data) {
if(this.channel != null) {
this.channel.tryMove(this, data);
self.socket.on("moveMedia", function(data) {
if(self.channel != null) {
self.channel.tryMove(self, data);
}
}.bind(this));
});
this.socket.on("jumpTo", function(data) {
if(this.channel != null) {
this.channel.tryJumpTo(this, data);
self.socket.on("jumpTo", function(data) {
if(self.channel != null) {
self.channel.tryJumpTo(self, data);
}
}.bind(this));
});
this.socket.on("playNext", function() {
if(this.channel != null) {
this.channel.tryPlayNext(this);
self.socket.on("playNext", function() {
if(self.channel != null) {
self.channel.tryPlayNext(self);
}
}.bind(this));
});
this.socket.on("clearPlaylist", function() {
if(this.channel != null) {
this.channel.tryClearqueue(this);
self.socket.on("clearPlaylist", function() {
if(self.channel != null) {
self.channel.tryClearqueue(self);
}
}.bind(this));
});
this.socket.on("shufflePlaylist", function() {
if(this.channel != null) {
this.channel.tryShufflequeue(this);
self.socket.on("shufflePlaylist", function() {
if(self.channel != null) {
self.channel.tryShufflequeue(self);
}
}.bind(this));
});
this.socket.on("togglePlaylistLock", function() {
if(this.channel != null) {
this.channel.tryToggleLock(this);
self.socket.on("togglePlaylistLock", function() {
if(self.channel != null) {
self.channel.tryToggleLock(self);
}
}.bind(this));
});
this.socket.on("mediaUpdate", function(data) {
if(this.channel != null) {
this.channel.tryUpdate(this, data);
self.socket.on("mediaUpdate", function(data) {
if(self.channel != null) {
self.channel.tryUpdate(self, data);
}
}.bind(this));
});
this.socket.on("searchMedia", function(data) {
if(this.channel != null) {
self.socket.on("searchMedia", function(data) {
if(self.channel != null) {
if(data.source == "yt") {
var callback = function(vids) {
this.socket.emit("searchResults", {
var searchfn = self.server.infogetter.Getters["ytSearch"];
searchfn(data.query.split(" "), function (e, vids) {
if(!e) {
self.socket.emit("searchResults", {
results: vids
});
}
});
} else {
self.channel.search(data.query, function (vids) {
self.socket.emit("searchResults", {
results: vids
});
}.bind(this);
this.channel.search(data.query, callback);
}
else {
this.socket.emit("searchResults", {
results: this.channel.search(data.query)
});
}
}
}.bind(this));
});
this.socket.on("closePoll", function() {
if(this.channel != null) {
this.channel.tryClosePoll(this);
self.socket.on("closePoll", function() {
if(self.channel != null) {
self.channel.tryClosePoll(self);
}
}.bind(this));
});
this.socket.on("vote", function(data) {
if(this.channel != null) {
this.channel.tryVote(this, data);
self.socket.on("vote", function(data) {
if(self.channel != null) {
self.channel.tryVote(self, data);
}
}.bind(this));
});
this.socket.on("registerChannel", function(data) {
if(this.channel == null) {
this.socket.emit("channelRegistration", {
self.socket.on("registerChannel", function(data) {
if(self.channel == null) {
self.socket.emit("channelRegistration", {
success: false,
error: "You're not in any channel!"
});
}
else {
this.channel.tryRegister(this);
self.channel.tryRegister(self);
}
}.bind(this));
});
this.socket.on("unregisterChannel", function() {
if(this.channel == null) {
self.socket.on("unregisterChannel", function() {
if(self.channel == null) {
return;
}
if(!this.channel.registered) {
this.socket.emit("unregisterChannel", {
success: false,
error: "This channel is already unregistered"
});
}
else if(this.rank < 10) {
this.socket.emit("unregisterChannel", {
success: false,
error: "You don't have permission to unregister"
});
}
else if(this.channel.unregister()) {
this.socket.emit("unregisterChannel", {
success: true
});
}
else {
this.socket.emit("unregisterChannel", {
success: false,
error: "Unregistration failed. Please see a site admin if this continues."
});
}
}.bind(this));
self.channel.unregister(self);
});
this.socket.on("setOptions", function(data) {
if(this.channel != null) {
this.channel.tryUpdateOptions(this, data);
self.socket.on("setOptions", function(data) {
if(self.channel != null) {
self.channel.tryUpdateOptions(self, data);
}
}.bind(this));
});
this.socket.on("setPermissions", function(data) {
if(this.channel != null) {
this.channel.tryUpdatePermissions(this, data);
self.socket.on("setPermissions", function(data) {
if(self.channel != null) {
self.channel.tryUpdatePermissions(self, data);
}
}.bind(this));
});
this.socket.on("setChannelCSS", function(data) {
if(this.channel != null) {
this.channel.trySetCSS(this, data);
self.socket.on("setChannelCSS", function(data) {
if(self.channel != null) {
self.channel.trySetCSS(self, data);
}
}.bind(this));
});
this.socket.on("setChannelJS", function(data) {
if(this.channel != null) {
this.channel.trySetJS(this, data);
self.socket.on("setChannelJS", function(data) {
if(self.channel != null) {
self.channel.trySetJS(self, data);
}
}.bind(this));
});
this.socket.on("updateFilter", function(data) {
if(this.channel != null) {
this.channel.tryUpdateFilter(this, data);
self.socket.on("updateFilter", function(data) {
if(self.channel != null) {
self.channel.tryUpdateFilter(self, data);
}
}.bind(this));
});
this.socket.on("removeFilter", function(data) {
if(this.channel != null) {
this.channel.tryRemoveFilter(this, data);
self.socket.on("removeFilter", function(data) {
if(self.channel != null) {
self.channel.tryRemoveFilter(self, data);
}
}.bind(this));
});
this.socket.on("moveFilter", function(data) {
if(this.channel != null) {
this.channel.tryMoveFilter(this, data);
self.socket.on("moveFilter", function(data) {
if(self.channel != null) {
self.channel.tryMoveFilter(self, data);
}
}.bind(this));
});
this.socket.on("setMotd", function(data) {
if(this.channel != null) {
this.channel.tryUpdateMotd(this, data);
self.socket.on("setMotd", function(data) {
if(self.channel != null) {
self.channel.tryUpdateMotd(self, data);
}
}.bind(this));
});
this.socket.on("requestLoginHistory", function() {
if(this.channel != null) {
this.channel.sendLoginHistory(this);
self.socket.on("requestLoginHistory", function() {
if(self.channel != null) {
self.channel.sendLoginHistory(self);
}
}.bind(this));
});
this.socket.on("requestBanlist", function() {
if(this.channel != null) {
this.channel.sendBanlist(this);
self.socket.on("requestBanlist", function() {
if(self.channel != null) {
self.channel.sendBanlist(self);
}
}.bind(this));
});
this.socket.on("requestChatFilters", function() {
if(this.channel != null) {
this.channel.sendChatFilters(this);
self.socket.on("requestChatFilters", function() {
if(self.channel != null) {
self.channel.sendChatFilters(self);
}
}.bind(this));
});
this.socket.on("requestChannelRanks", function() {
if(this.channel != null) {
if(this.noflood("requestChannelRanks", 0.25))
self.socket.on("requestChannelRanks", function() {
if(self.channel != null) {
if(self.noflood("requestChannelRanks", 0.25))
return;
this.channel.sendChannelRanks(this);
self.channel.sendChannelRanks(self);
}
}.bind(this));
});
this.socket.on("voteskip", function(data) {
if(this.channel != null) {
this.channel.tryVoteskip(this);
self.socket.on("voteskip", function(data) {
if(self.channel != null) {
self.channel.tryVoteskip(self);
}
}.bind(this));
});
this.socket.on("listPlaylists", function(data) {
if(this.name == "" || this.rank < 1) {
this.socket.emit("listPlaylists", {
self.socket.on("listPlaylists", function(data) {
if(self.name == "" || self.rank < 1) {
self.socket.emit("listPlaylists", {
pllist: [],
error: "You must be logged in to manage playlists"
});
return;
}
var list = this.server.db.getUserPlaylists(this.name);
for(var i = 0; i < list.length; i++) {
list[i].time = formatTime(list[i].time);
}
this.socket.emit("listPlaylists", {
pllist: list,
self.server.db.listUserPlaylists(self.name, function (err, list) {
if(err)
list = [];
for(var i = 0; i < list.length; i++) {
list[i].time = formatTime(list[i].time);
}
self.socket.emit("listPlaylists", {
pllist: list,
});
});
}.bind(this));
});
this.socket.on("savePlaylist", function(data) {
if(this.rank < 1) {
this.socket.emit("savePlaylist", {
self.socket.on("savePlaylist", function(data) {
if(self.rank < 1) {
self.socket.emit("savePlaylist", {
success: false,
error: "You must be logged in to manage playlists"
});
return;
}
if(this.channel == null) {
this.socket.emit("savePlaylist", {
if(self.channel == null) {
self.socket.emit("savePlaylist", {
success: false,
error: "Not in a channel"
});
@ -487,79 +471,100 @@ User.prototype.initCallbacks = function() {
return;
}
var pl = this.channel.playlist.items.toArray();
var result = this.server.db.saveUserPlaylist(pl, this.name, data.name);
this.socket.emit("savePlaylist", {
success: result,
error: result ? false : "Unknown"
});
var list = this.server.db.getUserPlaylists(this.name);
for(var i = 0; i < list.length; i++) {
list[i].time = formatTime(list[i].time);
}
this.socket.emit("listPlaylists", {
pllist: list,
});
}.bind(this));
var pl = self.channel.playlist.items.toArray();
self.server.db.saveUserPlaylist(pl, self.name, data.name,
function (err, res) {
if(err) {
self.socket.emit("savePlaylist", {
success: false,
error: err
});
return;
}
self.socket.emit("savePlaylist", {
success: true
});
this.socket.on("queuePlaylist", function(data) {
if(this.channel != null) {
this.channel.tryQueuePlaylist(this, data);
}
}.bind(this));
self.server.db.listUserPlaylists(self.name,
function (err, list) {
if(err)
list = [];
for(var i = 0; i < list.length; i++) {
list[i].time = formatTime(list[i].time);
}
self.socket.emit("listPlaylists", {
pllist: list,
});
});
});
});
this.socket.on("deletePlaylist", function(data) {
self.socket.on("queuePlaylist", function(data) {
if(self.channel != null) {
self.channel.tryQueuePlaylist(self, data);
}
});
self.socket.on("deletePlaylist", function(data) {
if(typeof data.name != "string") {
return;
}
this.server.db.deleteUserPlaylist(this.name, data.name);
var list = this.server.db.getUserPlaylists(this.name);
for(var i = 0; i < list.length; i++) {
list[i].time = formatTime(list[i].time);
}
this.socket.emit("listPlaylists", {
pllist: list,
self.server.db.deleteUserPlaylist(self.name, data.name,
function () {
self.server.db.listUserPlaylists(self.name,
function (err, list) {
if(err)
list = [];
for(var i = 0; i < list.length; i++) {
list[i].time = formatTime(list[i].time);
}
self.socket.emit("listPlaylists", {
pllist: list,
});
});
});
}.bind(this));
});
this.socket.on("readChanLog", function () {
if(this.channel !== null) {
this.channel.tryReadLog(this);
self.socket.on("readChanLog", function () {
if(self.channel !== null) {
self.channel.tryReadLog(self);
}
}.bind(this));
});
this.socket.on("acp-init", function() {
if(this.global_rank >= Rank.Siteadmin)
this.server.acp.init(this);
}.bind(this));
self.socket.on("acp-init", function() {
if(self.global_rank >= Rank.Siteadmin)
self.server.acp.init(self);
});
this.socket.on("borrow-rank", function(rank) {
if(this.global_rank < 255)
self.socket.on("borrow-rank", function(rank) {
if(self.global_rank < 255)
return;
if(rank > this.global_rank)
if(rank > self.global_rank)
return;
this.rank = rank;
this.socket.emit("rank", rank);
if(this.channel != null)
this.channel.broadcastUserUpdate(this);
self.rank = rank;
self.socket.emit("rank", rank);
if(self.channel != null)
self.channel.broadcastUserUpdate(self);
}.bind(this));
});
}
var lastguestlogin = {};
// Attempt to login
User.prototype.login = function(name, pw, session) {
var self = this;
// No password => try guest login
if(pw == "" && session == "") {
if(this.ip in lastguestlogin) {
var diff = (Date.now() - lastguestlogin[this.ip])/1000;
if(diff < this.server.cfg["guest-login-delay"]) {
this.socket.emit("login", {
if(self.ip in lastguestlogin) {
var diff = (Date.now() - lastguestlogin[self.ip])/1000;
if(diff < self.server.cfg["guest-login-delay"]) {
self.socket.emit("login", {
success: false,
error: ["Guest logins are restricted to one per ",
this.server.cfg["guest-login-delay"]
self.server.cfg["guest-login-delay"]
+ " seconds per IP. ",
"This restriction does not apply to registered users."
].join("")
@ -567,149 +572,111 @@ User.prototype.login = function(name, pw, session) {
return false;
}
}
try {
// Sorry bud, can't take that name
if(Auth.isRegistered(name)) {
this.socket.emit("login", {
success: false,
error: "That username is already taken"
});
return false;
}
// YOUR ARGUMENT IS INVALID
else if(!Auth.validateName(name)) {
this.socket.emit("login", {
success: false,
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
});
}
else {
if(this.channel != null) {
for(var i = 0; i < this.channel.users.length; i++) {
if(this.channel.users[i].name == name) {
this.socket.emit("login", {
success: false,
error: "That name is already taken on this channel"
});
return;
}
}
}
lastguestlogin[this.ip] = Date.now();
this.rank = Rank.Guest;
Logger.syslog.log(this.ip + " signed in as " + name);
this.server.db.recordVisit(this.ip, name);
this.name = name;
this.loggedIn = false;
this.socket.emit("login", {
success: true,
name: name
});
this.socket.emit("rank", this.rank);
if(this.channel != null) {
this.channel.logger.log(this.ip + " signed in as " + name);
this.channel.broadcastNewUser(this);
}
}
}
catch(e) {
this.socket.emit("login", {
if(!$util.isValidUserName(name)) {
self.socket.emit("login", {
success: false,
error: e
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
});
return;
}
}
else {
try {
var row;
if((row = Auth.login(name, pw, session))) {
if(this.channel != null) {
for(var i = 0; i < this.channel.users.length; i++) {
if(this.channel.users[i].name == name) {
this.channel.kick(this.channel.users[i], "Duplicate login");
}
}
}
if(this.global_rank >= 255)
ActionLog.record(this.ip, name, "login-success");
this.loggedIn = true;
this.socket.emit("login", {
success: true,
session: row.session_hash,
name: name
});
Logger.syslog.log(this.ip + " logged in as " + name);
this.server.db.recordVisit(this.ip, name);
this.profile = {
image: row.profile_image,
text: row.profile_text
};
var chanrank = (this.channel != null) ? this.channel.getRank(name)
: Rank.Guest;
var rank = (chanrank > row.global_rank) ? chanrank
: row.global_rank;
this.rank = (this.rank > rank) ? this.rank : rank;
this.global_rank = row.global_rank;
this.socket.emit("rank", this.rank);
this.name = name;
if(this.channel != null) {
this.channel.logger.log(this.ip + " logged in as " + name);
this.channel.broadcastNewUser(this);
}
}
// Wrong password
else {
ActionLog.record(this.ip, this.name, "login-failure");
this.socket.emit("login", {
success: false,
error: "Invalid session"
});
return false;
}
}
catch(e) {
this.socket.emit("login", {
success: false,
error: e
});
}
}
}
// Attempt to register a user account
User.prototype.register = function(name, pw) {
if(pw == "") {
// Sorry bud, password required
this.socket.emit("register", {
success: false,
error: "You must provide a password"
self.server.db.isUsernameTaken(name, function (err, taken) {
if(err) {
self.socket.emit("login", {
success: false,
error: "Internal error: " + err
});
return;
}
if(taken) {
self.socket.emit("login", {
success: false,
error: "That username is taken"
});
return;
}
if(self.channel != null) {
for(var i = 0; i < self.channel.users.length; i++) {
if(self.channel.users[i].name == name) {
self.socket.emit("login", {
success: false,
error: "That name is already taken on self channel"
});
return;
}
}
}
lastguestlogin[self.ip] = Date.now();
self.rank = Rank.Guest;
Logger.syslog.log(self.ip + " signed in as " + name);
self.server.db.recordVisit(self.ip, name);
self.name = name;
self.loggedIn = false;
self.socket.emit("login", {
success: true,
name: name
});
self.socket.emit("rank", self.rank);
if(self.channel != null) {
self.channel.logger.log(self.ip + " signed in as " + name);
self.channel.broadcastNewUser(self);
}
});
return false;
}
else if(Auth.isRegistered(name)) {
this.socket.emit("register", {
success: false,
error: "That username is already taken"
});
return false;
}
else if(!Auth.validateName(name)) {
this.socket.emit("register", {
success: false,
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
});
}
else if(Auth.register(name, pw)) {
console.log(this.ip + " registered " + name);
this.socket.emit("register", {
success: true
});
this.login(name, pw);
}
else {
this.socket.emit("register", {
success: false,
error: "[](/ppshrug) Registration Failed."
} else {
self.server.db.userLogin(name, pw, session, function (err, row) {
if(err) {
self.server.actionlog.record(self.ip, name, "login-failure");
self.socket.emit("login", {
success: false,
error: err
});
return;
}
if(self.channel != null) {
for(var i = 0; i < self.channel.users.length; i++) {
if(self.channel.users[i].name.toLowerCase() == name.toLowerCase()) {
self.channel.kick(self.channel.users[i], "Duplicate login");
}
}
}
if(self.global_rank >= 255)
self.server.actionlog.record(self.ip, name, "login-success");
self.loggedIn = true;
self.socket.emit("login", {
success: true,
session: row.session_hash,
name: name
});
Logger.syslog.log(self.ip + " logged in as " + name);
self.server.db.recordVisit(self.ip, name);
self.profile = {
image: row.profile_image,
text: row.profile_text
};
self.global_rank = row.global_rank;
var afterRankLookup = function () {
self.socket.emit("rank", self.rank);
self.name = name;
if(self.channel != null) {
self.channel.logger.log(self.ip + " logged in as " +
name);
self.channel.broadcastNewUser(self);
}
};
if(self.channel !== null) {
self.channel.getRank(name, function (err, rank) {
if(!err)
self.rank = rank;
else
self.rank = self.global_rank;
afterRankLookup();
});
} else {
self.rank = self.global_rank;
afterRankLookup();
}
});
}
}

29
utilities.js Normal file
View file

@ -0,0 +1,29 @@
module.exports = {
isValidChannelName: function (name) {
return name.match(/^[\w-_]{1,30}$/);
},
isValidUserName: function (name) {
return name.match(/^[\w-_]{1,20}$/);
},
randomSalt: function (length) {
var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789!@#$%^&*_+=~";
var salt = [];
for(var i = 0; i < length; i++) {
salt.push(chars[parseInt(Math.random()*chars.length)]);
}
return salt.join('');
},
maskIP: function (ip) {
if(ip.match(/^\d+\.\d+\.\d+\.\d+$/)) {
// standard 32 bit IP
return ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "x.x.$1");
} else if(ip.match(/^\d+\.\d+\.\d+/)) {
// /24 range
return ip.replace(/\d+\.\d+\.(\d+)/, "x.x.$1.*");
}
}
};

View file

@ -9,19 +9,44 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
So, it turns out that $.post causes Firefox to use a GET request
on cross-site requests. What the hell? I'd understand if they just
made it error instead, but why give me chicken tenders if I ordered a
cheeseburger and act like everything's peachy?
*/
function postJSON(url, data, callback) {
$.ajax(url, {
method: "POST",
crossDomain: true,
data: data,
success: function (data) {
try {
data = data.substring(data.indexOf("{"));
data = data.substring(0, data.lastIndexOf("}") + 1);
data = JSON.parse(data);
callback(data);
} catch(e) {
return;
}
},
dataType: "text"
});
}
var uname = readCookie("cytube_uname") || "";
var session = readCookie("cytube_session") || "";
var api = WEB_URL + "/api/json/";
var loggedin = false;
if(uname && session) {
var loginstr = "name=" + encodeURIComponent(uname)
+ "&session=" + session;
var url = api + "login?" + loginstr + "&callback=?";
$.getJSON(url, function(data) {
if(data.success) {
var data = {
name: uname,
session: session
};
postJSON(WEB_URL + "/api/login?callback=?", data, function (data) {
console.log(data);
if(data.success)
onLogin();
}
});
}
@ -57,7 +82,8 @@ $("#email").click(makeTabCallback("#email", "#changeemailpane"));
$("#profile").click(makeTabCallback("#profile", "#profilepane"));
$("#profile").click(function() {
if(uname != "") {
$.getJSON(api + "getprofile?name=" + encodeURIComponent(uname) + "&callback=?", function(data) {
$.getJSON(WEB_URL+"/api/users/"+uname+"/profile?callback=?",
function (data) {
if(data.success) {
$("#profiletext").val(data.profile_text);
$("#profileimg").val(data.profile_image);
@ -82,8 +108,10 @@ $("#channels").click(function () {
return;
}
$.getJSON(api + "listuserchannels?name=" + encodeURIComponent(uname) +
"&session=" + session + "&callback=?", function(data) {
var auth = "name=" + encodeURIComponent(uname) + "&session=" +
encodeURIComponent(session);
$.getJSON(WEB_URL+"/api/account/mychannels?"+auth+"&callback=?",
function (data) {
$("#channellist tbody").remove();
data.channels.forEach(function (chan) {
var tr = $("<tr/>").appendTo($("#channellist"));
@ -134,12 +162,12 @@ $("#registerbtn").click(function() {
}
// Input valid, try registering
var url = api + "register?" + [
"name=" + encodeURIComponent(name),
"pw=" + encodeURIComponent(pw)
].join("&") + "&callback=?";
$.getJSON(url, function(data) {
var data = {
name: name,
pw: pw
};
postJSON(WEB_URL + "/api/register?callback=?", data, function (data) {
if(data.success) {
uname = name;
session = data.session;
@ -170,10 +198,12 @@ $("#loginbtn").click(function() {
return;
}
uname = $("#loginusername").val();
var loginstr = "name=" + encodeURIComponent(uname)
+ "&pw=" + encodeURIComponent($("#loginpw").val());
var url = api + "login?" + loginstr + "&callback=?";
$.getJSON(url, function(data) {
var data = {
name: uname,
pw: $("#loginpw").val()
};
postJSON(WEB_URL+"/api/login?callback=?", data, function(data) {
if(data.success) {
session = data.session;
onLogin();
@ -230,12 +260,13 @@ $("#cpwbtn").click(function() {
}
// Input valid, try changing password
var url = api + "changepass?" + [
"name=" + encodeURIComponent(name),
"oldpw=" + encodeURIComponent(oldpw),
"newpw=" + encodeURIComponent(newpw)
].join("&") + "&callback=?";
$.getJSON(url, function(data) {
var data = {
name: name,
oldpw: oldpw,
newpw: newpw
};
postJSON(WEB_URL + "/api/account/passwordchange?callback=?", data,
function (data) {
if(data.success) {
$("<div/>").addClass("alert alert-success")
.text("Password changed.")
@ -266,7 +297,7 @@ $("#cebtn").click(function() {
return;
}
if(!email.match(/^[a-z0-9_\.]+@[a-z0-9_\.]+[a-z]+$/)) {
if(!email.match(/^[\w_\.]+@[\w_\.]+[a-zA-Z]+$/)) {
$("<div/>").addClass("alert alert-error")
.text("Invalid email")
.insertAfter($("#ceemail").parent().parent());
@ -282,12 +313,13 @@ $("#cebtn").click(function() {
return;
}
var url = api + "setemail?" + [
"name=" + encodeURIComponent(name),
"pw=" + encodeURIComponent(pw),
"email=" + encodeURIComponent(email)
].join("&") + "&callback=?";
$.getJSON(url, function(data) {
var data = {
name: name,
pw: pw,
email: email
};
postJSON(WEB_URL + "/api/account/email?callback=?", data,
function (data) {
if(data.success) {
$("<div/>").addClass("alert alert-success")
.text("Email updated")
@ -312,11 +344,12 @@ $("#rpbtn").click(function() {
var name = $("#rpusername").val();
var email = $("#rpemail").val();
var url = api + "resetpass?" + [
"name=" + encodeURIComponent(name),
"email=" + encodeURIComponent(email)
].join("&") + "&callback=?";
$.getJSON(url, function(data) {
var data = {
name: name,
email: email
};
postJSON(WEB_URL + "/api/account/passwordreset?callback=?", data,
function (data) {
$("#rpbtn").text("Send Reset");
if(data.success) {
$("<div/>").addClass("alert alert-success")
@ -336,20 +369,16 @@ $("#profilesave").click(function() {
$("#profilepane").find(".alert-error").remove();
$("#profilepane").find(".alert-success").remove();
var img = $("#profileimg").val();
/*
img = escape(img).replace(/\//g, "%2F")
.replace(/&/g, "%26")
.replace(/=/g, "%3D")
.replace(/\?/g, "%3F");
*/
var url = api + "setprofile?" + [
"name=" + encodeURIComponent(uname),
"session=" + session,
"profile_image=" + encodeURIComponent(img),
"profile_text=" + encodeURIComponent($("#profiletext").val())
].join("&") + "&callback=?";
var text = $("#profiletext").val();
var data = {
name: uname,
session: session,
profile_image: img,
profile_text: text
};
$.getJSON(url, function(data) {
postJSON(WEB_URL+"/api/account/profile?callback=?", data,
function (data) {
if(data.success) {
$("<div/>").addClass("alert alert-success")
.text("Profile updated.")

View file

@ -1,4 +1,3 @@
var BASE = WEB_URL + "/api/json/";
var AUTH = "";
var NO_WEBSOCKETS = false;
@ -156,20 +155,21 @@ function reverseLog() {
$("#log_reverse").click(reverseLog);
function getSyslog() {
$.ajax(WEB_URL+"/api/plain/readlog?type=sys&"+AUTH).done(function(data) {
$.ajax(WEB_URL+"/api/logging/syslog?"+AUTH).done(function(data) {
$("#log").text(data);
});
}
$("#syslog").click(getSyslog);
function getErrlog() {
$.ajax(WEB_URL+"/api/plain/readlog?type=err&"+AUTH).done(function(data) {
$.ajax(WEB_URL+"/api/logging/errorlog?"+AUTH).done(function(data) {
$("#log").text(data);
});
}
$("#errlog").click(getErrlog);
function getActionLog() {
var types = "&actions=" + $("#actionlog_filter").val().join(",");
$.getJSON(WEB_URL+"/api/json/readactionlog?"+AUTH+types+"&callback=?").done(function(entries) {
$.getJSON(WEB_URL+"/api/logging/actionlog?"+AUTH+types+"&callback=?")
.done(function(entries) {
var tbl = $("#actionlog table");
entries.forEach(function (e) {
e.time = parseInt(e.time);
@ -217,7 +217,8 @@ function getActionLog() {
}
function getChanlog() {
var chan = $("#channame").val();
$.ajax(WEB_URL+"/api/plain/readlog?type=channel&channel="+chan+"&"+AUTH).done(function(data) {
$.ajax(WEB_URL+"/api/logging/channels/"+chan+"?"+AUTH)
.done(function(data) {
$("#log").text(data);
});
}

View file

@ -13,6 +13,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
Callbacks = {
error: function (reason) {
// Don't show the error for when the server goes down
if(reason && reason.returnValue === true)
return;
var d = $("<div/>").addClass("alert alert-error span12")
.appendTo($("#announcements"));
$("<h3/>").text("Uh-oh!").appendTo(d);
@ -74,6 +78,33 @@ Callbacks = {
alert(data.msg);
},
costanza: function (data) {
hidePlayer();
$("#costanza-modal").modal("hide");
var modal = $("<div/>").addClass("modal hide fade")
.attr("id", "costanza-modal")
.appendTo($("body"));
var body = $("<div/>").addClass("modal-body").appendTo(modal);
$("<button/>").addClass("close")
.attr("data-dismiss", "modal")
.attr("data-hidden", "true")
.html("&times;")
.appendTo(body);
$("<img/>").attr("src", "http://i0.kym-cdn.com/entries/icons/original/000/005/498/1300044776986.jpg")
.appendTo(body);
$("<strong/>").text(data.msg).appendTo(body);
modal.on("hidden", function () {
modal.remove();
unhidePlayer();
});
modal.modal();
},
announcement: function(data) {
$("#announcements").html("");
makeAlert(data.title, data.text)
@ -572,7 +603,7 @@ Callbacks = {
login: function(data) {
if(!data.success) {
if(data.error != "Invalid session") {
if(data.error != "Session expired") {
alert(data.error);
}
}
@ -696,6 +727,7 @@ Callbacks = {
/* REGION Playlist Stuff */
playlist: function(data) {
PL_QUEUED_ACTIONS = [];
// Clear the playlist first
var q = $("#queue");
q.html("");

View file

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CyTube</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A free, open source synchtube replacement">
<meta name="author" content="Calvin 'calzoneman' Montgomery">
<link href="./assets/css/bootstrap.css" rel="stylesheet">
<link href="./assets/css/bootstrap-responsive.css" rel="stylesheet">
<link href="./assets/css/ytsync.css" rel="stylesheet" id="defaultcss">
<link href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" rel="stylesheet">
<style>
body {
padding-top: 60px;
}
</style>
</head>
<body>
<div class="wrapper">
<!-- begin navbar -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="index.html">CyTube</a>
<ul class="nav">
<li class="active"><a href="index.html">Home</a></li>
<li><a href="help.html">Help</a></li>
<li><a href="account.html">Account</a></li>
<li><a href="javascript:void(0)" id="optlink">Options</a></li>
</ul>
<div class="navbar-form pull-right" id="loginform">
<input type="text" id="guestname" placeholder="Name">
<button class="btn" id="guestlogin">Guest Login</button>
<button class="btn" id="login">Login/Register</button>
</div>
<div class="navbar-form pull-right" id="logoutform" style="display: none;">
<span id="welcome"></span>
<button class="btn" id="logout">Logout</button>
</div>
</div>
</div>
</div>
<!-- end navbar -->
<!-- begin main page -->
<div class="container" id="mainpage">
<!-- top row (MOTD, drink count) -->
<div class="row-fluid" id="toprow">
<div class="well span12" id="motd">
</div>
<div class="span12" id="drinkbar">
<h1 id="drinkcount"></h1>
</div>
</div>
<!-- announcement area -->
<div class="row-fluid" id="announcements">
</div>
<!-- main row -->
<div class="row" id="main">
<!-- chat container -->
<div class="span5" id="chatwrap">
<!-- user count -->
<div id="usercountwrap" class="pointer">
<i class="icon-chevron-up pull-left" id="userlisttoggle" title="Show/Hide Userlist"></i>
<p id="usercount">Not connected</p>
</div>
<!-- userlist -->
<div id="userlist">
</div>
<!-- message buffer -->
<div id="messagebuffer">
</div>
<!-- chat input -->
<input type="text" id="chatline" maxlength="240" class="span5">
</div>
<!-- video container -->
<div class="span7" id="videowrap">
<!-- current video display -->
<p id="currenttitle">Nothing playing</p>
<!-- video frame -->
<div id="ytapiplayer">
</div>
</div>
</div>
<!-- playlist row -->
<div class="row" id="playlistrow">
<!-- left pane - Library + user playlists -->
<div class="span5" id="leftpane-outer">
<div class="row-fluid" id="leftpane-inner">
<!-- poll container -->
<div class="span12" id="pollwrap">
<!-- new poll controls -->
<button class="btn btn-primary btn-small" id="newpollbtn">New Poll</button>
</div>
<!-- library search -->
<div class="span12 pointer" id="librarytoggle">
<i class="icon-chevron-down pull-left"></i>
<p>Show Library</p>
</div>
<div id="librarywrap">
<div class="span7" id="querywrap">
<input type="text" id="library_query" class="input-block-level" placeholder="Search Query">
</div>
<div class="span5 btn-group" id="searchbtns">
<button class="btn" id="library_search">Library</button>
<button class="btn" id="youtube_search">YouTube</button>
</div>
</div>
<!-- user playlists -->
<div class="span12 pointer" id="userpltoggle">
<i class="icon-chevron-down pull-left"></i>
<p>Show Playlist Manager</p>
</div>
<div id="userplaylistwrap">
<div class="span7">
<input type="text" id="userpl_name" class="input-block-level" placeholder="Playlist Name">
</div>
<div class="span5">
<button class="btn btn-block" id="userpl_save">Save Playlist</button>
</div>
<ul class="span12" id="userpl_list">
</ul>
</div>
</div>
</div>
<!-- right pane - channel playlist -->
<div class="span7" id="rightpane-outer">
<div class="row-fluid" id="rightpane-inner">
<!-- account for left pane having the poll buffer -->
<div class="span12" id="queue_align"></div>
<!-- playlist controls -->
<div class="span12 pointer" id="playlisttoggle">
<i class="icon-chevron-down pull-left"></i>
<p>Show Playlist Controls</p>
</div>
<div id="playlist_controls">
<div class="span8">
<input type="text" id="mediaurl" class="input-block-level" placeholder="Media URL">
</div>
<div class="span4 btn-group">
<button class="btn" id="queue_next">Next</button>
<button class="btn" id="queue_end">End</button>
</div>
<div id="extended_controls" class="span12">
<button class="btn btn-danger btn-block" id="qlockbtn">Unlock Queue</button>
<button class="btn btn-block" id="getplaylist">Get Playlist URLs</button>
<button class="btn btn-block" id="clearplaylist">Clear Playlist</button>
<button class="btn btn-block" id="shuffleplaylist">Shuffle Playlist</button>
</div>
</div>
<!-- video queue -->
<ul class="span12 videolist" id="queue">
</ul>
</div>
</div>
</div>
</div>
<div class="push"></div>
<div id="sitefooter">
</div>
</div>
<div id="footer">
<p class="muted">
CyTube Software Copyright &copy; 2013 Calvin Montgomery&nbsp;&middot;&nbsp;Available for free on <a href="http://github.com/calzoneman/sync">GitHub</a>&nbsp;&middot;
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5Y7PUVVGVSEWG&lc=US&item_name=CyTube&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">Donate</a>
</p>
</div>
<script src="./assets/js/jquery.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<!-- My Javascript -->
<script src="./assets/js/data.js"></script>
<script src="./assets/js/util.js"></script>
<script src="./assets/js/ui.js"></script>
<script src="./assets/js/callbacks.js"></script>
<!--
<script src="./assets/js/iourl.js"></script>
<script src="./assets/js/notwebsocket.js"></script>
<script src="./assets/js/media.js"></script>
<script src="./assets/js/functions.js"></script>
<script src="./assets/js/client.js"></script>
<script src="./assets/js/callbacks.js"></script>
-->
<!-- APIs -->
<!--
<script src="http://api.dmcdn.net/all.js"></script>
<script src="http://jwpsrv.com/library/QouFCLBMEeKC+CIACpYGxA.js"></script>
<script src="./assets/js/sc.js"></script>
<script src="./assets/js/froogaloop.min.js"></script>
<script src="./assets/js/swf.js"></script>
-->
<!-- Third party -->
<script src="./assets/js/bootstrap.js"></script>
<script src="./assets/js/bootstrap-transition.js"></script>
<script src="./assets/js/bootstrap-modal.js"></script>
</body>
</html>

View file

@ -247,12 +247,12 @@
<script src="./assets/js/ui.js"></script>
<script src="./assets/js/callbacks.js"></script>
<!-- APIs -->
<script src="http://www.youtube.com/iframe_api"></script>
<script src="http://api.dmcdn.net/all.js"></script>
<script src="http://jwpsrv.com/library/QouFCLBMEeKC+CIACpYGxA.js"></script>
<script src="./assets/js/sc.js"></script>
<script src="./assets/js/froogaloop.min.js"></script>
<script src="./assets/js/swf.js"></script>
<script defer src="http://www.youtube.com/iframe_api"></script>
<script defer src="http://api.dmcdn.net/all.js"></script>
<script defer src="http://jwpsrv.com/library/QouFCLBMEeKC+CIACpYGxA.js"></script>
<script defer src="./assets/js/sc.js"></script>
<script defer src="./assets/js/froogaloop.min.js"></script>
<script defer src="./assets/js/swf.js"></script>
<!-- Third party -->
<script src="./assets/js/bootstrap.js"></script>
<script src="./assets/js/bootstrap-transition.js"></script>

View file

@ -1,55 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CyTube</title>
<link href="assets/css/bootstrap.css" rel="stylesheet">
<style type="text/css">
#channeldata td, #channeldata th {
text-align: center;
vertical-align: middle;
}
</style>
</head>
<body>
<table id="channeldata" class="table table-striped table-bordered">
<thead>
<tr>
<th>Channel</th>
<th>Connected</th>
<th>Playing</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script src="assets/js/jquery.js" type="text/javascript"></script>
<script src="assets/js/iourl.js" type="text/javascript"></script>
<script type="text/javascript">
var p = document.location + "";
var host = p.replace("http://", "");
if(host.indexOf("/") != -1) {
host = host.substring(0, host.indexOf("/"));
}
if(p.indexOf("?") != -1) {
p = p.substring(p.indexOf("?")+1);
}
$.getJSON(IO_URL+"/api/json/channeldata?"+p+"&callback=?", function(data) {
if(data.error) {
return;
}
$("#channeldata").find("tbody").remove();
for(var i = 0; i < data.length; i++) {
var d = data[i];
var tr = $("<tr/>").appendTo($("#channeldata"));
var name = $("<td/>").appendTo(tr);
$("<a/>").attr("href", host + "/r/" + d.name)
.text(d.name)
.appendTo(name);
$("<td/>").text(d.usercount || 0).appendTo(tr);
$("<td/>").text(d.title || "-").appendTo(tr);
}
});
</script>
</body>
</html>

View file

@ -130,7 +130,8 @@
return entry;
}
function refresh() {
$.getJSON(WEB_URL+"/api/json/listloaded?filter=public&callback=?", function(data) {
$.getJSON(WEB_URL+"/api/allchannels/public?callback=?",
function(data) {
$("#channeldata").find("tbody").remove();
data.sort(function(a, b) {
var x = a.usercount;

View file

@ -37,6 +37,30 @@
<script src="assets/js/jquery.js" type="text/javascript"></script>
<script src="assets/js/iourl.js" type="text/javascript"></script>
<script type="text/javascript">
/*
So, it turns out that $.post causes Firefox to use a GET request
on cross-site requests. What the hell? I'd understand if they just
made it error instead, but why give me chicken tenders if I ordered a
cheeseburger and act like everything's peachy?
*/
function postJSON(url, data, callback) {
$.ajax(url, {
method: "POST",
crossDomain: true,
data: data,
success: function (data) {
try {
data = data.substring(data.indexOf("{"));
data = data.substring(0, data.lastIndexOf("}") + 1);
data = JSON.parse(data);
callback(data);
} catch(e) {
return;
}
},
dataType: "text"
});
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
@ -66,9 +90,11 @@
window.addEventListener("message", respond, false);
$("#login").click(function() {
var u = encodeURIComponent($("#username").val());
var p = encodeURIComponent($("#pw").val());
$.getJSON(WEB_URL+"/api/json/login?name="+u+"&pw="+p+"&callback=?", function(data) {
var data = {
name: $("#username").val(),
pw: $("#pw").val()
};
postJSON(WEB_URL+"/api/login", data, function (data) {
data.uname = $("#username").val();
source.postMessage("cytube-login:"+JSON.stringify(data), document.location);
});

View file

@ -71,7 +71,8 @@
hash = loc.substring(loc.indexOf("?") + 1);
})();
var url = WEB_URL+"/api/json/recoverpw?hash="+hash+"&callback=?";
var url = WEB_URL+"/api/account/passwordrecover?hash="+hash+
"&callback=?";
$.getJSON(url, function(data) {
if(data.success) {
$("<div/>").addClass("alert alert-success")