sync/lib/channel.js

2459 lines
70 KiB
JavaScript
Raw Normal View History

2013-04-03 17:47:41 +00:00
2013-03-24 02:28:20 +00:00
/*
The MIT License (MIT)
Copyright (c) 2013 Calvin Montgomery
2013-04-03 17:47:41 +00:00
2013-03-24 02:28:20 +00:00
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:
2013-04-03 17:47:41 +00:00
2013-03-24 02:28:20 +00:00
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2013-04-03 17:47:41 +00:00
2013-03-24 02:28:20 +00:00
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.
*/
2013-02-16 17:19:59 +00:00
var fs = require("fs");
var path = require("path");
2013-10-11 21:31:40 +00:00
var url = require("url");
2013-10-11 23:15:44 +00:00
var Server = require("./server");
2013-04-03 17:47:41 +00:00
var Poll = require("./poll.js").Poll;
var Media = require("./media.js").Media;
2013-03-27 19:28:51 +00:00
var Logger = require("./logger.js");
2013-04-04 19:56:43 +00:00
var ChatCommand = require("./chatcommand.js");
2013-04-29 17:29:31 +00:00
var Filter = require("./filter.js").Filter;
2013-06-29 22:09:20 +00:00
var Playlist = require("./playlist");
var sanitize = require("validator").sanitize;
2013-08-18 22:58:16 +00:00
var $util = require("./utilities");
2013-09-30 00:53:27 +00:00
var AsyncQueue = require("./asyncqueue");
2013-10-11 21:31:40 +00:00
var ActionLog = require("./actionlog");
var InfoGetter = require("./get-info");
2013-02-16 05:02:42 +00:00
2013-10-11 21:31:40 +00:00
var Channel = function(name) {
2013-08-17 17:30:52 +00:00
var self = this;
2013-03-27 19:28:51 +00:00
Logger.syslog.log("Opening channel " + name);
2013-08-17 17:30:52 +00:00
self.initialized = false;
2013-08-18 17:21:34 +00:00
self.dbloaded = false;
2013-10-11 21:31:40 +00:00
self.server = Server.getServer();
2013-04-03 17:47:41 +00:00
2013-08-17 17:30:52 +00:00
self.name = name;
self.canonical_name = name.toLowerCase();
2013-04-03 17:47:41 +00:00
// Initialize defaults
2013-08-17 17:30:52 +00:00
self.registered = false;
self.users = [];
2013-10-16 22:36:05 +00:00
self.mutedUsers = new $util.Set();
2013-08-17 17:30:52 +00:00
self.playlist = new Playlist(self);
2013-09-30 00:53:27 +00:00
self.plqueue = new AsyncQueue();
2013-08-17 17:30:52 +00:00
self.position = -1;
self.drinks = 0;
self.leader = null;
self.chatbuffer = [];
self.openqueue = false;
self.poll = false;
self.voteskip = false;
self.permissions = {
2013-05-22 19:38:16 +00:00
oplaylistadd: -1,
oplaylistnext: 1.5,
oplaylistmove: 1.5,
oplaylistdelete: 2,
oplaylistjump: 1.5,
2013-06-01 20:56:23 +00:00
oplaylistaddlist: 1.5,
2013-05-22 19:38:16 +00:00
playlistadd: 1.5,
playlistnext: 1.5,
playlistmove: 1.5,
playlistdelete: 2,
playlistjump: 1.5,
2013-06-01 20:56:23 +00:00
playlistaddlist: 1.5,
playlistaddcustom: 3,
2013-06-05 02:51:41 +00:00
playlistaddlive: 1.5,
2013-07-04 23:11:13 +00:00
exceedmaxlength: 2,
2013-05-22 19:38:16 +00:00
addnontemp: 2,
settemp: 2,
playlistgeturl: 1.5,
playlistshuffle: 2,
playlistclear: 2,
pollctl: 1.5,
pollvote: -1,
2013-09-12 01:22:00 +00:00
viewhiddenpoll: 1.5,
2013-09-12 03:16:56 +00:00
voteskip: -1,
2013-06-25 14:18:33 +00:00
mute: 1.5,
2013-05-22 19:38:16 +00:00
kick: 1.5,
ban: 2,
motdedit: 3,
filteredit: 3,
2013-06-20 19:02:53 +00:00
drink: 1.5,
chat: 0
2013-05-22 19:38:16 +00:00
};
2013-08-17 17:30:52 +00:00
self.opts = {
2013-04-02 19:07:22 +00:00
allow_voteskip: true,
voteskip_ratio: 0.5,
afk_timeout: 180,
2013-08-17 17:30:52 +00:00
pagetitle: self.name,
2013-07-04 23:11:13 +00:00
maxlength: 0,
2013-06-17 22:16:59 +00:00
externalcss: "",
externaljs: "",
2013-05-01 22:49:34 +00:00
chat_antiflood: false,
2013-06-05 20:49:54 +00:00
show_public: false,
enable_link_regex: true
};
2013-08-17 17:30:52 +00:00
self.filters = [
2013-04-29 17:29:31 +00:00
new Filter("monospace", "`([^`]+)`", "g", "<code>$1</code>"),
new Filter("bold", "(^|\\s)\\*([^\\*]+)\\*", "g", "$1<strong>$2</strong>"),
2013-04-29 17:29:31 +00:00
new Filter("italic", "(^| )_([^_]+)_", "g", "$1<em>$2</em>"),
2013-05-22 19:38:16 +00:00
new Filter("strikethrough", "~~([^~]+)~~", "g", "<s>$1</s>"),
new Filter("inline spoiler", "\\[spoiler\\](.*)\\[\\/spoiler\\]", "ig", "<span class=\"spoiler\">$1</span>"),
2013-03-29 18:15:46 +00:00
];
2013-08-17 17:30:52 +00:00
self.motd = {
2013-04-01 21:02:09 +00:00
motd: "",
html: ""
};
2013-08-17 17:30:52 +00:00
self.ipbans = {};
self.namebans = {};
self.ip_alias = {};
self.name_alias = {};
self.login_hist = [];
self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs",
self.canonical_name + ".log"));
2013-08-17 17:30:52 +00:00
self.i = 0;
self.time = new Date().getTime();
self.plmeta = {
count: 0,
time: "00:00"
};
2013-04-03 17:47:41 +00:00
2013-08-17 17:30:52 +00:00
self.css = "";
self.js = "";
2013-05-15 15:34:27 +00:00
2013-08-17 17:30:52 +00:00
self.ipkey = "";
2013-05-21 16:17:01 +00:00
for(var i = 0; i < 15; i++) {
2013-08-17 17:30:52 +00:00
self.ipkey += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[parseInt(Math.random() * 65)]
2013-05-21 16:17:01 +00:00
}
2013-10-11 23:15:44 +00:00
Server.getServer().db.loadChannelData(self, function (err) {
if (err && err === "channel_dead")
return;
else if (!err || err === "channel_unregistered")
self.dbloaded = true;
2013-08-17 17:30:52 +00:00
if(self.registered) {
self.loadDump();
}
});
2013-04-03 17:47:41 +00:00
}
2013-05-22 19:38:16 +00:00
/* REGION Permissions */
Channel.prototype.hasPermission = function(user, key) {
if(key.indexOf("playlist") == 0 && this.openqueue) {
var key2 = "o" + key;
var v = this.permissions[key2];
2013-05-23 04:03:37 +00:00
if(typeof v == "number" && user.rank >= v) {
return true;
2013-05-22 19:38:16 +00:00
}
}
var v = this.permissions[key];
if(typeof v != "number") {
return false;
}
return user.rank >= v;
}
2013-04-03 17:47:41 +00:00
/* REGION Channel data */
Channel.prototype.loadDump = function() {
2013-08-13 14:46:18 +00:00
var self = this;
if(self.name === "")
return;
fs.stat(path.join(__dirname, "../chandump", self.name),
function (err, stats) {
if (self.dead)
return;
2013-08-13 14:46:18 +00:00
if(!err) {
var mb = stats.size / 1048576;
mb = parseInt(mb * 100) / 100;
if(mb > 1) {
Logger.errlog.log("Large chandump detected: " + self.name +
" (" + mb + " MB)");
self.updateMotd("Your channel file has exceeded the " +
"maximum size of 1MB and cannot be " +
"loaded. Please ask an administrator " +
2013-08-13 14:46:18 +00:00
"for assistance in restoring it.");
return;
2013-06-19 21:54:27 +00:00
}
}
fs.readFile(path.join(__dirname, "../chandump", self.name),
function(err, data) {
if (self.dead)
return;
2013-08-13 14:46:18 +00:00
if(err) {
if(err.code == "ENOENT") {
Logger.errlog.log("WARN: missing dump for " + self.name);
self.initialized = true;
self.saveDump();
2013-06-17 22:16:59 +00:00
}
else {
2013-08-13 14:46:18 +00:00
Logger.errlog.log("Failed to open channel dump " + self.name);
Logger.errlog.log(err);
2013-06-17 22:16:59 +00:00
}
2013-08-13 14:46:18 +00:00
return;
}
2013-08-13 14:46:18 +00:00
try {
self.logger.log("*** Loading channel dump from disk");
data = JSON.parse(data);
/* Load the playlist */
// Old
if(data.queue) {
if(data.position < 0)
data.position = 0;
for(var i = 0; i < data.queue.length; i++) {
var e = data.queue[i];
var m = new Media(e.id, e.title, e.seconds, e.type);
var p = self.playlist.makeItem(m);
p.queueby = data.queue[i].queueby ? data.queue[i].queueby
: "";
p.temp = e.temp;
self.playlist.items.append(p);
if(i == data.position)
self.playlist.current = p;
}
self.sendAll("playlist", self.playlist.items.toArray());
self.broadcastPlaylistMeta();
self.playlist.startPlayback();
}
// Current
else if(data.playlist) {
self.playlist.load(data.playlist, function() {
if (self.dead)
return;
self.sendAll("playlist", self.playlist.items.toArray());
self.broadcastPlaylistMeta();
self.playlist.startPlayback(data.playlist.time);
2013-08-13 14:46:18 +00:00
});
}
for(var key in data.opts) {
// Gotta love backwards compatibility
if(key == "customcss" || key == "customjs") {
var k = key.substring(6);
self.opts[k] = data.opts[key];
2013-04-29 17:29:31 +00:00
}
else {
2013-08-13 14:46:18 +00:00
self.opts[key] = data.opts[key];
2013-04-29 17:29:31 +00:00
}
}
2013-08-13 14:46:18 +00:00
for(var key in data.permissions) {
self.permissions[key] = data.permissions[key];
}
self.sendAll("setPermissions", self.permissions);
self.broadcastOpts();
self.users.forEach(function (u) {
u.autoAFK();
});
if(data.filters) {
for(var i = 0; i < data.filters.length; i++) {
var f = data.filters[i];
// Backwards compatibility
if(f[0] != undefined) {
var filt = new Filter("", f[0], "g", f[1]);
filt.active = f[2];
self.updateFilter(filt, false);
}
else {
var filt = new Filter(f.name, f.source, f.flags, f.replace);
filt.active = f.active;
filt.filterlinks = f.filterlinks;
self.updateFilter(filt, false);
}
}
self.broadcastChatFilters();
}
if(data.motd) {
self.motd = data.motd;
self.broadcastMotd();
}
self.setLock(!(data.openqueue || false));
self.chatbuffer = data.chatbuffer || [];
for(var i = 0; i < self.chatbuffer.length; i++) {
self.sendAll("chatMsg", self.chatbuffer[i]);
}
self.css = data.css || "";
self.js = data.js || "";
self.sendAll("channelCSSJS", {css: self.css, js: self.js});
self.initialized = true;
2013-04-01 21:02:09 +00:00
}
2013-08-13 14:46:18 +00:00
catch(e) {
Logger.errlog.log("Channel dump load failed: ");
Logger.errlog.log(e.stack);
2013-05-04 17:02:38 +00:00
}
2013-08-13 14:46:18 +00:00
});
});
2013-02-16 05:02:42 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.saveDump = function() {
if (this.dead)
return;
if(!this.initialized || this.name === "")
2013-06-15 20:07:38 +00:00
return;
2013-04-03 17:47:41 +00:00
var filts = new Array(this.filters.length);
for(var i = 0; i < this.filters.length; i++) {
2013-04-29 17:29:31 +00:00
filts[i] = this.filters[i].pack();
2013-04-03 17:47:41 +00:00
}
var dump = {
position: this.position,
currentTime: this.media ? this.media.currentTime : 0,
2013-07-01 22:45:55 +00:00
playlist: this.playlist.dump(),
2013-04-03 17:47:41 +00:00
opts: this.opts,
2013-05-22 19:38:16 +00:00
permissions: this.permissions,
2013-04-03 17:47:41 +00:00
filters: filts,
2013-04-29 23:59:51 +00:00
motd: this.motd,
2013-05-04 17:02:38 +00:00
openqueue: this.openqueue,
2013-05-15 15:34:27 +00:00
chatbuffer: this.chatbuffer,
css: this.css,
js: this.js
2013-04-03 17:47:41 +00:00
};
var text = JSON.stringify(dump);
fs.writeFileSync(path.join(__dirname, "../chandump", this.name), text);
2013-02-16 05:02:42 +00:00
}
Channel.prototype.readLog = function (filterIp, callback) {
var maxLen = 100000; // Most recent 100KB
var file = this.logger.filename;
fs.stat(file, function (err, data) {
if(err) {
callback(err, null);
return;
}
var start = data.size - maxLen;
if(start < 0) {
start = 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,
2013-08-18 22:58:16 +00:00
"x.x.$1"
).replace(
/\d+\.\d+\.(\d+)/g,
2013-08-18 22:58:16 +00:00
"x.x.$1.*"
);
}
callback(false, buffer);
});
});
}
Channel.prototype.tryReadLog = function (user) {
2013-10-12 23:59:50 +00:00
if(user.rank < 3) {
user.kick("Attempted readChanLog with insufficient permission");
return;
2013-10-12 23:59:50 +00:00
}
var filterIp = true;
if(user.global_rank >= 255)
filterIp = false;
this.readLog(filterIp, function (err, data) {
if(err) {
user.socket.emit("readChanLog", {
success: false
});
} else {
user.socket.emit("readChanLog", {
success: true,
data: data
});
}
});
}
2013-08-17 17:30:52 +00:00
Channel.prototype.tryRegister = function (user) {
var self = this;
if(self.registered) {
2013-10-11 21:31:40 +00:00
ActionLog.record(user.ip, user.name, "channel-register-failure",
2013-08-17 17:30:52 +00:00
[self.name, "Channel already registered"]);
user.socket.emit("registerChannel", {
2013-03-17 17:14:34 +00:00
success: false,
error: "This channel is already registered"
});
}
else if(!user.loggedIn) {
2013-10-11 21:31:40 +00:00
ActionLog.record(user.ip, user.name, "channel-register-failure",
2013-08-17 17:30:52 +00:00
[self.name, "Not logged in"]);
user.socket.emit("registerChannel", {
2013-03-17 17:14:34 +00:00
success: false,
error: "You must log in to register a channel"
});
}
2013-10-11 21:31:40 +00:00
else if(user.rank < 10) {
ActionLog.record(user.ip, user.name, "channel-register-failure",
2013-08-17 17:30:52 +00:00
[self.name, "Insufficient permissions"]);
user.socket.emit("registerChannel", {
2013-03-17 17:14:34 +00:00
success: false,
2013-08-22 04:10:55 +00:00
error: "You don't have permission to register this channel"
2013-03-17 17:14:34 +00:00
});
}
else {
2013-08-17 17:30:52 +00:00
self.server.db.registerChannel(self.name, user.name,
function (err, res) {
if(err) {
user.socket.emit("registerChannel", {
success: false,
error: "Unable to register channel: " + err
});
return;
}
2013-10-11 21:31:40 +00:00
ActionLog.record(user.ip, user.name,
2013-08-17 17:30:52 +00:00
"channel-register-success", self.name);
if (self.dead)
return;
2013-08-17 17:30:52 +00:00
self.registered = true;
self.initialized = true;
self.saveDump();
self.saveRank(user);
user.socket.emit("registerChannel", {
2013-08-17 17:30:52 +00:00
success: true
2013-03-17 17:14:34 +00:00
});
2013-08-17 17:30:52 +00:00
self.logger.log("*** " + user.name + " registered the channel");
});
2013-03-17 17:14:34 +00:00
}
}
2013-08-17 17:30:52 +00:00
Channel.prototype.unregister = function (user) {
var self = this;
if(!self.registered) {
user.socket.emit("unregisterChannel", {
success: false,
error: "This channel is already unregistered"
});
return;
2013-05-13 19:41:29 +00:00
}
2013-08-17 17:30:52 +00:00
if(user.rank < 10) {
user.socket.emit("unregisterChannel", {
success: false,
error: "You must be the channel owner to unregister it"
});
return;
2013-02-16 05:02:42 +00:00
}
2013-08-17 17:30:52 +00:00
self.server.db.dropChannel(self.name, function (err, res) {
if(err) {
user.socket.emit("unregisterChannel", {
success: false,
error: "Unregistration failed: " + err
});
return;
}
2013-08-17 17:30:52 +00:00
user.socket.emit("unregisterChannel", { success: true });
if (!self.dead)
self.registered = false;
2013-08-17 17:30:52 +00:00
});
2013-02-16 05:02:42 +00:00
}
2013-08-17 17:30:52 +00:00
Channel.prototype.getRank = function (name, callback) {
var self = this;
self.server.db.getGlobalRank(name, function (err, global) {
if (self.dead)
return;
2013-08-17 17:30:52 +00:00
if(err) {
callback(err, null);
return;
}
if(!self.registered) {
callback(null, global);
return;
}
self.server.db.getChannelRank(self.name, name,
function (err, rank) {
if(err) {
callback(err, null);
return;
}
callback(null, rank > global ? rank : global);
});
});
2013-02-16 05:02:42 +00:00
}
2013-08-18 18:35:57 +00:00
Channel.prototype.saveRank = function (user, callback) {
2013-08-18 18:16:46 +00:00
if(!this.registered)
return;
if(!user.saverank)
return;
2013-08-18 18:35:57 +00:00
this.server.db.setChannelRank(this.name, user.name, user.rank, callback);
2013-08-17 17:30:52 +00:00
}
Channel.prototype.saveInitialRank = function (user, callback) {
if(!this.registered)
return;
this.server.db.insertChannelRank(this.name, user.name, user.rank, callback);
};
2013-08-17 17:30:52 +00:00
Channel.prototype.getIPRank = function (ip, callback) {
var self = this;
2013-08-18 22:58:16 +00:00
self.server.db.listAliases(ip, function (err, names) {
if (self.dead)
return;
2013-10-07 15:04:08 +00:00
self.server.db.listGlobalRanks(names, function (err, res) {
2013-08-17 17:30:52 +00:00
if(err) {
callback(err, null);
return;
}
2013-06-15 20:07:38 +00:00
2013-08-17 17:30:52 +00:00
var rank = 0;
for(var i in res) {
2013-08-18 22:58:16 +00:00
rank = (res[i] > rank) ? res[i] : rank;
2013-08-17 17:30:52 +00:00
}
2013-10-07 15:04:08 +00:00
if (!self.registered) {
callback(null, rank);
return;
}
self.server.db.listChannelUserRanks(self.name, names,
function (err, res) {
if (self.dead)
return;
2013-08-18 22:58:16 +00:00
if(err) {
callback(err, null);
return;
}
for(var i in res) {
rank = (res[i] > rank) ? res[i] : rank;
}
callback(null, rank);
});
2013-10-07 15:04:08 +00:00
2013-08-17 17:30:52 +00:00
});
2013-08-18 22:58:16 +00:00
});
2013-05-19 21:23:35 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.cacheMedia = function(media) {
2013-08-17 17:37:35 +00:00
var self = this;
2013-11-07 23:19:36 +00:00
if (media.type === "gd")
return false;
2013-06-12 03:44:16 +00:00
// Prevent the copy in the playlist from messing with this one
media = media.dup();
2013-05-04 22:54:28 +00:00
if(media.temp) {
return;
}
2013-08-17 17:37:35 +00:00
if(self.registered) {
self.server.db.addToLibrary(self.name, media);
2013-02-16 05:02:42 +00:00
}
}
2013-06-18 03:57:29 +00:00
Channel.prototype.tryNameBan = function(actor, name) {
2013-08-17 17:30:52 +00:00
var self = this;
if(!self.hasPermission(actor, "ban")) {
2013-05-21 16:17:01 +00:00
return false;
}
name = name.toLowerCase();
2013-08-18 23:35:49 +00:00
if(name == actor.name.toLowerCase()) {
actor.socket.emit("costanza", {
msg: "Trying to ban yourself?"
});
return;
}
2013-05-21 16:17:01 +00:00
2013-08-17 17:30:52 +00:00
self.getRank(name, function (err, rank) {
if (self.dead)
return;
2013-08-17 17:30:52 +00:00
if(err) {
actor.socket.emit("errorMsg", {
2013-08-18 22:58:16 +00:00
msg: "Internal error " + err
2013-08-17 17:30:52 +00:00
});
return;
}
2013-05-21 16:17:01 +00:00
2013-08-17 17:30:52 +00:00
if(rank >= actor.rank) {
actor.socket.emit("errorMsg", {
2013-08-19 00:31:34 +00:00
msg: "You don't have permission to ban " + name
2013-08-17 17:30:52 +00:00
});
return;
}
self.namebans[name] = actor.name;
for(var i = 0; i < self.users.length; i++) {
if(self.users[i].name.toLowerCase() == name) {
self.kick(self.users[i], "You're banned!");
break;
}
}
self.logger.log("*** " + actor.name + " namebanned " + name);
var notice = {
username: "[server]",
msg: actor.name + " banned " + name,
2013-11-17 21:32:19 +00:00
meta: { addClass: "server-whisper" },
time: Date.now()
};
2013-08-17 17:30:52 +00:00
self.users.forEach(function(u) {
if(self.hasPermission(u, "ban")) {
self.sendBanlist(u);
u.socket.emit("chatMsg", notice);
}
2013-08-17 17:30:52 +00:00
});
if(!self.registered) {
return;
2013-05-21 16:17:01 +00:00
}
2013-08-17 17:30:52 +00:00
self.server.db.addChannelBan(self.name, "*", name, actor.name);
});
2013-05-21 16:17:01 +00:00
}
Channel.prototype.unbanName = function(actor, name) {
2013-08-17 17:37:35 +00:00
var self = this;
if(!self.hasPermission(actor, "ban")) {
2013-10-12 23:59:50 +00:00
actor.kick("Attempted unban with insufficient permission");
2013-05-21 16:17:01 +00:00
return false;
}
2013-08-17 17:37:35 +00:00
self.namebans[name] = null;
delete self.namebans[name];
self.logger.log("*** " + actor.name + " un-namebanned " + name);
2013-10-07 15:04:08 +00:00
if (!self.registered)
return;
2013-08-17 17:37:35 +00:00
self.server.db.clearChannelNameBan(self.name, name, function (err, res) {
if (self.dead)
return;
2013-05-21 16:17:01 +00:00
2013-08-17 17:37:35 +00:00
self.users.forEach(function(u) {
self.sendBanlist(u);
});
2013-06-18 14:46:28 +00:00
});
2013-05-21 16:17:01 +00:00
}
2013-06-18 03:57:29 +00:00
Channel.prototype.tryIPBan = function(actor, name, range) {
2013-08-17 17:30:52 +00:00
var self = this;
if(!self.hasPermission(actor, "ban")) {
return;
2013-05-21 16:17:01 +00:00
}
2013-06-18 03:57:29 +00:00
if(typeof name != "string") {
2013-08-17 17:30:52 +00:00
return;
2013-05-21 16:17:01 +00:00
}
2013-08-18 23:35:49 +00:00
name = name.toLowerCase();
if(name == actor.name.toLowerCase()) {
actor.socket.emit("costanza", {
msg: "Trying to ban yourself?"
});
return;
}
2013-08-17 17:37:35 +00:00
self.server.db.listIPsForName(name, function (err, ips) {
if (self.dead)
return;
2013-08-17 17:37:35 +00:00
if(err) {
actor.socket.emit("errorMsg", {
2013-08-18 22:58:16 +00:00
msg: "Internal error: " + err
2013-08-17 17:37:35 +00:00
});
return;
}
ips.forEach(function (ip) {
if(range)
ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3");
self.getIPRank(ip, function (err, rank) {
if (self.dead)
return;
2013-08-17 17:37:35 +00:00
if(err) {
actor.socket.emit("errorMsg", {
msg: "Internal error: " + err
2013-08-17 17:37:35 +00:00
});
return;
}
2013-08-17 17:30:52 +00:00
2013-08-17 17:37:35 +00:00
if(rank >= actor.rank) {
actor.socket.emit("errorMsg", {
2013-08-18 22:58:16 +00:00
msg: "You don't have permission to ban IP: " +
$util.maskIP(ip)
2013-08-17 17:37:35 +00:00
});
return;
}
2013-05-21 16:17:01 +00:00
2013-08-17 17:37:35 +00:00
self.ipbans[ip] = [name, actor.name];
self.logger.log("*** " + actor.name + " banned " + ip +
2013-08-17 17:37:35 +00:00
" (" + name + ")");
2013-05-21 16:17:01 +00:00
2013-08-17 17:37:35 +00:00
for(var i = 0; i < self.users.length; i++) {
if(self.users[i].ip.indexOf(ip) == 0) {
self.kick(self.users[i], "Your IP is banned!");
i--;
}
2013-08-17 17:30:52 +00:00
}
2013-06-18 14:46:28 +00:00
2013-08-17 17:37:35 +00:00
if(!self.registered)
return;
2013-08-17 17:30:52 +00:00
2013-08-18 18:05:12 +00:00
self.server.db.addChannelBan(self.name, ip, name,
2013-08-17 17:37:35 +00:00
actor.name,
function (err, res) {
if (self.dead)
return;
var notice = {
username: "[server]",
msg: actor.name + " banned " + $util.maskIP(ip) +
" (" + name + ")",
2013-11-17 21:32:19 +00:00
meta: { addClass: "server-whisper" },
time: Date.now()
};
2013-08-17 17:37:35 +00:00
self.users.forEach(function(u) {
if(self.hasPermission(u, "ban")) {
u.socket.emit("chatMsg", notice);
self.sendBanlist(u);
}
2013-08-17 17:37:35 +00:00
});
2013-08-17 17:30:52 +00:00
});
});
});
2013-06-18 14:46:28 +00:00
});
2013-05-21 16:17:01 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.unbanIP = function(actor, ip) {
2013-08-17 17:37:35 +00:00
var self = this;
2013-10-12 23:59:50 +00:00
if(!self.hasPermission(actor, "ban")) {
actor.kick("Attempted unban with insufficient permission");
2013-05-21 16:17:01 +00:00
return false;
2013-10-12 23:59:50 +00:00
}
2013-05-21 16:17:01 +00:00
2013-08-17 17:37:35 +00:00
self.ipbans[ip] = null;
self.users.forEach(function(u) {
self.sendBanlist(u);
2013-06-18 14:46:28 +00:00
});
2013-03-24 03:45:10 +00:00
2013-08-17 17:37:35 +00:00
self.logger.log("*** " + actor.name + " unbanned " + ip);
2013-08-07 03:21:54 +00:00
2013-08-17 17:37:35 +00:00
if(!self.registered)
2013-04-03 17:47:41 +00:00
return false;
// Update database ban table
2013-08-17 17:37:35 +00:00
self.server.db.clearChannelIPBan(self.name, ip);
2013-03-24 03:45:10 +00:00
}
2013-05-21 16:17:01 +00:00
Channel.prototype.tryUnban = function(actor, data) {
2013-10-12 23:59:50 +00:00
if(typeof data.ip_hidden === "string") {
2013-06-18 03:57:29 +00:00
var ip = this.hideIP(data.ip_hidden);
2013-05-21 16:17:01 +00:00
this.unbanIP(actor, ip);
}
2013-10-12 23:59:50 +00:00
if(typeof data.name === "string") {
2013-05-21 16:17:01 +00:00
this.unbanName(actor, data.name);
}
}
Channel.prototype.search = function(query, callback) {
2013-08-17 20:47:11 +00:00
var self = this;
if(!self.registered) {
callback([]);
return;
}
2013-08-17 20:47:11 +00:00
self.server.db.searchLibrary(self.name, query, function (err, res) {
if(err) {
res = [];
}
2013-08-18 17:21:34 +00:00
res.sort(function(a, b) {
2013-08-17 20:47:11 +00:00
var x = a.title.toLowerCase();
var y = b.title.toLowerCase();
2013-02-16 05:02:42 +00:00
2013-08-17 20:47:11 +00:00
return (x == y) ? 0 : (x < y ? -1 : 1);
});
res.forEach(function (r) {
r.duration = $util.formatTime(r.seconds);
});
2013-08-18 17:21:34 +00:00
callback(res);
2013-02-16 05:02:42 +00:00
});
}
2013-04-03 17:47:41 +00:00
/* REGION User interaction */
2013-02-16 05:02:42 +00:00
Channel.prototype.userJoin = function(user) {
var self = this;
2013-04-29 23:59:51 +00:00
var parts = user.ip.split(".");
var slash24 = parts[0] + "." + parts[1] + "." + parts[2];
2013-04-03 17:47:41 +00:00
// GTFO
2013-04-29 23:59:51 +00:00
if((user.ip in this.ipbans && this.ipbans[user.ip] != null) ||
(slash24 in this.ipbans && this.ipbans[slash24] != null)) {
2013-04-03 17:47:41 +00:00
this.logger.log("--- Kicking " + user.ip + " - banned");
2013-04-14 17:38:00 +00:00
this.kick(user, "You're banned!");
2013-03-24 03:01:37 +00:00
return;
2013-03-24 01:08:35 +00:00
}
2013-05-21 16:17:01 +00:00
if(user.name && user.name.toLowerCase() in this.namebans &&
this.namebans[user.name.toLowerCase()] != null) {
this.kick(user, "You're banned!");
return;
}
2013-03-24 01:08:35 +00:00
2013-04-03 17:47:41 +00:00
// Join the socket pool for this channel
user.socket.join(this.name);
2013-04-03 17:47:41 +00:00
2013-03-17 14:39:22 +00:00
// Prevent duplicate login
if(user.name != "") {
for(var i = 0; i < this.users.length; i++) {
2013-05-19 21:23:35 +00:00
if(this.users[i].name.toLowerCase() == user.name.toLowerCase()) {
2013-09-07 20:44:57 +00:00
if (this.users[i] == user) {
Logger.errlog.log("Wat: userJoin() called on user "+
"already in the channel");
break;
}
2013-06-04 22:33:51 +00:00
this.kick(this.users[i], "Duplicate login");
2013-03-17 14:39:22 +00:00
}
}
}
2013-04-03 17:47:41 +00:00
2013-02-16 05:02:42 +00:00
this.users.push(user);
this.broadcastVoteskipUpdate();
2013-02-16 05:02:42 +00:00
if(user.name != "") {
self.getRank(user.name, function (err, rank) {
if (self.dead)
return;
if(err) {
user.rank = user.global_rank;
user.saverank = false;
} else {
user.saverank = true;
user.rank = rank;
}
user.socket.emit("rank", rank);
self.broadcastNewUser(user);
});
2013-02-16 05:02:42 +00:00
}
2013-04-03 17:47:41 +00:00
this.broadcastUsercount();
2013-02-16 05:02:42 +00:00
// Set the new guy up
this.sendPlaylist(user);
2013-04-03 20:18:35 +00:00
this.sendMediaUpdate(user);
2013-06-11 15:29:21 +00:00
user.socket.emit("setPlaylistLocked", {locked: !this.openqueue});
2013-02-16 05:02:42 +00:00
this.sendUserlist(user);
this.sendRecentChat(user);
2013-05-15 15:34:27 +00:00
user.socket.emit("channelCSSJS", {css: this.css, js: this.js});
2013-03-16 21:49:58 +00:00
if(this.poll) {
user.socket.emit("newPoll", this.poll.packUpdate());
2013-03-16 21:49:58 +00:00
}
user.socket.emit("channelOpts", this.opts);
2013-05-22 19:38:16 +00:00
user.socket.emit("setPermissions", this.permissions);
2013-06-12 17:07:58 +00:00
user.socket.emit("setMotd", this.motd);
2013-06-11 15:29:21 +00:00
user.socket.emit("drinkCount", this.drinks);
2013-04-03 17:47:41 +00:00
2013-08-07 03:21:54 +00:00
this.logger.log("+++ " + user.ip + " joined");
Logger.syslog.log(user.ip + " joined channel " + this.name);
2013-02-16 05:02:42 +00:00
}
Channel.prototype.userLeave = function(user) {
2013-11-06 04:45:11 +00:00
user.channel = null;
2013-04-03 17:47:41 +00:00
// Their socket might already be dead, so wrap in a try-catch
try {
user.socket.leave(this.name);
}
catch(e) {}
2013-04-03 17:47:41 +00:00
// Undo vote for people who leave
2013-03-16 21:49:58 +00:00
if(this.poll) {
this.poll.unvote(user.ip);
this.broadcastPollUpdate();
}
2013-04-03 17:47:41 +00:00
if(this.voteskip) {
this.voteskip.unvote(user.ip);
}
// If they were leading, return control to the server
2013-03-16 21:49:58 +00:00
if(this.leader == user) {
this.changeLeader("");
}
2013-04-03 17:47:41 +00:00
// Remove the user from the client list for this channel
2013-03-24 02:05:13 +00:00
var idx = this.users.indexOf(user);
if(idx >= 0 && idx < this.users.length)
this.users.splice(idx, 1);
2013-08-01 13:39:10 +00:00
this.checkVoteskipPass();
2013-04-03 17:47:41 +00:00
this.broadcastUsercount();
2013-02-16 05:02:42 +00:00
if(user.name != "") {
this.sendAll("userLeave", {
2013-02-16 05:02:42 +00:00
name: user.name
});
}
2013-08-07 03:21:54 +00:00
this.logger.log("--- " + user.ip + " (" + user.name + ") left");
2013-05-03 18:15:05 +00:00
if(this.users.length == 0) {
this.logger.log("*** Channel empty, unloading");
2013-07-16 15:46:09 +00:00
this.server.unloadChannel(this);
2013-05-03 18:15:05 +00:00
}
2013-02-16 05:02:42 +00:00
}
2013-04-14 17:38:00 +00:00
Channel.prototype.kick = function(user, reason) {
user.socket.emit("kick", {
reason: reason
});
user.socket.disconnect(true);
user.channel = null;
2013-04-14 17:38:00 +00:00
}
2013-05-21 16:17:01 +00:00
Channel.prototype.hideIP = function(ip) {
var chars = new Array(15);
for(var i = 0; i < ip.length; i++) {
chars[i] = String.fromCharCode(ip.charCodeAt(i) ^ this.ipkey.charCodeAt(i));
}
return chars.join("");
}
2013-06-18 03:57:29 +00:00
Channel.prototype.sendLoginHistory = function(user) {
if(user.rank < 2)
return;
user.socket.emit("recentLogins", this.login_hist);
}
Channel.prototype.sendBanlist = function(user) {
2013-05-22 19:38:16 +00:00
if(this.hasPermission(user, "ban")) {
2013-04-03 17:47:41 +00:00
var ents = [];
for(var ip in this.ipbans) {
if(this.ipbans[ip] != null) {
2013-06-18 03:57:29 +00:00
var name = this.ipbans[ip][0];
var ip_hidden = this.hideIP(ip);
2013-05-21 16:17:01 +00:00
var disp = ip;
2013-10-11 21:31:40 +00:00
if(user.rank < 255) {
2013-08-18 22:58:16 +00:00
disp = $util.maskIP(ip);
2013-05-21 16:17:01 +00:00
}
2013-04-03 17:47:41 +00:00
ents.push({
2013-06-18 03:57:29 +00:00
ip_displayed: disp,
ip_hidden: ip_hidden,
2013-05-19 21:23:35 +00:00
name: name,
2013-06-18 03:57:29 +00:00
aliases: this.ip_alias[ip] || [],
2013-04-03 17:47:41 +00:00
banner: this.ipbans[ip][1]
});
2013-03-25 19:39:03 +00:00
}
2013-04-03 17:47:41 +00:00
}
2013-05-21 16:17:01 +00:00
for(var name in this.namebans) {
if(this.namebans[name] != null) {
ents.push({
2013-06-18 03:57:29 +00:00
ip_displayed: "*",
ip_hidden: false,
2013-05-21 16:17:01 +00:00
name: name,
2013-06-18 03:57:29 +00:00
aliases: this.name_alias[name] || [],
2013-05-21 16:17:01 +00:00
banner: this.namebans[name]
});
}
}
2013-06-18 03:57:29 +00:00
user.socket.emit("banlist", ents);
2013-02-16 05:02:42 +00:00
}
}
2013-06-18 14:57:53 +00:00
Channel.prototype.sendChatFilters = function(user) {
2013-05-22 19:38:16 +00:00
if(this.hasPermission(user, "filteredit")) {
2013-04-03 17:47:41 +00:00
var filts = new Array(this.filters.length);
for(var i = 0; i < this.filters.length; i++) {
2013-04-29 17:29:31 +00:00
filts[i] = this.filters[i].pack();
2013-04-03 17:47:41 +00:00
}
2013-06-18 14:57:53 +00:00
user.socket.emit("chatFilters", filts);
2013-03-16 22:17:36 +00:00
}
2013-06-18 14:57:53 +00:00
}
2013-06-18 14:46:28 +00:00
Channel.prototype.sendChannelRanks = function(user) {
2013-10-11 21:31:40 +00:00
if(user.rank >= 3 && this.registered) {
2013-08-17 20:47:11 +00:00
this.server.db.listChannelRanks(this.name, function (err, res) {
if(err) {
user.socket.emit("errorMsg", {
msg: "Internal error: " + err
});
return;
}
user.socket.emit("channelRanks", res);
});
}
2013-04-03 17:47:41 +00:00
}
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
Channel.prototype.sendPlaylist = function(user) {
2013-07-02 15:38:13 +00:00
user.socket.emit("playlist", this.playlist.items.toArray());
2013-07-01 22:45:55 +00:00
if(this.playlist.current)
user.socket.emit("setCurrent", this.playlist.current.uid);
2013-06-11 15:29:21 +00:00
user.socket.emit("setPlaylistMeta", this.plmeta);
2013-04-03 17:47:41 +00:00
}
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
Channel.prototype.sendMediaUpdate = function(user) {
2013-06-29 22:09:20 +00:00
if(this.playlist.current != null) {
user.socket.emit("changeMedia", this.playlist.current.media.fullupdate());
2013-02-16 05:02:42 +00:00
}
2013-04-03 17:47:41 +00:00
}
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
Channel.prototype.sendUserlist = function(user) {
var users = [];
for(var i = 0; i < this.users.length; i++) {
// Skip people who haven't logged in
if(this.users[i].name != "") {
users.push({
name: this.users[i].name,
rank: this.users[i].rank,
meta: this.users[i].meta,
profile: this.users[i].profile
2013-04-03 17:47:41 +00:00
});
}
2013-03-23 18:17:39 +00:00
}
2013-04-03 17:47:41 +00:00
user.socket.emit("userlist", users);
2013-11-09 04:12:17 +00:00
if (this.leader !== null) {
user.socket.emit("setLeader", this.leader.name);
}
2013-02-16 05:02:42 +00:00
}
2013-04-03 17:47:41 +00:00
// Send the last 15 messages for context
Channel.prototype.sendRecentChat = function(user) {
for(var i = 0; i < this.chatbuffer.length; i++) {
user.socket.emit("chatMsg", this.chatbuffer[i]);
2013-02-16 05:02:42 +00:00
}
}
2013-04-03 17:47:41 +00:00
/* REGION Broadcasts to all clients */
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
Channel.prototype.sendAll = function(message, data) {
if(this.name == "")
return;
2013-07-16 03:01:12 +00:00
this.server.io.sockets.in(this.name).emit(message, data);
2013-09-10 21:45:43 +00:00
if (this.server.cfg["enable-ssl"])
2013-10-09 23:10:26 +00:00
this.server.ioSecure.sockets.in(this.name).emit(message, data);
2013-02-16 05:02:42 +00:00
}
2013-10-11 21:31:40 +00:00
Channel.prototype.sendAllWithRank = function(rank, msg, data) {
for(var i = 0; i < this.users.length; i++) {
2013-10-11 21:31:40 +00:00
if(this.users[i].rank >= rank) {
this.users[i].socket.emit(msg, data);
}
}
}
2013-11-09 18:33:18 +00:00
Channel.prototype.sendAllExcept = function(user, msg, data) {
for(var i = 0; i < this.users.length; i++) {
if (this.users[i] !== user) {
this.users[i].socket.emit(msg, data);
}
}
}
Channel.prototype.broadcastPlaylistMeta = function() {
var total = 0;
2013-07-02 15:38:13 +00:00
var iter = this.playlist.items.first;
while(iter !== null) {
if(iter.media !== null)
total += iter.media.seconds;
2013-06-29 22:09:20 +00:00
iter = iter.next;
}
var timestr = $util.formatTime(total);
var packet = {
2013-07-02 15:38:13 +00:00
count: this.playlist.items.length,
time: timestr
};
this.plmeta = packet;
2013-06-11 15:29:21 +00:00
this.sendAll("setPlaylistMeta", packet);
}
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastUsercount = function() {
2013-06-11 15:29:21 +00:00
this.sendAll("usercount", this.users.length);
2013-04-03 17:47:41 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastNewUser = function(user) {
2013-08-17 20:47:11 +00:00
var self = this;
2013-08-18 19:21:42 +00:00
// If the channel is empty and isn't registered, the first person
// gets ownership of the channel (temporarily)
if(self.dbloaded && self.users.length == 1 && !self.registered) {
2013-10-04 03:07:44 +00:00
user.rank = (user.rank < 10) ? 10 : user.rank;
2013-08-18 19:21:42 +00:00
user.socket.emit("channelNotRegistered");
2013-10-04 03:07:44 +00:00
user.socket.emit("rank", user.rank);
2013-08-18 19:21:42 +00:00
}
2013-08-17 20:47:11 +00:00
self.server.db.listAliases(user.ip, function (err, aliases) {
if (self.dead)
return;
2013-08-17 20:47:11 +00:00
if(err) {
aliases = [];
}
2013-06-18 03:57:29 +00:00
2013-08-17 20:47:11 +00:00
self.ip_alias[user.ip] = aliases;
aliases.forEach(function (alias) {
2013-08-18 17:21:34 +00:00
self.name_alias[alias] = aliases;
2013-08-17 20:47:11 +00:00
});
2013-06-18 03:57:29 +00:00
2013-08-17 20:47:11 +00:00
self.login_hist.unshift({
name: user.name,
aliases: self.ip_alias[user.ip],
time: Date.now()
});
if(self.login_hist.length > 20)
self.login_hist.pop();
if(user.name.toLowerCase() in self.namebans &&
self.namebans[user.name.toLowerCase()] !== null) {
self.kick(user, "You're banned!");
return;
}
2013-11-09 18:33:18 +00:00
if (self.mutedUsers.contains(user.name.toLowerCase()) ||
self.mutedUsers.contains("[shadow]"+user.name.toLowerCase())) {
2013-10-16 22:36:05 +00:00
user.meta.icon = "icon-volume-off";
}
2013-11-09 18:33:18 +00:00
var pkt = {
2013-08-17 20:47:11 +00:00
name: user.name,
rank: user.rank,
meta: user.meta,
profile: user.profile
2013-11-09 18:33:18 +00:00
};
if (self.mutedUsers.contains("[shadow]"+user.name.toLowerCase())) {
self.sendAllExcept(user, "addUser", pkt);
pkt.meta.icon = false;
user.socket.emit("addUser", pkt);
} else {
self.sendAll("addUser", pkt);
}
2013-08-17 20:47:11 +00:00
2013-10-11 21:31:40 +00:00
if(user.rank > 0) {
self.saveInitialRank(user);
2013-08-17 20:47:11 +00:00
}
var msg = user.name + " joined (aliases: ";
msg += self.ip_alias[user.ip].join(", ") + ")";
var pkt = {
username: "[server]",
msg: msg,
2013-11-17 21:32:19 +00:00
meta: { addClass: "server-whisper" },
2013-08-17 20:47:11 +00:00
time: Date.now()
};
2013-11-09 18:33:18 +00:00
self.sendAllWithRank(2, "joinMessage", pkt);
2013-04-03 17:47:41 +00:00
});
2013-03-16 20:39:58 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastPoll = function() {
2013-09-12 01:22:00 +00:00
var self = this;
var unhidden = this.poll.packUpdate(true);
var hidden = this.poll.packUpdate(false);
2013-09-27 15:28:48 +00:00
2013-09-12 01:22:00 +00:00
this.users.forEach(function (u) {
if (self.hasPermission(u, "viewhiddenpoll"))
u.socket.emit("newPoll", unhidden);
else
u.socket.emit("newPoll", hidden);
});
2013-02-16 05:02:42 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastPollUpdate = function() {
2013-09-12 01:22:00 +00:00
var self = this;
var unhidden = this.poll.packUpdate(true);
var hidden = this.poll.packUpdate(false);
2013-09-27 15:28:48 +00:00
2013-09-12 01:22:00 +00:00
this.users.forEach(function (u) {
if (self.hasPermission(u, "viewhiddenpoll"))
u.socket.emit("updatePoll", unhidden);
else
u.socket.emit("updatePoll", hidden);
});
2013-04-03 17:47:41 +00:00
}
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastPollClose = function() {
this.sendAll("closePoll");
}
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastOpts = function() {
this.sendAll("channelOpts", this.opts);
2013-02-16 05:02:42 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastBanlist = function() {
var ents = [];
2013-05-21 16:17:01 +00:00
var adminents = [];
2013-04-03 17:47:41 +00:00
for(var ip in this.ipbans) {
if(this.ipbans[ip] != null) {
2013-06-18 03:57:29 +00:00
var name = this.ipbans[ip][0];
var ip_hidden = this.hideIP(ip);
2013-04-03 17:47:41 +00:00
ents.push({
2013-08-18 22:58:16 +00:00
ip_displayed: $util.maskIP(ip),
2013-06-18 03:57:29 +00:00
ip_hidden: ip_hidden,
2013-05-21 16:17:01 +00:00
name: name,
2013-06-18 03:57:29 +00:00
aliases: this.ip_alias[ip] || [],
2013-05-21 16:17:01 +00:00
banner: this.ipbans[ip][1]
});
adminents.push({
2013-06-18 03:57:29 +00:00
ip_displayed: ip,
ip_hidden: ip_hidden,
2013-05-19 21:23:35 +00:00
name: name,
2013-06-18 03:57:29 +00:00
aliases: this.ip_alias[ip] || [],
2013-04-03 17:47:41 +00:00
banner: this.ipbans[ip][1]
});
}
}
2013-05-21 16:17:01 +00:00
for(var name in this.namebans) {
if(this.namebans[name] != null) {
ents.push({
2013-06-18 03:57:29 +00:00
ip_displayed: "*",
ip_hidden: false,
2013-05-21 16:17:01 +00:00
name: name,
2013-06-18 03:57:29 +00:00
aliases: this.name_alias[name] || [],
2013-05-21 16:17:01 +00:00
banner: this.namebans[name]
});
adminents.push({
2013-06-18 03:57:29 +00:00
ip_displayed: "*",
ip_hidden: false,
2013-05-21 16:17:01 +00:00
name: name,
2013-06-18 03:57:29 +00:00
aliases: this.name_alias[name] || [],
2013-05-21 16:17:01 +00:00
banner: this.namebans[name]
});
}
}
2013-04-03 17:47:41 +00:00
for(var i = 0; i < this.users.length; i++) {
2013-05-22 19:38:16 +00:00
if(this.hasPermission(this.users[i], "ban")) {
2013-10-11 21:31:40 +00:00
if(this.users[i].rank >= 255) {
2013-06-18 03:57:29 +00:00
this.users[i].socket.emit("banlist", adminents);
2013-05-21 16:17:01 +00:00
}
else {
2013-06-18 03:57:29 +00:00
this.users[i].socket.emit("banlist", ents);
2013-05-21 16:17:01 +00:00
}
2013-04-03 17:47:41 +00:00
}
}
}
Channel.prototype.broadcastChatFilters = function() {
var filts = new Array(this.filters.length);
for(var i = 0; i < this.filters.length; i++) {
2013-04-29 17:29:31 +00:00
filts[i] = this.filters[i].pack();
2013-04-03 17:47:41 +00:00
}
for(var i = 0; i < this.users.length; i++) {
2013-05-22 19:38:16 +00:00
if(this.hasPermission(this.users[i], "filteredit")) {
2013-06-18 14:57:53 +00:00
this.users[i].socket.emit("chatFilters", filts);
2013-04-03 17:47:41 +00:00
}
}
}
2013-09-12 03:16:56 +00:00
Channel.prototype.calcVoteskipMax = function () {
var self = this;
// good ol' map-reduce
return self.users.map(function (u) {
if (!self.hasPermission(u, "voteskip"))
return 0;
return u.meta.afk ? 0 : 1;
}).reduce(function (a, b) {
return a + b;
2013-09-12 21:26:45 +00:00
}, 0);
2013-09-12 03:16:56 +00:00
};
Channel.prototype.broadcastVoteskipUpdate = function() {
var amt = this.voteskip ? this.voteskip.counts[0] : 0;
2013-09-12 03:16:56 +00:00
var count = this.calcVoteskipMax();
var need = this.voteskip ? Math.ceil(count * this.opts.voteskip_ratio) : 0;
for(var i = 0; i < this.users.length; i++) {
2013-10-11 21:31:40 +00:00
if(this.users[i].rank >= 1.5) {
this.users[i].socket.emit("voteskip", {
count: amt,
need: need
});
}
}
}
2013-04-03 17:47:41 +00:00
Channel.prototype.broadcastMotd = function() {
2013-06-11 19:41:03 +00:00
this.sendAll("setMotd", this.motd);
2013-04-03 17:47:41 +00:00
}
2013-04-04 19:56:43 +00:00
Channel.prototype.broadcastDrinks = function() {
2013-06-11 15:29:21 +00:00
this.sendAll("drinkCount", this.drinks);
2013-04-04 19:56:43 +00:00
}
2013-04-03 17:47:41 +00:00
/* REGION Playlist Stuff */
2013-07-06 03:34:04 +00:00
Channel.prototype.onVideoChange = function () {
this.voteskip = false;
this.broadcastVoteskipUpdate();
this.drinks = 0;
this.broadcastDrinks();
2013-07-06 03:34:04 +00:00
}
2013-04-28 22:06:58 +00:00
function isLive(type) {
2013-06-05 02:47:02 +00:00
return type == "li" // Livestream.com
|| type == "tw" // Twitch.tv
|| type == "jt" // Justin.tv
|| type == "rt" // RTMP
|| type == "jw" // JWPlayer
|| type == "us" // Ustream.tv
|| type == "im" // Imgur album
|| type == "cu";// Custom Embed
2013-04-28 22:06:58 +00:00
}
2013-06-29 22:09:20 +00:00
Channel.prototype.queueAdd = function(item, after) {
2013-07-01 22:45:55 +00:00
var chan = this;
function afterAdd() {
chan.sendAll("queue", {
item: item.pack(),
after: after
});
chan.broadcastPlaylistMeta();
}
2013-06-29 22:09:20 +00:00
if(after === "prepend")
2013-07-01 22:45:55 +00:00
this.playlist.prepend(item, afterAdd);
2013-06-29 22:09:20 +00:00
else if(after === "append")
2013-07-01 22:45:55 +00:00
this.playlist.append(item, afterAdd);
2013-06-29 22:09:20 +00:00
else
2013-07-01 22:45:55 +00:00
this.playlist.insertAfter(item, after, afterAdd);
2013-05-02 15:34:12 +00:00
}
2013-06-29 22:09:20 +00:00
Channel.prototype.autoTemp = function(item, user) {
if(isLive(item.media.type)) {
item.temp = true;
2013-05-04 22:54:28 +00:00
}
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "addnontemp")) {
2013-06-29 22:09:20 +00:00
item.temp = true;
2013-05-04 22:54:28 +00:00
}
}
2013-04-03 17:47:41 +00:00
Channel.prototype.tryQueue = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "playlistadd")) {
2013-04-03 17:47:41 +00:00
return;
}
if(typeof data.pos !== "string") {
2013-04-03 17:47:41 +00:00
return;
}
if(typeof data.id !== "string" && data.id !== false) {
return;
}
2013-04-03 17:47:41 +00:00
if (data.pos === "next" && !this.hasPermission(user, "playlistnext")) {
2013-04-03 17:47:41 +00:00
return;
}
2013-04-27 17:13:01 +00:00
2013-08-29 00:25:53 +00:00
var limit = {
burst: 3,
sustained: 1
};
2013-10-11 21:31:40 +00:00
if (user.rank >= 2 || this.leader == user) {
2013-08-29 00:25:53 +00:00
limit = {
burst: 10,
sustained: 2
};
}
if (user.queueLimiter.throttle(limit)) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: "You are adding videos too quickly",
link: null
});
2013-08-26 15:36:47 +00:00
return;
2013-04-24 17:45:17 +00:00
}
if (typeof data.title !== "string" || data.type !== "cu")
data.title = false;
2013-07-09 16:38:48 +00:00
data.queueby = user ? user.name : "";
data.temp = !this.hasPermission(user, "addnontemp");
2013-07-09 16:38:48 +00:00
if (data.list) {
2013-09-30 14:51:38 +00:00
if (data.pos === "next") {
data.list.reverse();
if (this.playlist.items.length === 0)
data.list.unshift(data.list.pop());
}
var i = 0;
var self = this;
var next = function () {
if (self.dead)
return;
if (i < data.list.length) {
data.list[i].pos = data.pos;
self.tryQueue(user, data.list[i]);
i++;
setTimeout(next, 2000);
}
};
next();
} else {
2013-07-02 15:38:13 +00:00
this.addMedia(data, user);
}
2013-07-02 15:38:13 +00:00
}
2013-07-03 21:29:49 +00:00
Channel.prototype.addMedia = function(data, user) {
2013-08-17 20:47:11 +00:00
var self = this;
if(data.type === "yp" &&
2013-08-17 20:47:11 +00:00
!self.hasPermission(user, "playlistaddlist")) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: "You don't have permission to add " +
"playlists",
link: $util.formatLink(data.id, data.type)
});
2013-07-16 15:46:09 +00:00
return;
}
2013-08-17 20:47:11 +00:00
if(data.type === "cu" &&
!self.hasPermission(user, "playlistaddcustom")) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: "You don't have permission to add " +
"custom embeds",
link: null
});
return;
}
if(isLive(data.type) &&
!self.hasPermission(user, "playlistaddlive")) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: "You don't have " +
"permission to add livestreams",
link: $util.formatLink(data.id, data.type)
});
return;
}
data.temp = data.temp || isLive(data.type);
data.queueby = user ? user.name : "";
2013-08-17 20:47:11 +00:00
data.maxlength = self.hasPermission(user, "exceedmaxlength")
? 0
: this.opts.maxlength;
2013-09-30 00:53:27 +00:00
if (data.pos === "end")
data.pos = "append";
2013-09-30 00:53:27 +00:00
if (data.type === "cu" && data.title) {
var t = data.title;
if(t.length > 100)
t = t.substring(0, 97) + "...";
data.title = t;
}
var afterData = function (q, c, m) {
2013-10-02 14:37:13 +00:00
if (data.maxlength && m.seconds > data.maxlength) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: "Media is too long!",
link: $util.formatLink(m.id, m.type)
});
2013-09-30 00:53:27 +00:00
q.release();
return;
2013-09-30 00:53:27 +00:00
}
2013-09-30 00:53:27 +00:00
m.pos = data.pos;
m.queueby = data.queueby;
m.temp = data.temp;
var res = self.playlist.addMedia(m);
if (res.error) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: res.error,
link: $util.formatLink(m.id, m.type)
});
2013-09-30 00:53:27 +00:00
q.release();
return;
2013-08-28 15:32:35 +00:00
}
2013-09-30 00:53:27 +00:00
2013-11-11 04:26:30 +00:00
if (m.restricted) {
user.socket.emit("queueWarn", {
msg: "This video is blocked in the following countries: " +
m.restricted,
link: $util.formatLink(m.id, m.type)
});
}
2013-09-30 00:53:27 +00:00
var item = res.item;
self.logger.log("### " + user.name + " queued " +
item.media.title);
self.sendAll("queue", {
item: item.pack(),
after: item.prev ? item.prev.uid : "prepend"
});
self.broadcastPlaylistMeta();
2013-09-30 00:53:27 +00:00
if (!c && !item.temp)
self.cacheMedia(item.media);
2013-09-30 00:53:27 +00:00
q.release();
};
2013-10-04 03:07:44 +00:00
// Pre-cached data (e.g. from a playlist)
if (typeof data.title === "string" && data.type !== "cu") {
self.plqueue.queue(function (q) {
var m = new Media(data.id, data.title, data.seconds, data.type);
afterData.bind(self, q, false)(m);
});
return;
}
// special case for youtube playlists
if (data.type === "yp") {
self.plqueue.queue(function (q) {
if (self.dead)
return;
2013-10-11 21:31:40 +00:00
InfoGetter.getMedia(data.id, data.type,
function (e, vids) {
if (e) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: e,
link: $util.formatLink(data.id, data.type)
});
q.release();
return;
}
if (data.pos === "next") {
vids.reverse();
if (self.playlist.length === 0)
vids.unshift(vids.pop());
}
var fake = { release: function () { } };
var cb = afterData.bind(self, fake, false);
for (var i = 0; i < vids.length; i++) {
cb(vids[i]);
}
q.release();
});
});
return;
}
2013-09-30 00:53:27 +00:00
// Don't check library for livestreams or if the channel is
// unregistered
if (!self.registered || isLive(data.type)) {
self.plqueue.queue(function (q) {
if (self.dead)
return;
2013-09-30 00:53:27 +00:00
var cb = afterData.bind(self, q, false);
2013-10-11 21:31:40 +00:00
InfoGetter.getMedia(data.id, data.type,
function (e, m) {
2013-09-30 00:53:27 +00:00
if (self.dead)
return;
if (e) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: e,
link: $util.formatLink(data.id, data.type)
});
2013-09-30 00:53:27 +00:00
q.release();
return;
}
2013-09-30 00:53:27 +00:00
cb(m);
});
});
2013-09-30 00:53:27 +00:00
} else {
self.plqueue.queue(function (q) {
if (self.dead)
return;
self.server.db.getLibraryItem(self.name, data.id,
function (err, item) {
if (self.dead)
return;
if (err) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: "Internal error: " + err,
link: $util.formatLink(data.id, data.type)
2013-10-06 06:43:25 +00:00
});
return;
2013-08-17 20:47:11 +00:00
}
if (item !== null) {
if (data.maxlength && item.seconds > data.maxlength) {
user.socket.emit("queueFail", {
msg: "Media is too long!",
link: $util.formatLink(item.id, item.type)
});
2013-09-30 00:53:27 +00:00
return;
}
2013-09-30 00:53:27 +00:00
afterData.bind(self, q, true)(item);
} else {
2013-10-11 21:31:40 +00:00
InfoGetter.getMedia(data.id, data.type,
function (e, m) {
2013-09-30 00:53:27 +00:00
if (self.dead)
return;
if (e) {
2013-10-06 06:43:25 +00:00
user.socket.emit("queueFail", {
msg: e,
link: $util.formatLink(data.id, data.type)
});
2013-09-30 00:53:27 +00:00
q.release();
return;
}
2013-08-17 20:47:11 +00:00
2013-09-30 00:53:27 +00:00
afterData.bind(self, q, false)(m);
});
}
});
2013-09-30 00:53:27 +00:00
});
}
};
2013-06-01 19:42:08 +00:00
Channel.prototype.tryQueuePlaylist = function(user, data) {
2013-08-17 20:47:11 +00:00
var self = this;
if (!self.hasPermission(user, "playlistaddlist")) {
return;
}
if(typeof data.name !== "string" ||
typeof data.pos !== "string") {
return;
}
if (data.pos == "next" && !this.hasPermission(user, "playlistnext")) {
return;
}
2013-08-17 20:47:11 +00:00
self.server.db.getUserPlaylist(user.name, data.name,
function (err, pl) {
if (self.dead)
return;
if (err) {
2013-08-17 20:47:11 +00:00
user.socket.emit("errorMsg", {
msg: "Playlist load failed: " + err
});
return;
}
2013-10-12 20:54:39 +00:00
try {
if (data.pos === "next") {
pl.reverse();
if (pl.length > 0 && self.playlist.items.length === 0)
pl.unshift(pl.pop());
}
2013-10-12 20:54:39 +00:00
for (var i = 0; i < pl.length; i++) {
pl[i].pos = data.pos;
pl[i].temp = !self.hasPermission(user, "addnontemp");
self.addMedia(pl[i], user);
}
} catch (e) {
Logger.errlog.log("Loading user playlist failed!");
Logger.errlog.log("PL: " + user.name + "-" + data.name);
Logger.errlog.log(e.stack);
}
2013-08-17 20:47:11 +00:00
});
}
2013-06-29 22:09:20 +00:00
Channel.prototype.setTemp = function(uid, temp) {
2013-07-02 15:38:13 +00:00
var item = this.playlist.items.find(uid);
2013-06-29 22:09:20 +00:00
if(!item)
return;
2013-06-29 22:09:20 +00:00
item.temp = temp;
2013-05-04 22:54:28 +00:00
this.sendAll("setTemp", {
2013-06-29 22:09:20 +00:00
uid: uid,
2013-05-04 22:54:28 +00:00
temp: temp
});
if(!temp) {
2013-07-01 22:45:55 +00:00
this.cacheMedia(item.media);
2013-05-04 22:54:28 +00:00
}
}
Channel.prototype.trySetTemp = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "settemp")) {
2013-05-04 22:54:28 +00:00
return;
}
2013-06-29 22:09:20 +00:00
if(typeof data.uid != "number" || typeof data.temp != "boolean") {
2013-05-04 22:54:28 +00:00
return;
}
2013-06-29 22:09:20 +00:00
this.setTemp(data.uid, data.temp);
2013-05-04 22:54:28 +00:00
}
2013-06-11 15:29:21 +00:00
2013-07-01 22:45:55 +00:00
Channel.prototype.dequeue = function(uid) {
var self = this;
self.plqueue.queue(function (q) {
if (self.dead)
return;
if (self.playlist.remove(uid)) {
self.sendAll("delete", {
uid: uid
});
self.broadcastPlaylistMeta();
}
q.release();
});
2013-04-03 17:47:41 +00:00
}
Channel.prototype.tryDequeue = function(user, data) {
2013-08-07 03:21:54 +00:00
if(!this.hasPermission(user, "playlistdelete"))
2013-04-03 17:47:41 +00:00
return;
2013-10-12 23:59:50 +00:00
if(typeof data !== "number") {
2013-08-07 03:21:54 +00:00
return;
2013-10-12 23:59:50 +00:00
}
2013-08-07 03:42:32 +00:00
var plitem = this.playlist.items.find(data);
2013-08-07 03:21:54 +00:00
if(plitem && plitem.media)
this.logger.log("### " + user.name + " deleted " + plitem.media.title);
this.dequeue(data);
2013-04-03 17:47:41 +00:00
}
Channel.prototype.tryUncache = function(user, data) {
2013-08-18 23:03:49 +00:00
var self = this;
2013-10-11 21:31:40 +00:00
if(user.rank < 2) {
return;
}
if(typeof data.id != "string") {
return;
}
2013-10-07 15:04:08 +00:00
if (!self.registered)
return;
2013-08-17 20:47:11 +00:00
self.server.db.removeFromLibrary(self.name, data.id,
function (err, res) {
if (self.dead)
return;
2013-08-17 20:47:11 +00:00
if(err)
return;
self.logger.log("*** " + user.name + " deleted " + data.id +
2013-08-17 20:47:11 +00:00
" from library");
});
}
2013-04-03 17:47:41 +00:00
Channel.prototype.playNext = function() {
2013-06-29 22:09:20 +00:00
this.playlist.next();
2013-04-03 17:47:41 +00:00
}
Channel.prototype.tryPlayNext = function(user) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "playlistjump")) {
2013-04-03 17:47:41 +00:00
return;
2013-07-01 22:45:55 +00:00
}
2013-04-03 17:47:41 +00:00
2013-08-07 03:21:54 +00:00
this.logger.log("### " + user.name + " skipped the video");
2013-07-01 22:45:55 +00:00
this.playNext();
2013-04-03 17:47:41 +00:00
}
2013-06-29 22:09:20 +00:00
Channel.prototype.jumpTo = function(uid) {
2013-07-01 22:45:55 +00:00
return this.playlist.jump(uid);
2013-04-03 17:47:41 +00:00
}
Channel.prototype.tryJumpTo = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "playlistjump")) {
2013-04-03 17:47:41 +00:00
return;
}
2013-06-29 22:09:20 +00:00
if(typeof data !== "number") {
2013-04-03 17:47:41 +00:00
return;
}
2013-08-07 03:21:54 +00:00
this.logger.log("### " + user.name + " skipped the video");
2013-06-11 15:29:21 +00:00
this.jumpTo(data);
2013-04-03 17:47:41 +00:00
}
2013-04-22 20:37:42 +00:00
Channel.prototype.clearqueue = function() {
2013-06-29 22:09:20 +00:00
this.playlist.clear();
this.plqueue.reset();
2013-07-02 15:38:13 +00:00
this.sendAll("playlist", this.playlist.items.toArray());
this.broadcastPlaylistMeta();
2013-04-22 20:37:42 +00:00
}
Channel.prototype.tryClearqueue = function(user) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "playlistclear")) {
2013-04-22 20:37:42 +00:00
return;
}
2013-08-07 03:21:54 +00:00
this.logger.log("### " + user.name + " cleared the playlist");
2013-04-22 20:37:42 +00:00
this.clearqueue();
}
Channel.prototype.shufflequeue = function() {
var n = [];
2013-07-03 21:29:49 +00:00
var pl = this.playlist.items.toArray(false);
this.playlist.clear();
this.plqueue.reset();
2013-06-29 22:09:20 +00:00
while(pl.length > 0) {
var i = parseInt(Math.random() * pl.length);
2013-07-03 21:29:49 +00:00
var item = this.playlist.makeItem(pl[i].media);
item.temp = pl[i].temp;
item.queueby = pl[i].queueby;
this.playlist.items.append(item);
2013-06-29 22:09:20 +00:00
pl.splice(i, 1);
}
2013-07-03 21:29:49 +00:00
this.playlist.current = this.playlist.items.first;
2013-07-02 15:38:13 +00:00
this.sendAll("playlist", this.playlist.items.toArray());
2013-06-11 19:41:03 +00:00
this.sendAll("setPlaylistMeta", this.plmeta);
2013-07-03 21:29:49 +00:00
this.playlist.startPlayback();
2013-04-22 20:37:42 +00:00
}
Channel.prototype.tryShufflequeue = function(user) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "playlistshuffle")) {
2013-04-22 20:37:42 +00:00
return;
}
2013-08-07 03:21:54 +00:00
this.logger.log("### " + user.name + " shuffled the playlist");
2013-04-22 20:37:42 +00:00
this.shufflequeue();
}
2013-04-03 17:47:41 +00:00
Channel.prototype.tryUpdate = function(user, data) {
if(this.leader != user) {
2013-10-12 23:59:50 +00:00
user.kick("Received mediaUpdate from non-leader");
2013-04-03 17:47:41 +00:00
return;
}
2013-10-12 23:59:50 +00:00
if(typeof data.id !== "string" || typeof data.currentTime !== "number") {
return;
}
2013-04-03 17:47:41 +00:00
2013-06-29 22:09:20 +00:00
if(this.playlist.current === null) {
2013-04-04 19:56:43 +00:00
return;
}
2013-06-30 19:33:38 +00:00
if(isLive(this.playlist.current.media.type)
&& this.playlist.current.media.type != "jw") {
2013-04-03 17:47:41 +00:00
return;
}
2013-06-29 22:09:20 +00:00
if(this.playlist.current.media.id != data.id) {
return;
}
2013-06-29 22:09:20 +00:00
this.playlist.current.media.currentTime = data.currentTime;
this.playlist.current.media.paused = data.paused;
this.sendAll("mediaUpdate", this.playlist.current.media.timeupdate());
2013-04-03 17:47:41 +00:00
}
2013-06-11 15:29:21 +00:00
Channel.prototype.move = function(data, user) {
var self = this;
self.plqueue.queue(function (q) {
if (self.dead)
return;
if (self.playlist.move(data.from, data.after)) {
var fromit = self.playlist.items.find(data.from);
var afterit = self.playlist.items.find(data.after);
var aftertitle = (afterit && afterit.media)
? afterit.media.title : "";
if (fromit) {
self.logger.log("### " + user.name + " moved " +
fromit.media.title +
(aftertitle ? " after " + aftertitle : ""));
}
2013-08-07 03:21:54 +00:00
self.sendAll("moveVideo", {
from: data.from,
after: data.after,
});
}
2013-06-29 22:09:20 +00:00
q.release();
});
2013-04-03 17:47:41 +00:00
}
Channel.prototype.tryMove = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "playlistmove")) {
return;
2013-05-22 19:38:16 +00:00
}
2013-04-03 17:47:41 +00:00
2013-10-12 23:59:50 +00:00
if(typeof data.from !== "number" || (typeof data.after !== "number" && typeof data.after !== "string")) {
return;
2013-10-12 23:59:50 +00:00
}
2013-04-03 17:47:41 +00:00
this.move(data, user);
2013-04-03 17:47:41 +00:00
}
/* REGION Polls */
Channel.prototype.tryOpenPoll = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "pollctl") && this.leader != user) {
return;
}
2013-10-12 23:59:50 +00:00
if(typeof data.title !== "string" || !(data.opts instanceof Array)) {
return;
}
2013-09-12 01:22:00 +00:00
var obscured = (data.obscured === true);
var poll = new Poll(user.name, data.title, data.opts, obscured);
this.poll = poll;
this.broadcastPoll();
this.logger.log("*** " + user.name + " Opened Poll: '" + poll.title + "'");
}
2013-04-03 17:47:41 +00:00
Channel.prototype.tryClosePoll = function(user) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "pollctl")) {
2013-04-03 17:47:41 +00:00
return;
}
if(this.poll) {
2013-09-12 01:22:00 +00:00
if (this.poll.obscured) {
this.poll.obscured = false;
this.broadcastPollUpdate();
}
2013-08-07 03:21:54 +00:00
this.logger.log("*** " + user.name + " closed the active poll");
2013-04-03 17:47:41 +00:00
this.poll = false;
this.broadcastPollClose();
}
}
Channel.prototype.tryVote = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "pollvote")) {
return;
}
2013-06-19 23:39:40 +00:00
if(typeof data.option !== "number") {
2013-04-03 17:47:41 +00:00
return;
}
if(this.poll) {
this.poll.vote(user.ip, data.option);
this.broadcastPollUpdate();
}
}
Channel.prototype.tryVoteskip = function(user) {
2013-04-04 16:05:01 +00:00
if(!this.opts.allow_voteskip) {
return;
}
2013-09-12 03:16:56 +00:00
if(!this.hasPermission(user, "voteskip"))
return;
2013-07-28 21:36:53 +00:00
// Voteskip = auto-unafk
user.setAFK(false);
user.autoAFK();
2013-04-03 17:47:41 +00:00
if(!this.voteskip) {
this.voteskip = new Poll("voteskip", "voteskip", ["yes"]);
}
this.voteskip.vote(user.ip, 0);
2013-08-07 03:21:54 +00:00
this.logger.log("### " + user.name + " voteskipped");
2013-08-01 13:39:10 +00:00
this.checkVoteskipPass();
}
Channel.prototype.checkVoteskipPass = function () {
if(!this.opts.allow_voteskip)
return false;
if(!this.voteskip)
return false;
2013-09-12 03:16:56 +00:00
var count = this.calcVoteskipMax();
var need = Math.ceil(count * this.opts.voteskip_ratio);
2013-08-01 13:39:10 +00:00
if(this.voteskip.counts[0] >= need)
this.playNext();
this.broadcastVoteskipUpdate();
return true;
2013-04-03 17:47:41 +00:00
}
/* REGION Channel Option stuff */
Channel.prototype.setLock = function(locked) {
this.openqueue = !locked;
2013-06-11 15:29:21 +00:00
this.sendAll("setPlaylistLocked", {locked: locked});
2013-04-03 17:47:41 +00:00
}
Channel.prototype.trySetLock = function(user, data) {
2013-10-11 21:31:40 +00:00
if(user.rank < 2) {
2013-04-03 17:47:41 +00:00
return;
}
2013-04-03 17:47:41 +00:00
if(data.locked == undefined) {
return;
}
2013-08-07 03:21:54 +00:00
this.logger.log("*** " + user.name + " set playlist lock to " + data.locked);
2013-04-03 17:47:41 +00:00
this.setLock(data.locked);
}
2013-06-11 15:29:21 +00:00
Channel.prototype.tryToggleLock = function(user) {
2013-10-11 21:31:40 +00:00
if(user.rank < 2) {
2013-06-11 15:29:21 +00:00
return;
}
2013-08-07 04:04:36 +00:00
this.logger.log("*** " + user.name + " set playlist lock to " + this.openqueue);
2013-06-11 15:29:21 +00:00
this.setLock(this.openqueue);
}
2013-06-18 15:51:42 +00:00
Channel.prototype.tryRemoveFilter = function(user, f) {
2013-10-12 23:59:50 +00:00
if(!this.hasPermission(user, "filteredit")) {
user.kick("Attempted removeFilter with insufficient permission");
return;
}
2013-06-18 15:51:42 +00:00
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " removed filter: " + f.name);
2013-06-18 15:51:42 +00:00
this.removeFilter(f);
}
Channel.prototype.removeFilter = function(filter) {
2013-04-03 17:47:41 +00:00
for(var i = 0; i < this.filters.length; i++) {
2013-06-18 15:51:42 +00:00
if(this.filters[i].name == filter.name) {
this.filters.splice(i, 1);
break;
}
}
this.broadcastChatFilters();
}
2013-07-03 21:29:49 +00:00
Channel.prototype.updateFilter = function(filter, emit) {
2013-06-18 15:51:42 +00:00
if(filter.name == "")
filter.name = filter.source;
var found = false;
for(var i = 0; i < this.filters.length; i++) {
2013-06-18 15:51:42 +00:00
if(this.filters[i].name == filter.name) {
found = true;
this.filters[i] = filter;
break;
}
}
2013-06-18 15:51:42 +00:00
if(!found) {
this.filters.push(filter);
}
2013-07-03 21:29:49 +00:00
if(emit !== false)
this.broadcastChatFilters();
}
2013-06-18 15:51:42 +00:00
Channel.prototype.tryUpdateFilter = function(user, f) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "filteredit")) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted updateFilter with insufficient permission");
return;
}
if (typeof f.source !== "string" || typeof f.flags !== "string" ||
typeof f.replace !== "string") {
2013-04-03 17:47:41 +00:00
return;
}
2013-06-18 15:51:42 +00:00
var re = f.source;
var flags = f.flags;
// Temporary fix
2013-09-12 18:03:04 +00:00
// 2013-09-12 Temporary my ass
f.replace = f.replace.replace(/style/g, "stlye");
f.replace = sanitize(f.replace).xss();
2013-09-12 18:03:04 +00:00
f.replace = f.replace.replace(/stlye/g, "style");
2013-06-18 15:51:42 +00:00
try {
new RegExp(re, flags);
}
catch(e) {
2013-04-03 17:47:41 +00:00
return;
}
2013-06-18 15:51:42 +00:00
var filter = new Filter(f.name, f.source, f.flags, f.replace);
2013-10-12 23:59:50 +00:00
filter.active = !!f.active;
filter.filterlinks = !!f.filterlinks;
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " updated filter: " + f.name);
2013-06-18 15:51:42 +00:00
this.updateFilter(filter);
}
2013-04-03 17:47:41 +00:00
2013-06-18 15:51:42 +00:00
Channel.prototype.moveFilter = function(data) {
if(data.from < 0 || data.to < 0 || data.from >= this.filters.length ||
data.to > this.filters.length) {
return;
2013-04-03 17:47:41 +00:00
}
2013-06-18 15:51:42 +00:00
var f = this.filters[data.from];
var to = data.to > data.from ? data.to + 1 : data.to;
var from = data.to > data.from ? data.from : data.from + 1;
this.filters.splice(to, 0, f);
this.filters.splice(from, 1);
this.broadcastChatFilters();
}
Channel.prototype.tryMoveFilter = function(user, data) {
2013-10-12 23:59:50 +00:00
if(!this.hasPermission(user, "filteredit")) {
user.kick("Attempted moveFilter with insufficient permission");
2013-06-18 15:51:42 +00:00
return;
2013-10-12 23:59:50 +00:00
}
2013-06-18 15:51:42 +00:00
2013-10-12 23:59:50 +00:00
if(typeof data.to !== "number" || typeof data.from !== "number") {
2013-06-18 15:51:42 +00:00
return;
2013-10-12 23:59:50 +00:00
}
2013-06-18 15:51:42 +00:00
this.moveFilter(data);
2013-04-03 17:47:41 +00:00
}
2013-05-22 19:38:16 +00:00
Channel.prototype.tryUpdatePermissions = function(user, perms) {
2013-10-11 21:31:40 +00:00
if(user.rank < 3) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted setPermissions with insufficient permission");
2013-05-22 19:38:16 +00:00
return;
}
for(var key in perms) {
this.permissions[key] = perms[key];
}
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " updated permissions");
2013-05-22 19:38:16 +00:00
this.sendAll("setPermissions", this.permissions);
}
2013-04-03 17:47:41 +00:00
Channel.prototype.tryUpdateOptions = function(user, data) {
2013-10-11 21:31:40 +00:00
if(user.rank < 2) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted setOptions with insufficient permission");
2013-04-03 17:47:41 +00:00
return;
}
const adminonly = {
2013-05-01 22:49:34 +00:00
pagetitle: true,
2013-06-20 18:43:37 +00:00
externalcss: true,
externaljs: true,
2013-05-01 22:49:34 +00:00
show_public: true
};
2013-08-07 14:32:43 +00:00
if ("afk_timeout" in data) {
data.afk_timeout = parseInt(data.afk_timeout);
if(data.afk_timeout < 0)
data.afk_timeout = 0;
}
2013-04-03 17:47:41 +00:00
for(var key in this.opts) {
if(key in data) {
2013-10-11 21:31:40 +00:00
if(key in adminonly && user.rank < 3) {
continue;
}
if(key === "afk_timeout" && this.opts[key] != data[key]) {
this.users.forEach(function (u) {
u.autoAFK();
});
}
2013-04-03 17:47:41 +00:00
this.opts[key] = data[key];
}
}
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " updated channel options");
2013-04-03 17:47:41 +00:00
this.broadcastOpts();
}
2013-05-15 15:34:27 +00:00
Channel.prototype.trySetCSS = function(user, data) {
2013-10-11 21:31:40 +00:00
if(user.rank < 3) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted setChannelCSS with insufficient permission");
2013-05-15 15:34:27 +00:00
return;
}
2013-10-12 23:59:50 +00:00
if (typeof data.css !== "string") {
return;
}
2013-05-15 15:34:27 +00:00
var css = data.css || "";
if(css.length > 20000) {
css = css.substring(0, 20000);
}
this.css = css;
this.sendAll("channelCSSJS", {
css: this.css,
js: this.js
});
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " set channel CSS");
2013-05-15 15:34:27 +00:00
}
Channel.prototype.trySetJS = function(user, data) {
2013-10-11 21:31:40 +00:00
if(user.rank < 3) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted setChannelJS with insufficient permission");
return;
}
if (typeof data.js !== "string") {
2013-05-15 15:34:27 +00:00
return;
}
var js = data.js || "";
if(js.length > 20000) {
js = js.substring(0, 20000);
}
this.js = js;
this.sendAll("channelCSSJS", {
css: this.css,
js: this.js
});
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " set channel JS");
2013-05-15 15:34:27 +00:00
}
2013-04-03 17:47:41 +00:00
Channel.prototype.updateMotd = function(motd) {
var html = motd.replace(/\n/g, "<br>");
// Temporary fix
html = html.replace(/style/g, "stlye");
html = sanitize(html).xss();
html = html.replace(/stlye/g, "style");
2013-05-12 15:48:41 +00:00
//html = this.filterMessage(html);
2013-04-03 17:47:41 +00:00
this.motd = {
motd: motd,
html: html
};
this.broadcastMotd();
}
Channel.prototype.tryUpdateMotd = function(user, data) {
2013-05-22 19:38:16 +00:00
if(!this.hasPermission(user, "motdedit")) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted setMotd with insufficient permission");
return;
}
if (typeof data.motd !== "string") {
2013-04-03 17:47:41 +00:00
return;
}
2013-04-28 22:10:00 +00:00
data.motd = data.motd || "";
this.updateMotd(data.motd);
2013-08-07 03:21:54 +00:00
this.logger.log("%%% " + user.name + " set the MOTD");
2013-04-03 17:47:41 +00:00
}
/* REGION Chat */
Channel.prototype.tryChat = function(user, data) {
2013-11-17 19:12:56 +00:00
if (data.meta === undefined) {
data.meta = {};
}
2013-04-03 17:47:41 +00:00
if(user.name == "") {
return;
}
2013-06-20 19:02:53 +00:00
if(!this.hasPermission(user, "chat"))
return;
2013-10-16 22:36:05 +00:00
if (this.mutedUsers.contains(user.name.toLowerCase())) {
2013-06-25 14:18:33 +00:00
user.socket.emit("noflood", {
action: "chat",
msg: "You have been muted on this channel."
});
return;
}
2013-06-20 19:02:53 +00:00
if(typeof data.msg !== "string") {
2013-04-03 17:47:41 +00:00
return;
}
2013-11-17 21:32:19 +00:00
// Validate meta
var meta = {};
if (user.rank >= 2) {
if ("modflair" in data.meta && data.meta.modflair === user.rank) {
meta.modflair = data.meta.modflair;
}
}
data.meta = meta;
2013-04-03 17:47:41 +00:00
var msg = data.msg;
2013-04-30 15:30:59 +00:00
if(msg.length > 240) {
msg = msg.substring(0, 240);
}
2013-04-27 17:13:01 +00:00
if(this.opts.chat_antiflood && user.noflood("chat", 2.0)) {
return;
}
2013-04-03 17:47:41 +00:00
2013-11-09 02:45:59 +00:00
if (this.mutedUsers.contains("[shadow]" + user.name.toLowerCase())) {
msg = sanitize(msg).escape();
msg = this.filterMessage(msg);
var msgobj = {
username: user.name,
msg: msg,
2013-11-17 19:12:56 +00:00
meta: data.meta,
2013-11-09 02:45:59 +00:00
time: Date.now()
};
user.socket.emit("chatMsg", msgobj);
return;
}
2013-11-17 19:12:56 +00:00
if (msg.indexOf("/") === 0) {
ChatCommand.handle(this, user, msg, data.meta);
} else {
if (msg.indexOf(">") === 0) {
data.meta.addClass = "greentext";
}
this.sendMessage(user, msg, data.meta);
}
2013-04-28 18:04:15 +00:00
}
Channel.prototype.chainMessage = function(user, msg, data) {
2013-03-05 18:51:58 +00:00
if(msg.indexOf("/") == 0)
2013-04-28 18:04:15 +00:00
ChatCommand.handle(this, user, msg, data);
2013-03-05 18:51:58 +00:00
2013-02-16 05:02:42 +00:00
else if(msg.indexOf(">") == 0)
2013-04-28 18:04:15 +00:00
this.sendMessage(user.name, msg, "greentext", data);
2013-03-05 18:51:58 +00:00
else
2013-04-28 18:04:15 +00:00
this.sendMessage(user.name, msg, "", data);
2013-03-05 18:51:58 +00:00
}
2013-04-01 21:02:09 +00:00
Channel.prototype.filterMessage = function(msg) {
2013-09-27 15:28:48 +00:00
const link = /(\w+:\/\/(?:[^:\/\[\]\s]+|\[[0-9a-f:]+\])(?::\d+)?(?:\/[^\/\s]*)*)/ig;
2013-05-13 23:54:52 +00:00
var subs = msg.split(link);
2013-03-29 18:15:46 +00:00
// Apply other filters
2013-05-13 23:54:52 +00:00
for(var j = 0; j < subs.length; j++) {
2013-06-05 20:49:54 +00:00
if(this.opts.enable_link_regex && subs[j].match(link)) {
2013-09-27 15:28:48 +00:00
var orig = subs[j];
2013-06-18 15:51:42 +00:00
for(var i = 0; i < this.filters.length; i++) {
if(!this.filters[i].filterlinks || !this.filters[i].active)
continue;
subs[j] = this.filters[i].filter(subs[j]);
}
2013-09-27 15:28:48 +00:00
// only apply link filter if another filter hasn't changed
// the link
if (subs[j] === orig) {
subs[j] = url.format(url.parse(subs[j]));
subs[j] = subs[j].replace(link,
"<a href=\"$1\" target=\"_blank\">$1</a>");
}
2013-03-29 18:15:46 +00:00
continue;
2013-05-13 23:54:52 +00:00
}
for(var i = 0; i < this.filters.length; i++) {
if(!this.filters[i].active)
continue;
subs[j] = this.filters[i].filter(subs[j]);
}
2013-03-29 18:15:46 +00:00
}
2013-05-13 23:54:52 +00:00
return subs.join("");
2013-04-01 21:02:09 +00:00
}
2013-11-17 19:12:56 +00:00
Channel.prototype.sendMessage = function (user, msg, meta, filter) {
msg = sanitize(msg).escape();
msg = this.filterMessage(msg);
var msgobj = {
username: user.name,
msg: msg,
meta: meta,
time: Date.now()
}
if (filter && filter.byRank !== undefined) {
this.sendAllWithRank("chatMsg", msgobj, filter.byRank);
} else {
this.sendAll("chatMsg", msgobj);
this.chatbuffer.push(msgobj);
if(this.chatbuffer.length > 15)
this.chatbuffer.shift();
var unescaped = sanitize(msg).entityDecode();
2013-11-17 21:32:19 +00:00
this.logger.log("<" + user.name + (meta.addClass ? "." + meta.addclass : "")
+ "> " + unescaped);
2013-11-17 19:12:56 +00:00
}
};
Channel.prototype.sendMessageOld = function(username, msg, msgclass, data) {
2013-04-03 17:47:41 +00:00
// I don't want HTML from strangers
msg = sanitize(msg).escape();
2013-04-01 21:02:09 +00:00
msg = this.filterMessage(msg);
var msgobj = {
2013-03-05 18:51:58 +00:00
username: username,
2013-02-16 05:02:42 +00:00
msg: msg,
msgclass: msgclass,
time: Date.now()
};
if(data) {
for(var key in data) {
msgobj[key] = data[key];
}
}
this.sendAll("chatMsg", msgobj);
this.chatbuffer.push(msgobj);
2013-04-03 17:47:41 +00:00
if(this.chatbuffer.length > 15)
this.chatbuffer.shift();
var unescaped = sanitize(msg).entityDecode();
this.logger.log("<" + username + "." + msgclass + "> " + unescaped);
2013-03-05 18:51:58 +00:00
};
2013-02-16 05:02:42 +00:00
2013-04-03 17:47:41 +00:00
/* REGION Rank stuff */
Channel.prototype.trySetRank = function(user, data) {
2013-08-17 17:30:52 +00:00
var self = this;
2013-10-12 23:59:50 +00:00
if(user.rank < 2) {
user.kick("Attempted setChannelRank with insufficient permission");
return;
2013-10-12 23:59:50 +00:00
}
2013-10-12 23:59:50 +00:00
if(typeof data.user !== "string" || typeof data.rank !== "number") {
return;
2013-10-12 23:59:50 +00:00
}
if(data.rank >= user.rank)
return;
if(data.rank < 1)
return;
var receiver;
2013-08-17 17:30:52 +00:00
for(var i = 0; i < self.users.length; i++) {
if(self.users[i].name == data.user) {
receiver = self.users[i];
break;
}
}
if(receiver) {
if(receiver.rank >= user.rank)
return;
receiver.rank = data.rank;
if(receiver.loggedIn) {
2013-08-18 18:35:57 +00:00
self.saveRank(receiver, function (err, res) {
if (self.dead)
return;
self.logger.log("*** " + user.name + " set " +
2013-08-18 18:35:57 +00:00
data.user + "'s rank to " + data.rank);
2013-10-11 21:31:40 +00:00
self.sendAllWithRank(3, "setChannelRank", data);
2013-08-18 18:35:57 +00:00
});
}
2013-11-09 18:33:18 +00:00
self.sendAll("setUserRank", {
name: receiver.name,
rank: receiver.rank
});
2013-08-18 18:16:46 +00:00
} else if(self.registered) {
2013-08-17 17:30:52 +00:00
self.getRank(data.user, function (err, rrank) {
if (self.dead)
return;
2013-08-17 17:30:52 +00:00
if(err)
return;
if(rrank >= user.rank)
return;
2013-08-18 18:08:31 +00:00
self.server.db.setChannelRank(self.name, data.user,
2013-08-17 17:30:52 +00:00
data.rank, function (err, res) {
if (self.dead)
return;
self.logger.log("*** " + user.name + " set " +
2013-08-17 17:30:52 +00:00
data.user + "'s rank to " + data.rank);
2013-10-11 21:31:40 +00:00
self.sendAllWithRank(3, "setChannelRank", data);
2013-08-17 17:30:52 +00:00
});
});
2013-02-16 05:02:42 +00:00
}
}
Channel.prototype.changeLeader = function(name) {
if(this.leader != null) {
2013-02-17 05:00:33 +00:00
var old = this.leader;
2013-02-16 05:02:42 +00:00
this.leader = null;
2013-05-22 19:38:16 +00:00
if(old.rank == 1.5) {
old.rank = old.oldrank;
}
2013-11-09 04:12:17 +00:00
this.sendAll("setUserRank", {
2013-11-15 16:24:43 +00:00
name: old.name,
2013-11-09 04:12:17 +00:00
rank: old.rank
});
2013-02-16 05:02:42 +00:00
}
if(name == "") {
2013-11-09 04:12:17 +00:00
this.sendAll("setLeader", "");
2013-03-27 19:28:51 +00:00
this.logger.log("*** Resuming autolead");
2013-06-30 00:59:33 +00:00
this.playlist.lead(true);
2013-02-16 05:02:42 +00:00
return;
}
for(var i = 0; i < this.users.length; i++) {
if(this.users[i].name == name) {
2013-11-09 04:12:17 +00:00
this.sendAll("setLeader", name);
2013-03-27 19:28:51 +00:00
this.logger.log("*** Assigned leader: " + name);
2013-06-30 00:59:33 +00:00
this.playlist.lead(false);
2013-02-16 05:02:42 +00:00
this.leader = this.users[i];
2013-05-22 19:38:16 +00:00
if(this.users[i].rank < 1.5) {
this.users[i].oldrank = this.users[i].rank;
this.users[i].rank = 1.5;
}
2013-11-09 04:12:17 +00:00
this.sendAll("setUserRank", {
name: name,
rank: this.users[i].rank
});
2013-02-16 05:02:42 +00:00
}
}
}
2013-04-03 17:47:41 +00:00
Channel.prototype.tryChangeLeader = function(user, data) {
2013-10-11 21:31:40 +00:00
if(user.rank < 2) {
2013-10-12 23:59:50 +00:00
user.kick("Attempted assignLeader with insufficient permission");
2013-04-03 17:47:41 +00:00
return;
2013-04-02 19:07:22 +00:00
}
2013-02-16 05:02:42 +00:00
2013-10-12 23:59:50 +00:00
if(typeof data.name !== "string") {
2013-02-16 05:02:42 +00:00
return;
}
2013-04-03 17:47:41 +00:00
this.changeLeader(data.name);
2013-08-07 03:21:54 +00:00
this.logger.log("### " + user.name + " assigned leader to " + data.name);
2013-02-16 05:02:42 +00:00
}
2013-07-16 03:01:12 +00:00
module.exports = Channel;