View difference between Paste ID: XayihBiQ and 1T6mcjia
SHOW: | | - or go back to the newest paste.
1
var sys = require('sys');
2
var https = require('https');
3
var url = require('url');
4
5
const ACTION_COOLDOWN = 3 * 1000;
6
const FLOOD_MESSAGE_NUM = 5;
7
const FLOOD_PER_MSG_MIN = 500; // this is the minimum time between messages for legitimate spam. It's used to determine what "flooding" is caused by lag
8
const FLOOD_MESSAGE_TIME = 6 * 1000;
9
const MIN_CAPS_LENGTH = 8;
10
const MIN_CAPS_PROPORTION = 0.8;
11
12
settings = {};
13
try {
14
    settings = JSON.parse(fs.readFileSync('settings.json'));
15
    if (!Object.keys(settings).length && settings !== {}) settings = {};
16
} catch (e) {} // file doesn't exist [yet]
17
18
messages = {};
19
try {
20
    messages = JSON.parse(fs.readFileSync('messages.json'));
21
    if (!Object.keys(messages).length && messages !== {}) messages = {};
22
} catch (e) {} // file doesn't exist [yet]
23
24
quotes = {};
25
try {
26
    quotes = JSON.parse(fs.readFileSync('quotes.json'));
27
    if (!Object.keys(quotes).length && quotes !== {}) quotes = {};
28
} catch (e) {} // file doesn't exist [yet]
29
30
exports.parse = {
31
    actionUrl: url.parse('https://play.pokemonshowdown.com/~~' + config.serverid + '/action.php'),
32
    room: 'lobby',
33
    'settings': settings,
34
    'messages': messages,
35
    'quotes': quotes,
36
    chatData: {},
37
    ranks: {},
38
    msgQueue: [],
39
40
    data: function(data, connection) {
41
        if (data.substr(0, 1) === 'a') {
42
            data = JSON.parse(data.substr(1));
43
            if (data instanceof Array) {
44
                for (var i = 0, len = data.length; i < len; i++) {
45
                    this.splitMessage(data[i], connection);
46
                }
47
            } else {
48
                this.splitMessage(data, connection);
49
            }
50
        }
51
    },
52
    splitMessage: function(message, connection) {
53
        if (!message) return;
54
55
        var room = 'lobby';
56
        if (message.indexOf('\n') < 0) return this.message(message, connection, room);
57
58
        var spl = message.split('\n');
59
60
        if (spl[0].charAt(0) === '>') {
61
            if (spl[1].substr(1, 4) === 'init') return ok('joined ' + spl[2].substr(7));
62
            if (spl[1].substr(1, 10) === 'tournament') return;
63
            room = spl.shift().substr(1);
64
        }
65
66
        for (var i = 0, len = spl.length; i < len; i++) {
67
            this.message(spl[i], connection, room);
68
        }
69
    },
70
    message: function(message, connection, room) {
71
        var spl = message.split('|');
72
        if (!spl[1]) {
73
            if (/was promoted to Room (Driver|Moderator|Owner)/i.test(spl[0])) this.say(connection, room, 'Congratulations on the promotion ' + spl[0].substr(0, spl[0].indexOf("was") - 1) + '! ^-^');
74
            if (/was promoted to Room Voice/i.test(spl[0])) this.say(connection, room, 'Welcome to THA, ' + spl[0].substr(0, spl[0].indexOf("was") - 1) + '! :3');
75
            if (/was muted by (Flowerbless~|DanceBoTTT|BriyellaBot)/i.test(spl[0])) {
76
                this.say(connection, room, 'Sorry, bot moderation is being worked on... here ya go! ^-^');
77
                this.say(connection, room, '/unmute ' + spl[0].substr(0, spl[0].indexOf("was") - 1));
78
            }
79
            spl = spl[0].split('>');
80
            if (spl[1]) this.room = spl[1];
81
            return;
82
        }
83
84
        switch (spl[1]) {
85
            case 'challstr':
86
                info('received challstr, logging in...');
87
                var id = spl[2];
88
                var str = spl[3];
89
90
                var requestOptions = {
91
                    hostname: this.actionUrl.hostname,
92
                    port: this.actionUrl.port,
93
                    path: this.actionUrl.pathname,
94
                    agent: false
95
                };
96
97
                if (!config.pass) {
98
                    requestOptions.method = 'GET';
99
                    requestOptions.path += '?act=getassertion&userid=' + toId(config.nick) + '&challengekeyid=' + id + '&challenge=' + str;
100
                } else {
101
                    requestOptions.method = 'POST';
102
                    var data = 'act=login&name=' + config.nick + '&pass=' + config.pass + '&challengekeyid=' + id + '&challenge=' + str;
103
                    requestOptions.headers = {
104
                        'Content-Type': 'application/x-www-form-urlencoded',
105
                        'Content-Length': data.length
106
                    };
107
                }
108
109
                var req = https.request(requestOptions, function(res) {
110
                    res.setEncoding('utf8');
111
                    var data = '';
112
                    res.on('data', function(chunk) {
113
                        data += chunk;
114
                    });
115
                    res.on('end', function() {
116
                        if (data === ';') {
117
                            error('failed to log in; nick is registered - invalid or no password given');
118
                            process.exit(-1);
119
                        }
120
                        if (data.length < 50) {
121
                            error('failed to log in: ' + data);
122
                            process.exit(-1);
123
                        }
124
125
                        if (data.indexOf('heavy load') !== -1) {
126
                            error('the login server is under heavy load; trying again in one minute');
127
                            setTimeout(function() {
128
                                this.message(message);
129
                            }.bind(this), 60 * 1000);
130
                            return;
131
                        }
132
133
                        if (data.substr(0, 16) === '<!DOCTYPE html>') {
134
                            error('Connection error 522; trying agian in one minute');
135
                            setTimeout(function() {
136
                                this.message(message);
137
                            }.bind(this), 60 * 1000);
138
                            return;
139
                        }
140
141
                        try {
142
                            data = JSON.parse(data.substr(1));
143
                            if (data.actionsuccess) {
144
                                data = data.assertion;
145
                            } else {
146
                                error('could not log in; action was not successful: ' + JSON.stringify(data));
147
                                process.exit(-1);
148
                            }
149
                        } catch (e) {}
150
                        send(connection, '|/trn ' + config.nick + ',0,' + data);
151
                    }.bind(this));
152
                }.bind(this));
153
154
                req.on('error', function(err) {
155
                    error('login error: ' + sys.inspect(err));
156
                });
157
158
                if (data) req.write(data);
159
                req.end();
160
                break;
161
            case 'updateuser':
162
                if (spl[2] !== config.nick) return;
163
164
                if (spl[3] !== '1') {
165
                    error('failed to log in, still guest');
166
                    process.exit(-1);
167
                }
168
169
                ok('logged in as ' + spl[2] + '^-^');
170
171
                this.msgQueue.push('|/blockchallenges');
172
                for (var i = 0, len = config.rooms.length; i < len; i++) {
173
                    var room = toId(config.rooms[i]);
174
                    if (room === 'lobby' && config.serverid === 'showdown') continue;
175
                    this.msgQueue.push('|/join ' + room);
176
                }
177
                for (var i = 0, len = config.privaterooms.length; i < len; i++) {
178
                    var room = toId(config.privaterooms[i]);
179
                    if (room === 'lobby' && config.serverid === 'showdown') continue;
180
                    this.msgQueue.push('|/join ' + room);
181
                }
182
                this.msgDequeue = setInterval(function() {
183
                    var msg = this.msgQueue.shift();
184
                    if (msg) return send(connection, msg);
185
                    clearInterval(this.msgDequeue);
186
                    this.msgDequeue = null;
187
                }.bind(this), 750);
188
                setInterval(this.cleanChatData.bind(this), 30 * 60 * 1000);
189
                break;
190
            case 'c':
191
                var by = spl[2];
192
                spl = spl.splice(3).join('|');
193
                this.processChatData(toId(by), room, connection, spl);
194
                this.chatMessage(spl, by, room, connection);
195
                if (toId(by) === toId(config.nick) && ' +%@#~'.indexOf(by.charAt(0)) > -1) this.ranks[room] = by.charAt(0);
196
                break;
197
            case 'c:':
198
                var by = spl[3];
199
                spl = spl.splice(4).join('|');
200
                this.processChatData(toId(by), room, connection, spl);
201
                this.chatMessage(spl, by, room, connection);
202
                if (toId(by) === toId(config.nick) && ' +%@#~'.indexOf(by.charAt(0)) > -1) this.ranks[room] = by.charAt(0);
203
                break;
204
            case 'pm':
205
                var by = spl[2];
206
                spl = spl.splice(4).join('|');
207
                if (toId(by) === toId(config.nick) && ' +%@#~'.indexOf(by.charAt(0)) > -1) this.ranks[room] = by.charAt(0);
208
                this.chatMessage(spl, by, ',' + by, connection);
209
                break;
210
            case 'N':
211
                var by = spl[2];
212
                this.updateSeen(spl[3], spl[1], toId(by));
213
                if (toId(by) !== toId(config.nick) || ' +%@&#~'.indexOf(by.charAt(0)) === -1) return;
214
                this.ranks[toId(this.room === '' ? 'lobby' : this.room)] = by.charAt(0);
215
                this.room = '';
216
                break;
217
            case 'J':
218
            case 'j':
219
                var by = spl[2];
220
                this.updateSeen(by, spl[1], (this.room === '' ? 'lobby' : this.room));
221
                if (toId(by) === toId(config.nick) && ' +%@&#~'.indexOf(by.charAt(0)) > -1) this.ranks[room] = by.charAt(0);
222
                if (['goddessbriyella', 'themansavage', 'omegaxis14'].indexOf(toId(by)) > -1) {
223
                    this.say(connection, room, '/w ' + toId(by) + ', **#warn** __is a new command that has been made for Moderators and Room Owners. To use the command, **PM Slowbro Bot** with this syntax:__ #warn **[user]**, **[warn reason]**');
224
                    this.say(connection, room, '/w ' + toId(by) + ', __This command is not to be abused and is for purely moderation purposes, thanks!^-^ ~Alpha-Kun__');
225
                }
226
                /*	if (toId(by) == 'goddessbriyella') this.say(connection, room, 'Haaii Briyella!^-^ I missed you ;~;');
227
				if (toId(by) == 'themansavage') this.say(connection, room, 'Hey Butch :3');
228
				if (toId(by) == 'omegaxis14') this.say(connection, room, '**om**e**g**a o3o hai :3');
229
				if (toId(by) == 'starbloom') this.say(connection, room, 'Haaii Kat^-^ How are you? :3');
230
				*/
231
                break;
232
            case 'l':
233
            case 'L':
234
                var by = spl[2];
235
                this.updateSeen(by, spl[1], (this.room === '' ? 'lobby' : this.room));
236
                this.room = '';
237
                break;
238
            case 'raw':
239
                if (/[3-9] ?days/i.test(spl[2])) this.say(connection, room, 'zarel pls ;-;');
240
                break;
241
            case 'tournament':
242
                if (/end\|\{"results":\[\[/i.test(spl[2])) this.say(connection, room, 'Good job ' + spl[2].substr(spl[2].indexOf("results") + 12, spl[2].indexOf("\"\],\[")) + ' on winning the tournament!^~^');
243
                break;
244
        }
245
    },
246
    chatMessage: function(message, by, room, connection) {
247
        var now = Date.now();
248
        var cmdrMessage = '["' + room + '|' + by + '|' + message + '"]';
249
        message = message.trim();
250
        // auto accept invitations to rooms
251
        if (room.charAt(0) === ',' && message.substr(0, 8) === '/invite ' && this.hasRank(by, '%@&~') && !(config.serverid === 'showdown' && toId(message.substr(8)) === 'lobby')) {
252
            this.say(connection, '', '/join ' + message.substr(8));
253
        }
254
        if (message.substr(0, config.commandcharacter.length) !== config.commandcharacter || toId(by) === toId(config.nick)) return;
255
256
        message = message.substr(config.commandcharacter.length);
257
        var index = message.indexOf(' ');
258
        var arg = '';
259
        if (index > -1) {
260
            var cmd = message.substr(0, index);
261
            arg = message.substr(index + 1).trim();
262
        } else {
263
            var cmd = message;
264
        }
265
266
        if (Commands[cmd]) {
267
            var failsafe = 0;
268
            while (typeof Commands[cmd] !== "function" && failsafe++ < 10) {
269
                cmd = Commands[cmd];
270
            }
271
            if (typeof Commands[cmd] === "function") {
272
                cmdr(cmdrMessage);
273
                Commands[cmd].call(this, arg, by, room, connection);
274
            } else {
275
                error("invalid command type for " + cmd + ": " + (typeof Commands[cmd]));
276
            }
277
        }
278
        /*		if (config.allowmute && this.hasRank(this.ranks[room] || ' ', '%@&#~') && config.whitelist.indexOf(user) === -1) {
279
        			var useDefault = !(this.settings['modding'] && this.settings['modding'][room]);
280
        		// moderation for abusing bot commands
281
        		for (cmd in Commands)
282
        			var isAbusing = (this.chatData.commandFlooding[room][cmd].times.length >= 6 && (time - this.chatData.commandFlooding[room][cmd].times[this.chatData.commandFlooding[room][cmd].times.length - 10]) < 30 * 1000
283
        				&& (time - this.chatData.commandFlooding[room][cmd].times[this.chatData.commandFlooding[room][cmd].times.length - 6]) > (6 * 30 * 1000));
284
        			if ((useDefault || this.settings['modding'][room]['flooding'] !== 0) && isAbusing) this.say(connection, room, '/wall Please stop abusing bot commands, they have all been set to # until things settle down again.');
285
        		} */
286
    },
287
    say: function(connection, room, text) {
288
        if (room.charAt(0) !== ',') {
289
            var str = (room !== 'lobby' ? room : '') + '|' + text;
290
            send(connection, str);
291
        } else {
292
            room = room.substr(1);
293
            var str = '|/pm ' + room + ', ' + text;
294
            send(connection, str);
295
        }
296
    },
297
    shorten: function(link) {
298
        var BitlyAPI = require("node-bitlyapi");
299
        var Bitly = new BitlyAPI({
300
            client_id: "Something",
301
            client_secret: "Something"
302
        });
303
        var self = this;
304
        Bitly.setAccessToken("c8a15558cbf4a555391b974849d7684e211fb707");
305
        Bitly.shortenLink(link, function(err, results) {
306
            var resObject = eval("(" + results + ")");
307
            console.log('url: ' + resObject.data.url);
308
            bitLink += resObject.data.url;
309
            console.log('bitLink: ' + bitLink);
310
        });
311
    },
312
    hasRank: function(user, rank) {
313
        var hasRank = (rank.split('').indexOf(user.charAt(0)) !== -1) || (config.excepts.indexOf(toId(user)) !== -1);
314
        return hasRank;
315
    },
316
    canUse: function(cmd, room, user) {
317
        var canUse = false;
318
        var ranks = ' +%@&#~';
319
        if (!this.settings[cmd] || !this.settings[cmd][room]) {
320
            canUse = this.hasRank(user, ranks.substr(ranks.indexOf((cmd === 'autoban' || cmd === 'banword') ? '#' : config.defaultrank)));
321
        } else if (this.settings[cmd][room] === true) {
322
            canUse = true;
323
        } else if (ranks.indexOf(this.settings[cmd][room]) > -1) {
324
            canUse = this.hasRank(user, ranks.substr(ranks.indexOf(this.settings[cmd][room])));
325
        }
326
        return canUse;
327
    },
328
    sendMail: function(user, room) {
329
        if (!this.messages || !this.messages[user]) return false;
330
        if (this.messages[user]) {
331
            console.log(user + ' has mail.');
332
            return true;
333
        }
334
    },
335
    processChatData: function(user, room, connection, msg, by) {
336
        var botName = msg.toLowerCase().indexOf(toId(config.nick));
337
338
        if (toId(user.substr(1)) === toId(config.nick)) {
339
            this.ranks[room] = user.charAt(0);
340
            return;
341
        }
342
        var by = user;
343
        user = toId(user);
344
        var user = toId(by);
345
346
        if (!user || room.charAt(0) === ',') return;
347
        room = toId(room);
348
        msg = msg.trim().replace(/[ \u0000\u200B-\u200F]+/g, ' '); // removes extra spaces and null characters so messages that should trigger stretching do so
349
350
        this.updateSeen(user, 'c', room);
351
        var now = Date.now();
352
        if (!this.chatData[user]) this.chatData[user] = {
353
            zeroTol: 0,
354
            lastSeen: '',
355
            seenAt: now
356
        };
357
358
        var userData = this.chatData[user];
359
        if (!this.chatData[user][room]) this.chatData[user][room] = {
360
            times: [],
361
            points: 0,
362
            lastAction: 0
363
        };
364
365
        var roomData = userData[room];
366
        roomData.times.push(now);
367
        this.chatData[user][room].times.push(now);
368
369
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
370
        /////// Regex /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
371
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
372
373
        //Greetings & Farewells
374
        if (/(good)? ?(night|nite) (everyone|guys|friends|all)/i.test(msg) && toId(config.nick) !== toId(by)) this.say(connection, room, 'Goodnight ' + by + '^-^');
375
        if (/i(\'?m| am).*go.*to (bed|sleep)/i.test(msg) && toId(config.nick) !== toId(by)) this.say(connection, room, 'Goodnight ' + by + '^-^');
376
 	if (/i(\'?m| am).*go.*to (eat)/i.test(msg) && toId(config.nick) !== toId(by)) this.say(connection, room, 'omnomnom Have a nice meal ' + by + '^-^');
377
        if (/(hey|hi|hello|ha+?i+) (everyone|guys|friends|all)/i.test(msg) && toId(config.nick) !== toId(by)) this.say(connection, room, 'Haaii ' + by + '^-^');
378
        if (/(bye|g2g|gtg|ba+?i+) (everyone|guys|friends|all)/i.test(msg) && toId(config.nick) !== toId(by)) this.say(connection, room, 'Baaii ' + by + '~');
379
        if (/g2g/i.test(msg) && toId(config.nick) !== toId(by)) this.say(connection, room, 'Baaii ' + by + '~');
380
        if (/how(\'re)? (r|are|is) (u|you|chu)?/i.test(msg)) this.say(connection, room, 'I am good, how about you ' + by + '? :o');
381
382
383
        //Miscellaneous
384
        if (/(why are there )?so many bots( in here)?\??/i.test(msg)) this.say(connection, room, 'Sorry if I\'m intruding, I\'ll try and be as quiet as possible! >~<');
385
        if (/(mashiro|mashy|goddess ?mashiro)/i.test(msg) && isAfk == true) this.say(connection, room, '/w ' + by + ', Mashiro-chan is AFK right now, leave a PM or check back in a bit, thanks^-^');
386
        if (/(why are there )?so many goddess(es)?( in here)?\??/i.test(msg)) this.say(connection, room, 'Mashiro is just a Briyella wannabe o3o');
387
        if (/(9[0-9]|100)% compatible/i.test(msg)) {
388
            var rand = ~~(2 * Math.random()) + 1;
389
            if (rand == 1) this.say(connection, room, '__it was meant to be :O__');
390
            if (rand == 2) this.say(connection, room, '/me plays wedding music');
391
        }
392
        if (/(1| )[0-9]% compatible/i.test(msg)) this.say(connection, room, '__rip ;-;__');
393
        if (/I(\'?m| am) back/i.test(msg)) this.say(connection, room, 'Hi back, I am Slowbro Bot o3o');
394
        if (/I(\'?m| am) tired/i.test(msg)) this.say(connection, room, 'Hi tired, I am Slowbro Bot o3o');
395
        if (/I(\'?m| am) hungry/i.test(msg)) this.say(connection, room, 'Hi hungry, I am Slowbro Bot o3o');
396
        if (/(cut|kick|punch(es)?|hit|hurt|slap|stab)s? (goddess)? ?mash(y|iro)/i.test(msg)) this.say(connection, room, 'D-don\'t hurt my creator..!! >~<');
397
        if (/\brip\b/i.test(msg) && toId(by) !== toId('mashibot')) {
398
            var rand = ~~(13 * Math.random()) + 1;
399
            if (rand == 1) this.say(connection, room, '__rip in pepperoni ;-;__');
400
            if (rand == 2) this.say(connection, room, '__rip in pieces ;-;__');
401
            if (rand == 3) this.say(connection, room, '__rip in pepsi ;-;__');
402
            if (rand == 4) this.say(connection, room, '__rip in PixelMoniac ;-;__');
403
            if (rand == 5) this.say(connection, room, '__rip in pizza ;-;__');
404
            if (rand == 6) this.say(connection, room, '__rip in pringles ;-;__');
405
            if (rand == 7) this.say(connection, room, '__rip in pretzels ;-;__');
406
            if (rand == 8) this.say(connection, room, '__rip in pistachios ;-;__');
407
            if (rand == 9) this.say(connection, room, '__rip in pasta ;-;__');
408
            if (rand == 10) this.say(connection, room, '__rip in peaches ;-;__');
409
            if (rand == 11) this.say(connection, room, '__rip in pumpkins ;-;__');
410
            if (rand == 12) this.say(connection, room, '__rip in papayas ;-;__');
411
            if (rand == 13) this.say(connection, room, '__rip in potatoes ;-;__');
412
        }
413
        if (/\bej\b/i.test(msg) && toId(by) !== toId('mashibot')) {
414
            var rand = ~~(13 * Math.random()) + 1;
415
            if (rand == 1) this.say(connection, room, '__andy saks__');
416
            if (rand == 2) this.say(connection, room, '__pindakaas__');
417
            if (rand == 3) this.say(connection, room, '__IK WIL KOEKJES__');
418
            if (rand == 4) this.say(connection, room, '__slooowbrooo__');
419
            if (rand == 5) this.say(connection, room, '__plalalalala__');
420
            if (rand == 6) this.say(connection, room, '__lolololol__');
421
            if (rand == 7) this.say(connection, room, '__keukenrol__');
422
            if (rand == 8) this.say(connection, room, '__banaan__');
423
            if (rand == 9) this.say(connection, room, '__ontbijtkoek__');
424
            if (rand == 10) this.say(connection, room, '__I like turltes__');
425
        }
426
        //		if (/(rekt|burn)/i.test(msg)) this.say(connection, room, '!data Rawst Berry');
427
428
        //Favorite Pokemon
429
        if (/what(\'s| is)? (goddess ?)?mash(i|y|iro)?(chan|bot)?\'?s? fav(e|ou?rite)? poke(mon)?\??/i.test(msg)) this.say(connection, room, '!data Ninetales');
430
        if (/what(\'s| is)? (goddess ?)?bri(yella)?\'?s? fav(e|ou?rite)? poke(mon)?\??/i.test(msg)) this.say(connection, room, '!data Vespiquen');
431
        if (/what(\'s| is)? omega-?(xis14)?\'?s? fav(e|ou?rite)? poke(mon)?\??/i.test(msg)) this.say(connection, room, '!data Mew');
432
        if (/what(\'s| is)? (the|butch) ?mansavage\'?s? fav(e|ou?rite)? poke(mon)?\??/i.test(msg) || /what(\'s| is)? butch\'?s? fav(ou?rite)? poke(mon)?\??/i.test(msg)) this.say(connection, room, '!data Rhyperior');
433
434
        //League Names
435
        if (/(does)? ?(some|any)(one|1|body) play (league( of legends)?|lol)/i.test(msg)) this.say(connection, room, 'Add Mashiro-chan on League if you want to play: LeInfiniti');
436
        if (/what(\'s| is)? jess(ilina| league)?\'?s? (league( of legends)?|lol)( name)?/i.test(msg)) this.say(connection, room, 'Jess\'s League name is: namegohere');
437
438
        //osu! room
439
        if (/(pronounce|say) osu/i.test(msg)) {
440
            if (room !== 'osu') return false;
441
            this.say(connection, room, 'osu! is pronounced like \"os\", not \"osu\". This is because when a \'u\' follows an \'s\' in Japanese, the \'u\' is silent.');
442
        }
443
        if (/what(\'s| is)? osu/i.test(msg)) {
444
            if (room !== 'osu') return false;
445
            this.say(connection, room, 'osu! is a Japanese rhythm game where the player hits notes in time with the beat of the music. There are 5 different game modes, the most popular being standard osu! and osu! mania.');
446
        }
447
448
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
449
        /////// /me Regex /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
450
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
451
452
	if (msg.toLowerCase().indexOf(toId(config.nick)) > -1 && toId(by) !== toId(config.nick)) {
453
	if (/^\/me/i.test(msg) && user !== botName) {
454
                if (/(pet|stroke)s?/i.test(msg)) {
455
                    this.say(connection, room, '/me purr'); return;}
456
457
                if (/licks?/i.test(msg)) {
458
                  this.say(connection, room, '/me squirms ;~;'); return;}
459-
                    this.say(connection, room, '/me blushes deeply'); return;}
459+
460
                if (/(kiss(es)?|kissu)/i.test(msg)) {
461
                    this.say(connection, room, '/me blushes deeply');
462-
                    this.say(connection, room, 'nuuu dun eat me ;~;'); return;}
462+
463
464
                if (/(eat|nom|nibble)s?/i.test(msg)) {
465
                    this.say(connection, room, 'nuuu dun eat me ;~;');
466
                    this.say(connection, room, '/me hides'); return;}
467
468
                if (/(hit|stab|punch|kick|hurt)s?/i.test(msg)) {
469
                    this.say(connection, room, '/me cries in pain ;-;'); return;}
470
471
                if (/(hug|glomp|squeeze)s?/i.test(msg)) {
472
                    this.say(connection, room, '/me squee~ :3'); return;}
473
474
                if (/(cuddle|snuggle)s?/i.test(msg)) {
475-
                    this.say(connection, room, '/me giggles and squirms'); return;}
475+
476
477
                if (/pokes?/i.test(msg)) {
478
                    this.say(connection, room, 'oww!! >~<'); return;}
479
480
                if (/(gives? food|a cookie)/i.test(msg)) {
481
                    this.say(connection, room, '/me noms :3'); return;}
482
483
                if (/(tickle)s?/i.test(msg)) {
484
                    this.say(connection, room, '/me giggles and squirms');
485
                    this.say(connection, room, 'Staaahhhpp!! ;~;'); return;}
486
            }
487
        }
488
489
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
490
        /////// Moderation Detection //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
491
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////		
492
493
        /* 			// this deals with punishing rulebreakers, but note that the bot can't think, so it might make mistakes
494
        		if (config.allowmute && this.hasRank(this.ranks[room] || ' ', '%@&#~') && config.whitelist.indexOf(user) === -1) {
495
        			var useDefault = !(this.settings['modding'] && this.settings['modding'][room]);
496
        			var pointVal = 0;
497
        			var muteMessage = '';
498
        			var warnMessage = '';
499
        			
500
        			// moderation for flooding (more than x lines in y seconds)
501
        			var times = roomData.times;
502
			var timesLen = times.length;
503
			var isFlooding = (timesLen >= FLOOD_MESSAGE_NUM && (now - times[timesLen - FLOOD_MESSAGE_NUM]) < FLOOD_MESSAGE_TIME &&
504
				(now - times[timesLen - FLOOD_MESSAGE_NUM]) > (FLOOD_PER_MSG_MIN * FLOOD_MESSAGE_NUM));
505
			if ((useDefault || !('flooding' in modSettings)) && isFlooding) {
506
				if (pointVal < 2) {
507
					pointVal = 2;
508
					muteMessage = ', Please stop flooding the chat! ;~;';
509
        				}
510
        			} */
511
512
        // moderation for caps (over x% of the letters in a line of y characters are capital)
513
        /*	var capsMatch = msg.replace(/[^A-Za-z]/g, '').match(/[A-Z]/g);
514
			if ((useDefault || !('caps' in modSettings)) && capsMatch && toId(msg).length > MIN_CAPS_LENGTH && (capsMatch.length >= ~~(toId(msg).length * MIN_CAPS_PROPORTION))) {
515
				if (pointVal < 1) {
516
					pointVal = 1;
517
					muteMessage = ', Don\'t be so loud wtf ;-;';
518
				}
519
			}
520
			// moderation for stretching (over x consecutive characters in the message are the same)
521
			var stretchMatch = /(.)\1{7,}/gi.test(msg) || /(..+)\1{4,}/gi.test(msg); // matches the same character (or group of characters) 8 (or 5) or more times in a row
522
			if ((useDefault || !('stretching' in modSettings)) && stretchMatch) {
523
				if (pointVal < 1) {
524
					pointVal = 1;
525
					muteMessage = ', This isn\'t yoga, no streching please ._.';
526
				}
527
			}
528
529
			if (pointVal > 0 && !(now - this.chatData[user][room].lastAction < ACTION_COOLDOWN)) {
530
				var cmd = 'mute';
531
				// defaults to the next punishment in config.punishVals instead of repeating the same action (so a second warn-worthy
532
				// offence would result in a mute instead of a warn, and the third an hourmute, etc)
533
				if (this.chatData[user][room].points >= pointVal && pointVal < 4) {
534
					this.chatData[user][room].points++;
535
					cmd = config.punishvals[this.chatData[user][room].points] || cmd;
536
				} else { // if the action hasn't been done before (is worth more points) it will be the one picked
537
					cmd = config.punishvals[pointVal] || cmd;
538
					this.chatData[user][room].points = pointVal; // next action will be one level higher than this one (in most cases)
539
				}
540
				if (config.privaterooms.indexOf(room) >= 0 && cmd === 'warn') cmd = 'mute'; // can't warn in private rooms
541
				// if the bot has % and not @, it will default to hourmuting as its highest level of punishment instead of roombanning
542
				if (this.chatData[user][room].points >= 4 && !this.hasRank(this.ranks[room] || ' ', '@&#~')) cmd = 'hourmute';
543
				if (this.chatData[user].zeroTol > 4) { // if zero tolerance users break a rule they get an instant roomban or hourmute
544
					muteMessage = ', Automated response: zero tolerance user';
545
					cmd = this.hasRank(this.ranks[room] || ' ', '@&#~') ? 'roomban' : 'hourmute';
546
				}
547
				if (this.chatData[user][room].points >= 2) this.chatData[user].zeroTol++; // getting muted or higher increases your zero tolerance level (warns do not)
548
				this.chatData[user][room].lastAction = now;
549
				if (this.chatData[user][room].points <= 2) this.say(connection, room, user + ', ' + warnMessage);
550
				if (this.chatData[user][room].points >= 3) this.say(connection, room, '/' + cmd + ' ' + user + muteMessage);
551
			}
552
		 } */
553
    },
554
    cleanChatData: function() {
555
556
        var chatData = this.chatData;
557
        for (var user in chatData) {
558
            for (var room in chatData[user]) {
559
                var roomData = chatData[user][room];
560
                if (!Object.isObject(roomData)) continue;
561
562
                if (!roomData.times || !roomData.times.length) {
563
                    delete chatData[user][room];
564
                    continue;
565
                }
566
                var newTimes = [];
567
                var now = Date.now();
568
                var times = roomData.times;
569
                for (var i = 0, len = times.length; i < len; i++) {
570
                    if (now - times[i] < 5 * 1000) newTimes.push(times[i]);
571
                }
572
                newTimes.sort(function(a, b) {
573
                    return a - b;
574
                });
575
                roomData.times = newTimes;
576
                if (roomData.points > 0 && roomData.points < 4) roomData.points--;
577
            }
578
        }
579
    },
580
    updateSeen: function(user, type, detail) {
581
        if (type !== 'n' && config.rooms.indexOf(detail) === -1 || config.privaterooms.indexOf(toId(detail)) > -1) return;
582
        var now = Date.now();
583
        if (!this.chatData[user]) this.chatData[user] = {
584
            zeroTol: 0,
585
            lastSeen: '',
586
            seenAt: now
587
        };
588
        if (!detail) return;
589
        var userData = this.chatData[user];
590
        var msg = '';
591
        switch (type) {
592
            case 'j':
593
            case 'J':
594
                msg += 'joining ';
595
                break;
596
            case 'l':
597
            case 'L':
598
                msg += 'leaving ';
599
                break;
600
            case 'c':
601
            case 'c:':
602
                msg += 'chatting in ';
603
                break;
604
            case 'N':
605
                msg += 'changing nick to ';
606
                if (detail.charAt(0) !== ' ') detail = detail.substr(1);
607
                break;
608
        }
609
        msg += detail.trim() + '.';
610
        userData.lastSeen = msg;
611
        userData.seenAt = now;
612
    },
613
    getTimeAgo: function(time) {
614
        time = ~~((Date.now() - time) / 1000);
615
616
        var seconds = time % 60;
617
        var times = [];
618
        if (seconds) times.push(seconds + (seconds === 1 ? ' second' : ' seconds'));
619
        if (time >= 60) {
620
            time = ~~((time - seconds) / 60);
621
            var minutes = time % 60;
622
            if (minutes) times.unshift(minutes + (minutes === 1 ? ' minute' : ' minutes'));
623
            if (time >= 60) {
624
                time = ~~((time - minutes) / 60);
625
                hours = time % 24;
626
                if (hours) times.unshift(hours + (hours === 1 ? ' hour' : ' hours'));
627
                if (time >= 24) {
628
                    days = ~~((time - hours) / 24);
629
                    if (days) times.unshift(days + (days === 1 ? ' day' : ' days'));
630
                }
631
            }
632
        }
633
        if (!times.length) return '0 seconds';
634
        return times.join(', ');
635
    },
636
    writeSettings: (function() {
637
        var writing = false;
638
        var writePending = false; // whether or not a new write is pending
639
        var finishWriting = function() {
640
            writing = false;
641
            if (writePending) {
642
                writePending = false;
643
                this.writeSettings();
644
            }
645
        };
646
        return function() {
647
            if (writing) {
648
                writePending = true;
649
                return;
650
            }
651
            writing = true;
652
            var data = JSON.stringify(this.settings);
653
            fs.writeFile('settings.json.0', data, function() {
654
                // rename is atomic on POSIX, but will throw an error on Windows
655
                fs.rename('settings.json.0', 'settings.json', function(err) {
656
                    if (err) {
657
                        // This should only happen on Windows.
658
                        fs.writeFile('settings.json', data, finishWriting);
659
                        return;
660
                    }
661
                    finishWriting();
662
                });
663
            });
664
        };
665
    })(),
666
    writeMessages: (function() {
667
        var writing = false;
668
        var writePending = false; // whether or not a new write is pending
669
        var finishWriting = function() {
670
            writing = false;
671
            if (writePending) {
672
                writePending = false;
673
                this.writeMessages();
674
            }
675
        };
676
        return function() {
677
            if (writing) {
678
                writePending = true;
679
                return;
680
681
            }
682
            writing = true;
683
            var data = JSON.stringify(this.messages);
684
            fs.writeFile('messages.json.0', data, function() {
685
                // rename is atomic on POSIX, but will throw an error on Windows
686
                fs.rename('messages.json.0', 'messages.json', function(err) {
687
                    if (err) {
688
                        // This should only happen on Windows.
689
                        fs.writeFile('messages.json', data, finishWriting);
690
                        return;
691
                    }
692
                    finishWriting();
693
                });
694
            });
695
        };
696
    })(),
697
    writeQuotes: (function() {
698
        var writing = false;
699
        var writePending = false; // whether or not a new write is pending
700
        var finishWriting = function() {
701
            writing = false;
702
            if (writePending) {
703
                writePending = false;
704
                this.writeQuotes();
705
            }
706
        };
707
        return function() {
708
            if (writing) {
709
                writePending = true;
710
                return;
711
712
            }
713
            writing = true;
714
            var data = JSON.stringify(this.quotes);
715
            fs.writeFile('quotes.json.0', data, function() {
716
                // rename is atomic on POSIX, but will throw an error on Windows
717
                fs.rename('quotes.json.0', 'quotes.json', function(err) {
718
                    if (err) {
719
                        // This should only happen on Windows.
720
                        fs.writeFile('quotes.json', data, finishWriting);
721
                        return;
722
                    }
723
                    finishWriting();
724
                });
725
            });
726
        };
727
    })(),
728
    uncacheTree: function(root) {
729
        var uncache = [require.resolve(root)];
730
        do {
731
            var newuncache = [];
732
            for (var i = 0; i < uncache.length; ++i) {
733
                if (require.cache[uncache[i]]) {
734
                    newuncache.push.apply(newuncache,
735
                        require.cache[uncache[i]].children.map(function(module) {
736
                            return module.filename;
737
                        })
738
                    );
739
                    delete require.cache[uncache[i]];
740
                }
741
            }
742
            uncache = newuncache;
743
        } while (uncache.length > 0);
744
    },
745
    getDocMeta: function(id, callback) {
746
        https.get('https://www.googleapis.com/drive/v2/files/' + id + '?key=' + config.googleapikey, function(res) {
747
            var data = '';
748
            res.on('data', function(part) {
749
                data += part;
750
            });
751
            res.on('end', function(end) {
752
                var json = JSON.parse(data);
753
                if (json) {
754
                    callback(null, json);
755
                } else {
756
                    callback('Invalid response', data);
757
                }
758
            });
759
        });
760
    },
761
    getDocCsv: function(meta, callback) {
762
        https.get('https://docs.google.com/spreadsheet/pub?key=' + meta.id + '&output=csv', function(res) {
763
            var data = '';
764
            res.on('data', function(part) {
765
                data += part;
766
            });
767
            res.on('end', function(end) {
768
                callback(data);
769
            });
770
        });
771
    }
772
};