Update README.md
[KisSync.git] / src / channel / emotes.js
bloba24cbb206fc2299c71ead82916d7482bdef971f4
1 var ChannelModule = require("./module");
2 var XSS = require("../xss");
4 function EmoteList(defaults) {
5 if (!defaults) {
6 defaults = [];
9 this.emotes = defaults.map(validateEmote).filter(function (f) {
10 return f !== false;
11 });
14 EmoteList.prototype = {
15 pack: function () {
16 return Array.prototype.slice.call(this.emotes);
19 importList: function (emotes) {
20 this.emotes = Array.prototype.slice.call(emotes);
23 emoteExists: function (emote){
24 for (let i = 0; i < this.emotes.length; i++) {
25 if (this.emotes[i].name === emote.name) {
26 return true;
30 return false;
33 renameEmote: function (emote) {
34 var found = false;
35 for (var i = 0; i < this.emotes.length; i++) {
36 if (this.emotes[i].name === emote.old) {
37 found = true;
38 this.emotes[i] = emote;
39 delete this.emotes[i].old;
40 break;
44 if(found){
45 return true;
47 return false;
50 updateEmote: function (emote) {
51 var found = false;
52 for (var i = 0; i < this.emotes.length; i++) {
53 if (this.emotes[i].name === emote.name) {
54 found = true;
55 this.emotes[i] = emote;
56 break;
60 /* If no emote was updated, add a new one */
61 if (!found) {
62 this.emotes.push(emote);
66 removeEmote: function (emote) {
67 for (var i = 0; i < this.emotes.length; i++) {
68 if (this.emotes[i].name === emote.name) {
69 this.emotes.splice(i, 1);
70 break;
75 moveEmote: function (from, to) {
76 if (from < 0 || to < 0 ||
77 from >= this.emotes.length || to >= this.emotes.length) {
78 return false;
81 var f = this.emotes[from];
82 /* Offset from/to indexes to account for the fact that removing
83 an element changes the position of one of them.
85 I could have just done a swap, but it's already implemented this way
86 and it works. */
87 to = to > from ? to + 1 : to;
88 from = to > from ? from : from + 1;
90 this.emotes.splice(to, 0, f);
91 this.emotes.splice(from, 1);
92 return true;
96 function validateEmote(f) {
97 if (typeof f.name !== "string" || typeof f.image !== "string") {
98 return false;
101 f.image = f.image.substring(0, 1000);
102 f.image = XSS.sanitizeText(f.image);
104 var s = XSS.looseSanitizeText(f.name).replace(/([\\.?+*$^|()[\]{}])/g, "\\$1");
105 s = "(^|\\s)" + s + "(?!\\S)";
106 f.source = s;
108 if (!f.image || !f.name) {
109 return false;
112 try {
113 new RegExp(f.source, "gi");
114 } catch (e) {
115 return false;
118 return f;
121 function EmoteModule(_channel) {
122 ChannelModule.apply(this, arguments);
123 this.emotes = new EmoteList();
124 this.supportsDirtyCheck = true;
127 EmoteModule.prototype = Object.create(ChannelModule.prototype);
129 EmoteModule.prototype.load = function (data) {
130 if ("emotes" in data) {
131 this.emotes = new EmoteList(data.emotes);
134 this.dirty = false;
137 EmoteModule.prototype.save = function (data) {
138 data.emotes = this.emotes.pack();
141 EmoteModule.prototype.packInfo = function (data, isAdmin) {
142 if (isAdmin) {
143 data.emoteCount = this.emotes.emotes.length;
147 EmoteModule.prototype.onUserPostJoin = function (user) {
148 user.socket.on("renameEmote", this.handleRenameEmote.bind(this, user));
149 user.socket.on("updateEmote", this.handleUpdateEmote.bind(this, user));
150 user.socket.on("importEmotes", this.handleImportEmotes.bind(this, user));
151 user.socket.on("moveEmote", this.handleMoveEmote.bind(this, user));
152 user.socket.on("removeEmote", this.handleRemoveEmote.bind(this, user));
153 this.sendEmotes([user]);
156 EmoteModule.prototype.sendEmotes = function (users) {
157 var f = this.emotes.pack();
158 users.forEach(function (u) {
159 u.socket.emit("emoteList", f);
163 EmoteModule.prototype.handleRenameEmote = function (user, data) {
164 if (typeof data !== "object") {
165 return;
169 ** This shouldn't be able to happen,
170 ** but we have idiots that like to send handcrafted frames to fuck with shit
172 if (typeof data.old !== "string"){
173 return;
176 if (!this.channel.modules.permissions.canEditEmotes(user)) {
177 return;
180 var e = this.emotes.emoteExists(data);
181 var f = validateEmote(data);
182 if (!f || e) {
183 var message = "Unable to rename emote '" + JSON.stringify(data) + "'. " +
184 "Please contact an administrator for assistance.";
185 if (!data.image || !data.name) {
186 message = "Emote names and images must not be blank.";
188 if (e) {
189 message = "Emote already exists.";
192 user.socket.emit("errorMsg", {
193 msg: message,
194 alert: true
196 return;
199 // See comment above
200 var success = this.emotes.renameEmote(Object.assign({}, f));
201 if(!success){ return; }
203 this.dirty = true;
205 var chan = this.channel;
206 chan.broadcastAll("renameEmote", f);
207 chan.logger.log(`[mod] ${user.getName()} renamed emote: ${f.old} -> ${f.name}`);
210 EmoteModule.prototype.handleUpdateEmote = function (user, data) {
211 if (typeof data !== "object") {
212 return;
215 if (!this.channel.modules.permissions.canEditEmotes(user)) {
216 return;
219 var f = validateEmote(data);
220 if (!f) {
221 var message = "Unable to update emote '" + JSON.stringify(data) + "'. " +
222 "Please contact an administrator for assistance.";
223 if (!data.image || !data.name) {
224 message = "Emote names and images must not be blank.";
227 user.socket.emit("errorMsg", {
228 msg: message,
229 alert: true
231 return;
234 this.emotes.updateEmote(f);
236 this.dirty = true;
238 var chan = this.channel;
239 chan.broadcastAll("updateEmote", f);
241 chan.logger.log("[mod] " + user.getName() + " updated emote: " + f.name + " -> " +
242 f.image);
245 EmoteModule.prototype.handleImportEmotes = function (user, data) {
246 if (!(data instanceof Array)) {
247 return;
250 /* Note: importing requires a different permission node than simply
251 updating/removing */
252 if (!this.channel.modules.permissions.canImportEmotes(user)) {
253 return;
256 this.emotes.importList(data.map(validateEmote).filter(function (f) {
257 return f !== false;
258 }));
260 this.dirty = true;
262 this.sendEmotes(this.channel.users);
265 EmoteModule.prototype.handleRemoveEmote = function (user, data) {
266 if (typeof data !== "object") {
267 return;
270 if (!this.channel.modules.permissions.canEditEmotes(user)) {
271 return;
274 if (typeof data.name !== "string") {
275 return;
278 this.emotes.removeEmote(data);
280 this.dirty = true;
282 this.channel.logger.log("[mod] " + user.getName() + " removed emote: " + data.name);
283 this.channel.broadcastAll("removeEmote", data);
286 EmoteModule.prototype.handleMoveEmote = function (user, data) {
287 if (typeof data !== "object") {
288 return;
291 if (!this.channel.modules.permissions.canEditEmotes(user)) {
292 return;
295 if (typeof data.to !== "number" || typeof data.from !== "number") {
296 return;
299 this.emotes.moveEmote(data.from, data.to);
301 this.dirty = true;
304 module.exports = EmoteModule;