diff --git a/lib/channel.js b/lib/channel.js
index 21b65756..8cb0b06b 100644
--- a/lib/channel.js
+++ b/lib/channel.js
@@ -103,6 +103,7 @@ function Channel(name) {
html: "" // Filtered MOTD text (XSS removed; \n replaced by
)
};
self.filters = DEFAULT_FILTERS;
+ self.emotes = [];
self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs",
self.uniqueName + ".log"));
self.css = ""; // Up to 20KB of inline CSS
@@ -264,6 +265,15 @@ Channel.prototype.loadState = function () {
}
}
+ // Emotes
+ if ("emotes" in data) {
+ data.emotes.forEach(function (e) {
+ self.emotes.push(e);
+ });
+
+ self.updateEmote({ name: ":test:", source: ":test:", image: "http://imgs.xkcd.com/comics/mobile_marketing.png" });
+ }
+
// MOTD
if ("motd" in data) {
self.motd = {
@@ -315,6 +325,7 @@ Channel.prototype.saveState = function () {
opts: self.opts,
permissions: self.permissions,
filters: filters,
+ emotes: self.emotes,
motd: self.motd,
playlistLock: self.playlistLock,
chatbuffer: self.chatbuffer,
@@ -555,6 +566,7 @@ Channel.prototype.join = function (user) {
self.sendMediaUpdate([user]);
self.sendPlaylistLock([user]);
self.sendUserlist([user]);
+ self.sendEmoteList([user]);
self.sendRecentChat([user]);
self.sendCSSJS([user]);
self.sendPoll([user]);
@@ -1013,6 +1025,16 @@ Channel.prototype.sendChatFilters = function (users) {
});
};
+/**
+ * Sends the emote list
+ */
+Channel.prototype.sendEmoteList = function (users) {
+ var self = this;
+ users.forEach(function (u) {
+ u.socket.emit("emoteList", self.emotes);
+ });
+};
+
/**
* Sends the channel permissions
*/
@@ -2444,6 +2466,138 @@ Channel.prototype.handleMoveFilter = function (user, data) {
this.moveFilter(data.from, data.to);
};
+/**
+ * Imports a list of emotes, replacing the current list
+ */
+Channel.prototype.importEmotes = function (emotes) {
+ this.emotes = emotes;
+ this.sendEmoteList(this.users);
+};
+
+/**
+ * Handles a user message to import a list of emotes
+ */
+Channel.prototype.handleImportEmotes = function (user, data) {
+ if (!this.hasPermission(user, "emoteimport")) {
+ return;
+ }
+
+ if (!(data instanceof Array)) {
+ return;
+ }
+
+ this.emotes = data.map(this.validateEmote.bind(this))
+ .filter(function (f) { return f !== false; });
+
+ this.sendEmoteList(this.users);
+};
+
+/**
+ * Validates data for an emote
+ */
+Channel.prototype.validateEmote = function (f) {
+ if (typeof f.source !== "string" || typeof f.image !== "string") {
+ return false;
+ }
+
+ if (typeof f.name !== "string") {
+ f.name = f.source;
+ }
+
+ f.image = f.image.substring(0, 1000);
+ f.image = XSS.sanitizeText(f.image);
+
+ try {
+ new RegExp(f.regex, "gi");
+ } catch (e) {
+ return false;
+ }
+
+ return f;
+};
+
+/**
+ * Updates an emote, or adds a new one if the emote does not exist
+ */
+Channel.prototype.updateEmote = function (emote) {
+ var self = this;
+
+ if (!emote.name) {
+ emote.name = emote.source;
+ }
+
+ var found = false;
+ for (var i = 0; i < self.emotes.length; i++) {
+ if (self.emotes[i].name === emote.name) {
+ found = true;
+ self.emotes[i] = emote;
+ break;
+ }
+ }
+
+ if (!found) {
+ self.emotes.push(emote);
+ }
+
+ self.users.forEach(function (u) {
+ u.socket.emit("updateEmote", emote);
+ });
+};
+
+/**
+ * Handles a user message to update an emote
+ */
+Channel.prototype.handleUpdateEmote = function (user, f) {
+ if (!this.hasPermission(user, "emoteedit")) {
+ user.kick("Attempted updateEmote with insufficient permission");
+ return;
+ }
+
+ var emote = this.validateEmote(f);
+ if (!emote) {
+ return;
+ }
+
+ this.logger.log("[mod] " + user.name + " updated emote: " + f.name + " -> " +
+ f.image);
+ this.updateEmote(emote);
+};
+
+/**
+ * Removes an emote
+ */
+Channel.prototype.removeEmote = function (emote) {
+ var self = this;
+
+ for (var i = 0; i < self.emotes.length; i++) {
+ if (self.emotes[i].name === emote.name) {
+ self.emotes.splice(i, 1);
+ self.users.forEach(function (u) {
+ u.socket.emit("deleteEmote", emote);
+ });
+ break;
+ }
+ }
+};
+
+/**
+ * Handles a user message to delete an emote
+ */
+Channel.prototype.handleRemoveEmote = function (user, f) {
+ if (!this.hasPermission(user, "emoteedit")) {
+ user.kick("Attempted removeEmote with insufficient permission");
+ return;
+ }
+
+ if (typeof f.name !== "string") {
+ return;
+ }
+
+ this.logger.log("[mod] " + user.name + " removed emote: " + f.name);
+ this.removeFilter(f);
+};
+
+
/**
* Handles a user message to change the channel permissions
*/
diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js
index 332d18df..62cf7f83 100644
--- a/www/assets/js/callbacks.js
+++ b/www/assets/js/callbacks.js
@@ -1032,7 +1032,31 @@ Callbacks = {
}
}
}
- }
+ },
+
+ emoteList: function (data) {
+ loadEmotes(data);
+ },
+
+ updateEmote: function (data) {
+ data.regex = new RegExp(data.source, "gi");
+ var found = false;
+ for (var i = 0; i < CHANNEL.emotes.length; i++) {
+ if (CHANNEL.emotes[i].name === data.name) {
+ found = true;
+ CHANNEL.emotes[i] = data;
+ break;
+ }
+ }
+
+ if (!found) {
+ CHANNEL.emotes.push(data);
+ }
+ },
+
+ deleteEmote: function (data) {
+
+ },
}
var SOCKET_DEBUG = true;
diff --git a/www/assets/js/data.js b/www/assets/js/data.js
index 2a7716e2..331e0d40 100644
--- a/www/assets/js/data.js
+++ b/www/assets/js/data.js
@@ -32,7 +32,8 @@ var CHANNEL = {
motd: "",
motd_text: "",
name: false,
- usercount: 0
+ usercount: 0,
+ emotes: []
};
var PLAYER = false;
diff --git a/www/assets/js/util.js b/www/assets/js/util.js
index b08a50ce..dda5aaf4 100644
--- a/www/assets/js/util.js
+++ b/www/assets/js/util.js
@@ -1270,6 +1270,8 @@ function formatChatMessage(data) {
if (data.meta.forceShowName)
skip = false;
+ data.msg = execEmotes(data.msg);
+
LASTCHATNAME = data.username;
LASTCHATTIME = data.time;
var div = $("