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 = [
|
||||
new Filter("monospace", "`(.+?)`", "g", "<code>$1</code>"),
|
||||
new Filter("bold", "\\*(.+?)\\*", "g", "<strong>$1</strong>"),
|
||||
|
@ -7,7 +10,7 @@ var DEFAULT_FILTERS = [
|
|||
];
|
||||
|
||||
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);
|
||||
|
||||
// Defaults
|
||||
|
@ -16,7 +19,7 @@ function Channel(name) {
|
|||
self.uniqueName = name.toLowerCase(); // To prevent casing issues
|
||||
self.registered = false; // set to true if the channel exists in the database
|
||||
self.users = [];
|
||||
self.mutedUsers = new $util.Set();
|
||||
self.mutedUsers = new util.Set();
|
||||
self.playlist = new Playlist(self);
|
||||
self.plqueue = new AsyncQueue(); // For synchronizing playlist actions
|
||||
self.drinks = 0;
|
||||
|
@ -81,12 +84,13 @@ function Channel(name) {
|
|||
html: "" // Filtered MOTD text (XSS removed; \n replaced by <br>)
|
||||
};
|
||||
self.filters = DEFAULT_FILTERS;
|
||||
self.banlist = new Banlist();
|
||||
self.ipbans = {};
|
||||
self.namebans = {};
|
||||
self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs",
|
||||
self.uniqueName + ".log"));
|
||||
self.css = ""; // Up to 20KB of inline CSS
|
||||
self.js = ""; // Up to 20KB of inline Javascript
|
||||
|
||||
|
||||
self.error = false; // Set to true if something bad happens => don't save state
|
||||
|
||||
self.on("ready", function () {
|
||||
|
@ -150,7 +154,7 @@ Channel.prototype.loadState = function () {
|
|||
return;
|
||||
}
|
||||
|
||||
fs.readFile(path.join(__dirname, "../chandump", self.name),
|
||||
fs.readFile(path.join(__dirname, "../chandump", self.uniqueName),
|
||||
function (err, data) {
|
||||
if (err) {
|
||||
// File didn't exist => start fresh
|
||||
|
@ -160,7 +164,7 @@ Channel.prototype.loadState = function () {
|
|||
} else {
|
||||
Logger.errlog.log("Failed to open channel dump " + self.uniqueName);
|
||||
Logger.errlog.log(err);
|
||||
self.setMOTD("Internal error when loading channel");
|
||||
self.setMOTD("Channel state load failed. Contact an administrator.");
|
||||
self.error = true;
|
||||
self.emit("ready");
|
||||
}
|
||||
|
@ -173,11 +177,16 @@ Channel.prototype.loadState = function () {
|
|||
|
||||
// Load the playlist
|
||||
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
|
||||
self.setLock(data.playlistLock || false);
|
||||
|
||||
|
||||
// Configurables
|
||||
if ("opts" in data) {
|
||||
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