sync/src/channel/emotes.js

241 lines
5.6 KiB
JavaScript
Raw Normal View History

2014-05-20 19:30:14 -07:00
var ChannelModule = require("./module");
var XSS = require("../xss");
class EmoteList {
constructor(list) {
if (!list) {
list = [];
}
2014-05-20 19:30:14 -07:00
this.emotes = new Map(list.map(e => [e.name, e]));
}
2014-05-20 19:30:14 -07:00
toJSON() {
let list = [];
2017-05-18 02:55:57 -07:00
for (let [key, value] of this.emotes.entries()) {
list.push(value);
2014-05-20 19:30:14 -07:00
}
return list;
}
2014-05-20 19:30:14 -07:00
hasEmote(name) {
return this.emotes.has(name);
}
2014-05-20 19:30:14 -07:00
setEmote(name, emote) {
this.emotes.set(name, emote);
}
2014-05-20 19:30:14 -07:00
deleteEmote(name) {
return this.emotes.delete(name);
}
2014-05-20 19:30:14 -07: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);
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();
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) {
this.emotes = new EmoteList(data.emotes);
2014-05-20 19:30:14 -07:00
}
this.dirty = false;
2014-05-20 19:30:14 -07:00
};
EmoteModule.prototype.save = function (data) {
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) {
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) {
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) {
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);
});
};
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;
}
if (!this.channel.modules.permissions.canEditEmotes(user)) {
return;
}
var e = this.emotes.hasEmote(data.name);
var f = validateEmote(data);
2017-05-18 02:55:57 -07:00
if (!f || e) {
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.";
}
user.socket.emit("errorMsg", {
msg: message,
alert: true
});
return;
}
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
this.dirty = true;
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;
}
this.emotes.setEmote(f.name, {
name: f.name,
source: f.source,
image: f.image
});
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;
}
this.emotes = new EmoteList(data.map(validateEmote).filter(f => f));
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;
}
this.emotes.deleteEmote(data.name);
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;