sync/lib/channel/filters.js
2014-12-27 01:39:30 -05:00

283 lines
7.9 KiB
JavaScript

var FilterList = require('cytubefilters');
var ChannelModule = require("./module");
var XSS = require("../xss");
var Logger = require("../logger");
function validateFilter(f) {
if (typeof f.source !== "string" || typeof f.flags !== "string" ||
typeof f.replace !== "string") {
return false;
}
if (typeof f.name !== "string") {
f.name = f.source;
}
f.replace = f.replace.substring(0, 1000);
f.replace = XSS.sanitizeHTML(f.replace);
f.flags = f.flags.substring(0, 4);
try {
new RegExp(f.source, f.flags);
} catch (e) {
return false;
}
var filter = {
name: f.name,
source: f.source,
replace: fixReplace(f.replace),
flags: f.flags,
active: !!f.active,
filterlinks: !!f.filterlinks
};
return filter;
}
function fixReplace(replace) {
return replace.replace(/\$(\d)/g, '\\$1');
}
function makeDefaultFilter(name, source, flags, replace) {
return {
name: name,
source: source,
flags: flags,
replace: fixReplace(replace),
active: true,
filterlinks: false
};
}
const DEFAULT_FILTERS = [
makeDefaultFilter("monospace", "`(.+?)`", "g", "<code>$1</code>"),
makeDefaultFilter("bold", "\\*(.+?)\\*", "g", "<strong>$1</strong>"),
makeDefaultFilter("italic", "_(.+?)_", "g", "<em>$1</em>"),
makeDefaultFilter("strike", "~~(.+?)~~", "g", "<s>$1</s>"),
makeDefaultFilter("inline spoiler", "\\[sp\\](.*?)\\[\\/sp\\]", "ig", "<span class=\"spoiler\">$1</span>")
];
function ChatFilterModule(channel) {
ChannelModule.apply(this, arguments);
this.filters = new FilterList(DEFAULT_FILTERS);
}
ChatFilterModule.prototype = Object.create(ChannelModule.prototype);
ChatFilterModule.prototype.load = function (data) {
if ("filters" in data) {
for (var i = 0; i < data.filters.length; i++) {
var f = validateFilter(data.filters[i]);
if (f) {
try {
this.filters.updateFilter(f);
} catch (e) {
if (e.message.match(/does not exist/i)) {
try {
this.filters.addFilter(f);
} catch (e) {
Logger.errlog.log("Filter load failed: " +
JSON.stringify(f) + " c:" + this.channel.name);
}
} else {
Logger.errlog.log("Filter load failed: " +
JSON.stringify(f) + " c:" + this.channel.name);
}
}
}
}
}
};
ChatFilterModule.prototype.save = function (data) {
data.filters = this.filters.pack();
};
ChatFilterModule.prototype.packInfo = function (data, isAdmin) {
if (isAdmin) {
data.chatFilterCount = this.filters.length;
}
};
ChatFilterModule.prototype.onUserPostJoin = function (user) {
user.socket.on("addFilter", this.handleAddFilter.bind(this, user));
user.socket.on("updateFilter", this.handleUpdateFilter.bind(this, user));
user.socket.on("importFilters", this.handleImportFilters.bind(this, user));
user.socket.on("moveFilter", this.handleMoveFilter.bind(this, user));
user.socket.on("removeFilter", this.handleRemoveFilter.bind(this, user));
user.socket.on("requestChatFilters", this.handleRequestChatFilters.bind(this, user));
};
ChatFilterModule.prototype.sendChatFilters = function (users) {
var f = this.filters.pack();
var chan = this.channel;
users.forEach(function (u) {
if (chan.modules.permissions.canEditFilters(u)) {
u.socket.emit("chatFilters", f);
}
});
};
ChatFilterModule.prototype.handleAddFilter = function (user, data) {
if (typeof data !== "object") {
return;
}
if (!this.channel.modules.permissions.canEditFilters(user)) {
return;
}
data = validateFilter(data);
if (!data) {
return;
}
try {
this.filters.addFilter(data);
} catch (e) {
user.socket.emit("errorMsg", {
msg: "Filter add failed: " + e.message,
alert: true
});
return;
}
var chan = this.channel;
chan.users.forEach(function (u) {
if (chan.modules.permissions.canEditFilters(u)) {
u.socket.emit("updateChatFilter", data);
}
});
chan.logger.log("[mod] " + user.getName() + " added filter: " + data.name + " -> " +
"s/" + data.source + "/" + data.replace + "/" + data.flags + " active: " +
data.active + ", filterlinks: " + data.filterlinks);
};
ChatFilterModule.prototype.handleUpdateFilter = function (user, data) {
if (typeof data !== "object") {
return;
}
if (!this.channel.modules.permissions.canEditFilters(user)) {
return;
}
data = validateFilter(data);
if (!data) {
return;
}
try {
this.filters.updateFilter(data);
} catch (e) {
if (e.message.match(/filter to be updated does not exist/i)) {
this.handleAddFilter(user, data);
} else {
user.socket.emit("errorMsg", {
msg: "Filter update failed: " + e.message,
alert: true
});
}
return;
}
var chan = this.channel;
chan.users.forEach(function (u) {
if (chan.modules.permissions.canEditFilters(u)) {
u.socket.emit("updateChatFilter", data);
}
});
chan.logger.log("[mod] " + user.getName() + " updated filter: " + data.name + " -> " +
"s/" + data.source + "/" + data.replace + "/" + data.flags + " active: " +
data.active + ", filterlinks: " + data.filterlinks);
};
ChatFilterModule.prototype.handleImportFilters = function (user, data) {
if (!(data instanceof Array)) {
return;
}
/* Note: importing requires a different permission node than simply
updating/removing */
if (!this.channel.modules.permissions.canImportFilters(user)) {
return;
}
try {
this.filters = new FilterList(data.map(validateFilter).filter(function (f) {
return f !== false;
}));
} catch (e) {
user.socket.emit("errorMsg", {
msg: "Filter import failed: " + e.message,
alert: true
});
return;
}
this.channel.logger.log("[mod] " + user.getName() + " imported the filter list");
this.sendChatFilters(this.channel.users);
};
ChatFilterModule.prototype.handleRemoveFilter = function (user, data) {
if (typeof data !== "object") {
return;
}
if (!this.channel.modules.permissions.canEditFilters(user)) {
return;
}
if (typeof data.name !== "string") {
return;
}
try {
this.filters.removeFilter(data);
} catch (e) {
user.socket.emit("errorMsg", {
msg: "Filter removal failed: " + e.message,
alert: true
});
return;
}
var chan = this.channel;
chan.users.forEach(function (u) {
if (chan.modules.permissions.canEditFilters(u)) {
u.socket.emit("deleteChatFilter", data);
}
});
this.channel.logger.log("[mod] " + user.getName() + " removed filter: " + data.name);
};
ChatFilterModule.prototype.handleMoveFilter = function (user, data) {
if (typeof data !== "object") {
return;
}
if (!this.channel.modules.permissions.canEditFilters(user)) {
return;
}
if (typeof data.to !== "number" || typeof data.from !== "number") {
return;
}
try {
this.filters.moveFilter(data.from, data.to);
} catch (e) {
user.socket.emit("errorMsg", {
msg: "Filter move failed: " + e.message,
alert: true
});
return;
}
};
ChatFilterModule.prototype.handleRequestChatFilters = function (user) {
this.sendChatFilters([user]);
};
module.exports = ChatFilterModule;