2014-05-20 19:30:14 -07:00
|
|
|
var ChannelModule = require("./module");
|
|
|
|
|
var XSS = require("../xss");
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
class EmoteList {
|
|
|
|
|
constructor(list) {
|
|
|
|
|
if (!list) {
|
|
|
|
|
list = [];
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
this.emotes = new Map(list.map(e => [e.name, e]));
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
toJSON() {
|
|
|
|
|
let list = [];
|
2017-05-18 02:55:57 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
for (let [key, value] of this.emotes.entries()) {
|
|
|
|
|
list.push(value);
|
2014-05-20 19:30:14 -07:00
|
|
|
}
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
return list;
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
hasEmote(name) {
|
|
|
|
|
return this.emotes.has(name);
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
setEmote(name, emote) {
|
|
|
|
|
this.emotes.set(name, emote);
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
deleteEmote(name) {
|
|
|
|
|
return this.emotes.delete(name);
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
size() {
|
|
|
|
|
return this.emotes.size;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-05-20 19:30:14 -07:00
|
|
|
|
|
|
|
|
function validateEmote(f) {
|
|
|
|
|
if (typeof f.name !== "string" || typeof f.image !== "string") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f.image = f.image.substring(0, 1000);
|
|
|
|
|
f.image = XSS.sanitizeText(f.image);
|
|
|
|
|
|
2017-03-14 21:36:44 -07:00
|
|
|
var s = XSS.looseSanitizeText(f.name).replace(/([\\\.\?\+\*\$\^\|\(\)\[\]\{\}])/g, "\\$1");
|
2014-05-20 19:30:14 -07:00
|
|
|
s = "(^|\\s)" + s + "(?!\\S)";
|
|
|
|
|
f.source = s;
|
|
|
|
|
|
2015-12-05 18:52:39 -08:00
|
|
|
if (!f.image || !f.name) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-20 19:30:14 -07:00
|
|
|
try {
|
|
|
|
|
new RegExp(f.source, "gi");
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return f;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function EmoteModule(channel) {
|
|
|
|
|
ChannelModule.apply(this, arguments);
|
|
|
|
|
this.emotes = new EmoteList();
|
2017-12-10 10:36:28 -08:00
|
|
|
this.supportsDirtyCheck = true;
|
2014-05-20 19:30:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EmoteModule.prototype = Object.create(ChannelModule.prototype);
|
|
|
|
|
|
|
|
|
|
EmoteModule.prototype.load = function (data) {
|
|
|
|
|
if ("emotes" in data) {
|
2018-01-18 19:34:57 -08:00
|
|
|
this.emotes = new EmoteList(data.emotes);
|
2014-05-20 19:30:14 -07:00
|
|
|
}
|
2017-12-10 10:36:28 -08:00
|
|
|
|
|
|
|
|
this.dirty = false;
|
2014-05-20 19:30:14 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EmoteModule.prototype.save = function (data) {
|
2018-01-18 19:34:57 -08:00
|
|
|
data.emotes = this.emotes.toJSON();
|
2014-05-20 19:30:14 -07:00
|
|
|
};
|
|
|
|
|
|
2014-05-23 23:09:36 -07:00
|
|
|
EmoteModule.prototype.packInfo = function (data, isAdmin) {
|
|
|
|
|
if (isAdmin) {
|
2018-01-18 19:34:57 -08:00
|
|
|
data.emoteCount = this.emotes.size();
|
2014-05-23 23:09:36 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2014-05-20 19:30:14 -07:00
|
|
|
EmoteModule.prototype.onUserPostJoin = function (user) {
|
2017-05-16 09:56:37 -07:00
|
|
|
user.socket.on("renameEmote", this.handleRenameEmote.bind(this, user));
|
2014-05-20 19:30:14 -07:00
|
|
|
user.socket.on("updateEmote", this.handleUpdateEmote.bind(this, user));
|
|
|
|
|
user.socket.on("importEmotes", this.handleImportEmotes.bind(this, user));
|
|
|
|
|
user.socket.on("removeEmote", this.handleRemoveEmote.bind(this, user));
|
|
|
|
|
this.sendEmotes([user]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EmoteModule.prototype.sendEmotes = function (users) {
|
2018-01-18 19:34:57 -08:00
|
|
|
var f = this.emotes.toJSON();
|
2014-05-20 19:30:14 -07:00
|
|
|
var chan = this.channel;
|
|
|
|
|
users.forEach(function (u) {
|
|
|
|
|
u.socket.emit("emoteList", f);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2017-05-16 09:56:37 -07:00
|
|
|
EmoteModule.prototype.handleRenameEmote = function (user, data) {
|
|
|
|
|
if (typeof data !== "object") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-18 02:55:57 -07:00
|
|
|
/*
|
|
|
|
|
** This shouldn't be able to happen,
|
|
|
|
|
** but we have idiots that like to send handcrafted frames to fuck with shit
|
|
|
|
|
*/
|
|
|
|
|
if (typeof data.old !== "string"){
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-16 09:56:37 -07:00
|
|
|
if (!this.channel.modules.permissions.canEditEmotes(user)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
var e = this.emotes.hasEmote(data.name);
|
2017-05-16 09:56:37 -07:00
|
|
|
var f = validateEmote(data);
|
2017-05-18 02:55:57 -07:00
|
|
|
if (!f || e) {
|
2017-05-16 09:56:37 -07:00
|
|
|
var message = "Unable to rename emote '" + JSON.stringify(data) + "'. " +
|
|
|
|
|
"Please contact an administrator for assistance.";
|
|
|
|
|
if (!data.image || !data.name) {
|
|
|
|
|
message = "Emote names and images must not be blank.";
|
|
|
|
|
}
|
2017-05-18 02:55:57 -07:00
|
|
|
if (e) {
|
|
|
|
|
message = "Emote already exists.";
|
|
|
|
|
}
|
2017-05-16 09:56:37 -07:00
|
|
|
|
|
|
|
|
user.socket.emit("errorMsg", {
|
|
|
|
|
msg: message,
|
|
|
|
|
alert: true
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
var hadOld = this.emotes.deleteEmote(f.old);
|
|
|
|
|
|
|
|
|
|
if (!hadOld) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.emotes.setEmote(f.name, {
|
|
|
|
|
name: f.name,
|
|
|
|
|
source: f.source,
|
|
|
|
|
image: f.image
|
|
|
|
|
});
|
2017-05-16 19:15:12 -07:00
|
|
|
|
2017-12-10 10:36:28 -08:00
|
|
|
this.dirty = true;
|
|
|
|
|
|
2017-05-16 09:56:37 -07:00
|
|
|
var chan = this.channel;
|
|
|
|
|
chan.broadcastAll("renameEmote", f);
|
|
|
|
|
chan.logger.log(`[mod] ${user.getName()} renamed emote: ${f.old} -> ${f.name}`);
|
|
|
|
|
};
|
|
|
|
|
|
2014-05-20 19:30:14 -07:00
|
|
|
EmoteModule.prototype.handleUpdateEmote = function (user, data) {
|
|
|
|
|
if (typeof data !== "object") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.channel.modules.permissions.canEditEmotes(user)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var f = validateEmote(data);
|
|
|
|
|
if (!f) {
|
2015-12-05 18:52:39 -08:00
|
|
|
var message = "Unable to update emote '" + JSON.stringify(data) + "'. " +
|
|
|
|
|
"Please contact an administrator for assistance.";
|
|
|
|
|
if (!data.image || !data.name) {
|
|
|
|
|
message = "Emote names and images must not be blank.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user.socket.emit("errorMsg", {
|
|
|
|
|
msg: message,
|
|
|
|
|
alert: true
|
|
|
|
|
});
|
2014-05-20 19:30:14 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
this.emotes.setEmote(f.name, {
|
|
|
|
|
name: f.name,
|
|
|
|
|
source: f.source,
|
|
|
|
|
image: f.image
|
|
|
|
|
});
|
2017-12-10 10:36:28 -08:00
|
|
|
|
|
|
|
|
this.dirty = true;
|
|
|
|
|
|
2014-05-20 19:30:14 -07:00
|
|
|
var chan = this.channel;
|
|
|
|
|
chan.broadcastAll("updateEmote", f);
|
|
|
|
|
|
|
|
|
|
chan.logger.log("[mod] " + user.getName() + " updated emote: " + f.name + " -> " +
|
|
|
|
|
f.image);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EmoteModule.prototype.handleImportEmotes = function (user, data) {
|
|
|
|
|
if (!(data instanceof Array)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Note: importing requires a different permission node than simply
|
|
|
|
|
updating/removing */
|
|
|
|
|
if (!this.channel.modules.permissions.canImportEmotes(user)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
this.emotes = new EmoteList(data.map(validateEmote).filter(f => f));
|
2017-12-10 10:36:28 -08:00
|
|
|
|
|
|
|
|
this.dirty = true;
|
|
|
|
|
|
2014-05-20 19:30:14 -07:00
|
|
|
this.sendEmotes(this.channel.users);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EmoteModule.prototype.handleRemoveEmote = function (user, data) {
|
|
|
|
|
if (typeof data !== "object") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.channel.modules.permissions.canEditEmotes(user)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof data.name !== "string") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 19:34:57 -08:00
|
|
|
this.emotes.deleteEmote(data.name);
|
2017-12-10 10:36:28 -08:00
|
|
|
|
|
|
|
|
this.dirty = true;
|
|
|
|
|
|
2014-05-20 19:30:14 -07:00
|
|
|
this.channel.logger.log("[mod] " + user.getName() + " removed emote: " + data.name);
|
|
|
|
|
this.channel.broadcastAll("removeEmote", data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = EmoteModule;
|