mirror of https://github.com/calzoneman/sync.git
Work on channel.js
This commit is contained in:
parent
ba0664641e
commit
9611f86d55
|
@ -1,3 +1,6 @@
|
||||||
|
var util = require("./utilities");
|
||||||
|
var Playlist = require("./playlist");
|
||||||
|
|
||||||
var DEFAULT_FILTERS = [
|
var DEFAULT_FILTERS = [
|
||||||
new Filter("monospace", "`(.+?)`", "g", "<code>$1</code>"),
|
new Filter("monospace", "`(.+?)`", "g", "<code>$1</code>"),
|
||||||
new Filter("bold", "\\*(.+?)\\*", "g", "<strong>$1</strong>"),
|
new Filter("bold", "\\*(.+?)\\*", "g", "<strong>$1</strong>"),
|
||||||
|
@ -7,7 +10,7 @@ var DEFAULT_FILTERS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
function Channel(name) {
|
function Channel(name) {
|
||||||
var self = this; // Alias `this` to prevent scoping issues
|
var self = this; // Alias `this` to prevent scoping issues
|
||||||
Logger.syslog.log("Loading channel " + name);
|
Logger.syslog.log("Loading channel " + name);
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
|
@ -16,7 +19,7 @@ function Channel(name) {
|
||||||
self.uniqueName = name.toLowerCase(); // To prevent casing issues
|
self.uniqueName = name.toLowerCase(); // To prevent casing issues
|
||||||
self.registered = false; // set to true if the channel exists in the database
|
self.registered = false; // set to true if the channel exists in the database
|
||||||
self.users = [];
|
self.users = [];
|
||||||
self.mutedUsers = new $util.Set();
|
self.mutedUsers = new util.Set();
|
||||||
self.playlist = new Playlist(self);
|
self.playlist = new Playlist(self);
|
||||||
self.plqueue = new AsyncQueue(); // For synchronizing playlist actions
|
self.plqueue = new AsyncQueue(); // For synchronizing playlist actions
|
||||||
self.drinks = 0;
|
self.drinks = 0;
|
||||||
|
@ -81,12 +84,13 @@ function Channel(name) {
|
||||||
html: "" // Filtered MOTD text (XSS removed; \n replaced by <br>)
|
html: "" // Filtered MOTD text (XSS removed; \n replaced by <br>)
|
||||||
};
|
};
|
||||||
self.filters = DEFAULT_FILTERS;
|
self.filters = DEFAULT_FILTERS;
|
||||||
self.banlist = new Banlist();
|
self.ipbans = {};
|
||||||
|
self.namebans = {};
|
||||||
self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs",
|
self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs",
|
||||||
self.uniqueName + ".log"));
|
self.uniqueName + ".log"));
|
||||||
self.css = ""; // Up to 20KB of inline CSS
|
self.css = ""; // Up to 20KB of inline CSS
|
||||||
self.js = ""; // Up to 20KB of inline Javascript
|
self.js = ""; // Up to 20KB of inline Javascript
|
||||||
|
|
||||||
self.error = false; // Set to true if something bad happens => don't save state
|
self.error = false; // Set to true if something bad happens => don't save state
|
||||||
|
|
||||||
self.on("ready", function () {
|
self.on("ready", function () {
|
||||||
|
@ -150,7 +154,7 @@ Channel.prototype.loadState = function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.readFile(path.join(__dirname, "../chandump", self.name),
|
fs.readFile(path.join(__dirname, "../chandump", self.uniqueName),
|
||||||
function (err, data) {
|
function (err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
// File didn't exist => start fresh
|
// File didn't exist => start fresh
|
||||||
|
@ -160,7 +164,7 @@ Channel.prototype.loadState = function () {
|
||||||
} else {
|
} else {
|
||||||
Logger.errlog.log("Failed to open channel dump " + self.uniqueName);
|
Logger.errlog.log("Failed to open channel dump " + self.uniqueName);
|
||||||
Logger.errlog.log(err);
|
Logger.errlog.log(err);
|
||||||
self.setMOTD("Internal error when loading channel");
|
self.setMOTD("Channel state load failed. Contact an administrator.");
|
||||||
self.error = true;
|
self.error = true;
|
||||||
self.emit("ready");
|
self.emit("ready");
|
||||||
}
|
}
|
||||||
|
@ -173,11 +177,16 @@ Channel.prototype.loadState = function () {
|
||||||
|
|
||||||
// Load the playlist
|
// Load the playlist
|
||||||
if ("playlist" in data) {
|
if ("playlist" in data) {
|
||||||
|
self.playlist.load(data.playlist, function () {
|
||||||
|
self.sendPlaylist(self.users);
|
||||||
|
self.sendPlaylistMeta(self.users);
|
||||||
|
self.playlist.startPlayback(data.playlist.time);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playlist lock
|
// Playlist lock
|
||||||
self.setLock(data.playlistLock || false);
|
self.setLock(data.playlistLock || false);
|
||||||
|
|
||||||
// Configurables
|
// Configurables
|
||||||
if ("opts" in data) {
|
if ("opts" in data) {
|
||||||
for (var key in data.opts) {
|
for (var key in data.opts) {
|
||||||
|
@ -203,8 +212,330 @@ Channel.prototype.loadState = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
// MOTD
|
||||||
|
if ("motd" in data) {
|
||||||
|
self.motd = {
|
||||||
|
motd: data.motd.motd,
|
||||||
|
html: data.motd.html
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat history
|
||||||
|
if ("chatbuffer" in data) {
|
||||||
|
data.chatbuffer.forEach(function (msg) {
|
||||||
|
self.chatbuffer.push(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline CSS/JS
|
||||||
|
self.css = data.css || "";
|
||||||
|
self.js = data.js || "";
|
||||||
|
self.emit("ready");
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
self.error = true;
|
||||||
|
Logger.errlog.log("Channel dump load failed (" + self.uniqueName + "): " + e);
|
||||||
|
self.setMOTD("Channel state load failed. Contact an administrator.");
|
||||||
|
self.emit("ready");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Channel.prototype.saveState = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (self.error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.registered || self.uniqueName === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filters = self.filters.map(function (f) {
|
||||||
|
return f.pack();
|
||||||
|
});
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
playlist: self.playlist.dump(),
|
||||||
|
opts: self.opts,
|
||||||
|
permissions: self.permissions,
|
||||||
|
filters: filters,
|
||||||
|
motd: self.motd,
|
||||||
|
playlistLock: self.playlistLock,
|
||||||
|
chatbuffer: self.chatbuffer,
|
||||||
|
css: self.css,
|
||||||
|
js: self.js
|
||||||
|
};
|
||||||
|
|
||||||
|
var text = JSON.stringify(data);
|
||||||
|
fs.writeFileSync(path.join(__dirname, "../chandump", self.uniqueName), text);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a user has the given permission node
|
||||||
|
*/
|
||||||
|
Channel.prototype.hasPermission = function (user, key) {
|
||||||
|
// Special case: you can have separate permissions for when playlist is unlocked
|
||||||
|
if (key.indexOf("playlist") === 0 && !this.playlistLock) {
|
||||||
|
var key2 = "o" + key;
|
||||||
|
var v = this.permissions[key2];
|
||||||
|
if (typeof v === "number" && user.rank >= v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var v = this.permissions[key];
|
||||||
|
if (typeof v !== "number") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.rank >= v;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defer a callback to complete when the channel is ready to accept users.
|
||||||
|
* Called immediately if the ready flag is already set
|
||||||
|
*/
|
||||||
|
Channel.prototype.whenReady = function (fn) {
|
||||||
|
var self = this;
|
||||||
|
if (self.ready) {
|
||||||
|
setImmediate(fn);
|
||||||
|
} else {
|
||||||
|
self.on("ready", fn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up a user's rank in the channel. Computed as max(global_rank, channel rank)
|
||||||
|
*/
|
||||||
|
Channel.prototype.getRank = function (name, callback) {
|
||||||
|
var self = this;
|
||||||
|
db.users.getGlobalRank(name, function (err, global) {
|
||||||
|
if (self.dead) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
callback(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.registered) {
|
||||||
|
callback(null, global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.channels.getRank(self.name, name, function (err, rank) {
|
||||||
|
if (self.dead) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
callback(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, Math.max(rank, global));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a user joins a channel
|
||||||
|
*/
|
||||||
|
Channel.prototype.join = function (user, password) {
|
||||||
|
var self = this;
|
||||||
|
self.whenReady(function () {
|
||||||
|
if (self.opts.password !== false && user.rank < 2) {
|
||||||
|
if (password !== self.opts.password) {
|
||||||
|
user.socket.emit("needPassword", typeof password === "undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.socket.emit("cancelNeedPassword");
|
||||||
|
var range = user.ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3");
|
||||||
|
if (user.ip in self.ipbans || range in self.ipbans ||
|
||||||
|
user.name.toLowerCase() in self.namebans) {
|
||||||
|
user.kick("You're banned!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.autoAFK();
|
||||||
|
user.socket.join(self.uniqueName);
|
||||||
|
user.channel = self;
|
||||||
|
|
||||||
|
self.users.push(user);
|
||||||
|
self.sendVoteskipUpdate(self.users);
|
||||||
|
self.sendUsercount(self.users);
|
||||||
|
|
||||||
|
user.whenLoggedIn(function () {
|
||||||
|
var lname = user.name.toLowerCase();
|
||||||
|
for (var i = 0; i < self.users.length; i++) {
|
||||||
|
if (self.users[i] === user) {
|
||||||
|
Logger.errlog.log("Wat: join() called on user already in channel");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.users[i].kick("Duplicate login");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.getRank(user.name, function (err, rank) {
|
||||||
|
if (self.dead) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
user.rank = user.global_rank;
|
||||||
|
} else {
|
||||||
|
user.rank = Math.max(rank, user.global_rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.socket.emit("rank", user.rank);
|
||||||
|
self.sendUserJoin(self.users);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
self.sendPlaylist([user]);
|
||||||
|
self.sendMediaUpdate([user]);
|
||||||
|
self.sendPlaylistLock([user]);
|
||||||
|
self.sendUserlist([user]);
|
||||||
|
self.sendRecentchat([user]);
|
||||||
|
self.sendCSSJS([user]);
|
||||||
|
self.sendPoll([user]);
|
||||||
|
self.sendOpts([user]);
|
||||||
|
self.sendPermissions([user]);
|
||||||
|
self.sendMotd([user]);
|
||||||
|
self.sendDrinkCount([user]);
|
||||||
|
|
||||||
|
self.logger.log("+++ " + user.ip + " joined");
|
||||||
|
Logger.syslog.log(user.ip + " joined channel " + self.name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a user leaves the channel.
|
||||||
|
* Cleans up and sends appropriate updates to other users
|
||||||
|
*/
|
||||||
|
Channel.prototype.part = function (user) {
|
||||||
|
user.channel = null;
|
||||||
|
|
||||||
|
// Clear poll vote
|
||||||
|
if (self.poll) {
|
||||||
|
self.poll.unvote(user.ip);
|
||||||
|
self.sendPoll(self.users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear voteskip vote
|
||||||
|
if (self.voteskip) {
|
||||||
|
self.voteskip.unvote(user.ip);
|
||||||
|
self.sendVoteskipUpdate(self.users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return video lead to server if necessary
|
||||||
|
if (self.leader === user) {
|
||||||
|
self.changeLeader("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from users array
|
||||||
|
var idx = self.users.indexOf(user);
|
||||||
|
if (idx >= 0 && idx < self.users.length) {
|
||||||
|
self.users.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A change in usercount might cause a voteskip result to change
|
||||||
|
self.checkVoteskipPass();
|
||||||
|
self.sendUsercount(self.users);
|
||||||
|
|
||||||
|
if (user.loggedIn) {
|
||||||
|
self.sendUserLeave(self.users, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.log("--- " + user.ip + " (" + user.name + ") left");
|
||||||
|
if (self.users.length === 0) {
|
||||||
|
self.emit("empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the MOTD and broadcast it to connected users
|
||||||
|
*/
|
||||||
|
Channel.prototype.setMOTD = function (message) {
|
||||||
|
var self = this;
|
||||||
|
self.motd.motd = message;
|
||||||
|
// TODO XSS filter
|
||||||
|
self.motd.html = message.replace(/\n/g, "<br>");
|
||||||
|
self.sendMOTD(self.users);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the MOTD to the given users
|
||||||
|
*/
|
||||||
|
Channel.prototype.sendMOTD = function (users) {
|
||||||
|
var self = this;
|
||||||
|
users.forEach(function (u) {
|
||||||
|
u.socket.emit("setMotd", self.motd);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Channel.prototype.tryReadLog = function (user) {
|
||||||
|
if (user.rank < 3) {
|
||||||
|
user.kick("Attempted readChanLog with insufficient permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterIp = user.global_rank < 255;
|
||||||
|
this.readLog(filterIp, function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
user.socket.emit("readChanLog", {
|
||||||
|
success: false,
|
||||||
|
data: "Reading channel log failed."
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
user.socket.emit("readChanLog", {
|
||||||
|
success: true,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Channel.prototype.readLog = function (filterIp, callback) {
|
||||||
|
var maxLen = 102400; // Limit to last 100KiB
|
||||||
|
var file = this.logger.filename;
|
||||||
|
|
||||||
|
fs.stat(file, function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
callback(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = Math.max(data.size - maxLen, 0);
|
||||||
|
var end = data.size - 1;
|
||||||
|
|
||||||
|
var rs = fs.createReadStream(file, {
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
});
|
||||||
|
|
||||||
|
var buffer = "";
|
||||||
|
rs.on("data", function (data) {
|
||||||
|
buffer += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
rs.on("end", function () {
|
||||||
|
if (filterIp) {
|
||||||
|
buffer = buffer.replace(
|
||||||
|
/\d+\.\d+\.(\d+\.\d+)/g,
|
||||||
|
"x.x.$1"
|
||||||
|
).replace(
|
||||||
|
/\d+\.\d+\.(\d+)/g,
|
||||||
|
"x.x.$.*"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, buffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue