User:Dorumin/chat.js

importArticles({   type: 'script',    articles: [        'u:dev:PrivateHistory/code.js',        'u:dev:FaviconNotifier/code.js',        'u:dev:IsTyping/code.js',        'u:dev:Tabinsert.js', // WTF WHY ISNT IT TabInsert.js???        'u:dev:ExtendedPrivateMessaging/code.js',        'u:dev:PrivateMessageAlert/code.js',        'u:dev:EscapeEmoticons/code.js',        'u:dev:EmoticonsWindow/code.js'    ] });

/* IsTyping */ window.IsTyping = { $indicator: $(' ', {       class: 'typing-indicator'    }).appendTo('body'), noStyle: true, doScroll: false };

/* Pings */ window.PING_REGEX = /doru|dorium|\bdor\b|\bdrumi|betterthancube/i; window.AUDIO_URL = 'https://vignette.wikia.nocookie.net/embercastle/images/9/9d/Chuggington-Koko\'s_Horn.ogg';

(function reload {   if (!window.mainRoom) {        setTimeout(reload, 200);        return;    }    mainRoom.model.chats.bind('afteradd', function { var div = mainRoom.viewDiscussion.chatDiv.get(0); if (div.scrollHeight - div.scrollTop - div.clientHeight < 200) { mainRoom.viewDiscussion.scrollToBottom; }   });

mainRoom.model.privateUsers.bind('add', function(u) {       var room = mainRoom.chats.privates[u.attributes.roomId];        room.model.chats.bind('afteradd', function { var div = room.viewDiscussion.chatDiv.get(0); if (div.scrollHeight - div.scrollTop - div.clientHeight < 500) { room.viewDiscussion.scrollToBottom; }       });    });

// My homegrown LightBlock which actually removes continued messages var blocks = JSON.parse(localStorage.getItem('chat-blocks') || '[]'); mainRoom.viewDiscussion.getTextInput.on('keypress', function(e) {       if (e.which != 13) return;        var split = this.value.split(' '),        command = split[0],        rest = split.slice(1).join(' ');        switch (command) {            case '/block':                e.preventDefault;                this.value = ;                if (blocks.includes(rest)) {                    inline(rest + ' is already blocked.');                } else {                    blocks.push(rest);                    localStorage.setItem('chat-blocks', JSON.stringify(blocks));                    inline('Blocked ' + rest + '.');                    inline('Currently blocked users: ' + blocks.join(', '));                }                break;            case '/unblock':                e.preventDefault;                this.value = ;                var index = blocks.indexOf(rest);                if (index === -1) { inline(rest + ' is not blocked.'); } else { blocks.splice(index, 1); localStorage.setItem('chat-blocks', JSON.stringify(blocks)); inline('Unblocked ' + rest + '.'); inline('Currently blocked users: ' + blocks.join(', ')); }               break; case '/blocks': e.preventDefault; this.value = ''; inline('Currently blocked users: ' + blocks.join(', ')); break; }   });    mainRoom.model.chats.bind('afteradd', function(model) { if (blocks.includes(model.attributes.name)) { mainRoom.model.chats.remove(model); // No backing out }   });    function inline(text) {        mainRoom.model.chats.add(new models.InlineAlert({            text: text,            synthetic: true        }));    }    ['main', 'private'].forEach(function(scope) { mainRoom.viewUsers.bind(scope + 'ListClick', function(e) {           $('#UserStatsMenu img').attr('src', function(_, src) { return src.replace('/scale-to-width-down/28', '/scale-to-width-down/150'); });           var user = mainRoom.model.users.findByName(e.name);			if (!user) return; // wtf			$('#UserStatsMenu .info').append(' ');			user.attributes.groups.forEach(function(group) { $('#UserStatsMenu .groups').append($(' ', { 'class': 'group', 'data-group': group }));           });            $('#UserStatsMenu').offset({ top: $('#UserStatsMenu').offset.top - $('#UserStatsMenu .groups').height - 4 });       });    }); });

mw.hook('dev.chat.render').add(function {   var button = new dev.chat.Button({ name: 'AFKButton', attr: { text: 'AFK', click: toggleAway }   }).el;    mainRoom.model.chats.bind('afteradd', function(model) { var userMain = mainRoom.model.users.findByName(wgUserName); if (model.attributes.name == wgUserName && userMain && userMain.attributes.statusState == 'away') { setBack; }   });    var sb = mainRoom.setBack;    function toggleAway {        var current = mainRoom.model.users.findByName(wgUserName).attributes.statusState; // No, mainRoom.userMain doesn't work        if (current == 'away') {            setBack;        } else {            setAway;        }    }    function setAway {        mainRoom.setAway;        clearTimeout(mainRoom.activityTimeout);        mainRoom.setBack = $.noop;        button.textContent = 'Back';    }    function setBack {        mainRoom.setBack;        mainRoom.activityTimer = setTimeout($.proxy(mainRoom.setAway, mainRoom), 5 * 60 * 1000);        mainRoom.setBack = sb;        mainRoom.setBack;        button.textContent = 'AFK';    }    mainRoom.socket.on("updateUser", function(msg) { var data = JSON.parse(msg.data).attrs, type = data.statusMessage, status = data.statusState, user = data.name; if (user == wgUserName && status != 'away') { mainRoom.setBack = sb; button.textContent = 'AFK'; }   });    /* Pings */    var avatars = {};

if (Notification.permission === 'default') { Notification.requestPermission; }

function parseInlineAlertRegex(elem) { // We could probably make it prettier, but as long as it's abstracted inside this function we probably won't need to deal with it again var msg = mw.messages.get(elem) .replace(/[-[\]{}*+?.,\\^$|#\s]/g, '\\$&') //escape regex .replace(/(\\\$\d)(?!.*\1)/g, '(.+?)') //place capturing groups .replace(/\\\$\d/g, '.+?'); //replace remaining $Ns

return new RegExp(msg); }

function notify(title, text, icon) { audio.play['catch'](console.log);

if (Notification.permission !== 'granted') { return; }       var notification = new Notification(title, {            body: text,            icon: icon        }); notification.onclick = function { window.focus; notification.close; };   }

function formatAvi(avatar) { // Aaanyways, this regex matches only the last occurrence of 28 in the string // (?!) means a negative lookahead, so it looks ahead of the match for any occurrences of (any character) times 0 to infinity // immediately followed by 28 return avatar.replace(/28(?!.*28)/, '150'); }

function highlight($elem, match) { var $message = $elem, html = $message.html, lastIndex = match.index + match[0].length, text = html.slice(match.index, lastIndex), span = $(' ', {               id: 'ping',                text: text,                css: {                    color: 'red'                }            }).get(0);

html = html.slice(0, match.index) + span.outerHTML + html.slice(lastIndex); $message.html(html); }

function saveAvatar(user) { avatars[user.attributes.name] = formatAvi(user.attributes.avatarSrc); }

var kick_regex = parseInlineAlertRegex('chat-user-was-kicked'), ban_regex = parseInlineAlertRegex('chat-user-was-banned'), audio = new Audio(AUDIO_URL);

function afterChat(chat) { var $elem = $('#entry-' + chat.cid).find('.message'), html = $elem.html, text = chat.attributes.text, name = chat.attributes.name, icon = formatAvi(chat.attributes.avatarSrc), inline = chat.attributes.isInlineAlert || false, match; if (document.hasFocus && mainRoom.active) return;

if (name !== mw.config.get('wgUserName') && !inline) { if (match = html.match(PING_REGEX)) { notify(name + ' pinged you!', text, icon); highlight($elem, match); }       } else if (inline) { if (match = text.match(kick_regex)) { notify(match[1] + ' was kicked!', '', avatars[match[1]]); } else if (match = text.match(ban_regex)) { notify(match[1] + ' was banned!', '', avatars[match[1]]); }       }    }    mainRoom.model.chats.bind('afteradd', afterChat); mainRoom.model.users.models.forEach(saveAvatar); mainRoom.model.users.bind('add', saveAvatar); });