User:Monchoman45/ChatHacks.js

/* * ChatHacks.js, by Monchoman45. * Version 5.0.1 * * Features: *  Ability to clear your chat window *  A host of slash commands (including /me and /afk) *  Inline alerts every time your away status changes *  Icons next to messages by staff and chat mods in the room *  Pings when someone mentions your name or any other specified phrases */ $(function {		$('body').prepend('\n.stafficon {\n\tmargin-bottom:-4px;\n}\n.modicon {\n\tmargin-bottom:-2px;\n}\n.UserStatsMenu {\n\tcolor:#000;\n}\n.Chat .continued {\n\ttop:0;\n\tmargin-bottom:0;\n}\n.Write [name="message"] {\n\twidth:93%;\n}\n.inline-alert span {\n\tcolor:#006CB0;\n\tcursor:pointer;\n}\n.inline-alert span:hover {\n\ttext-decoration:underline;\n}\n#pingspan {\n\tposition:absolute;\n\tz-index:5;\n\ttop:23px;\n\tmargin-left:15px;\n\tfont-size:15px;\n\tfont-weight:normal;\n\tline-height:15px;\n}\n#pings {\n\tresize:none;\n\tmargin-left:0;\n\tfont-size:12px;\n\theight:100px;\n\tdisplay:block;\n}\n#pingspan div span {\n\tfont-size:55%;\n}\n ');		//Add a 'go afk' button and a 'clear chat' button		$('#Write').append('AFKClear'); //Unbind all of the window listeners that set your status to back $(window).unbind('mousemove').unbind('focus').unbind('keypress'); //Add the sound space $('body').append(' '); //Add the area for adding more pings $('#ChatHeader .wordmark').append(' Ping phrases (?) ' + ((readCookie('pingphrases')) ? readCookie('pingphrases').split('\\n').join('\n') : wgUserName) + ' '); document.getElementById('pingspan').onmouseover = function(event) {document.getElementById('pings').style.display = 'block';} document.getElementById('pingspan').onmouseout = function(event) {document.getElementById('pings').style.display = 'none'; createCookie('pingphrases', document.getElementById('pings').value.split('\n').join('\\n'), 99999);} });

if(wgPageName == 'Special:Chat') { window.canding = false; //to prevent pinging people with scrollback setTimeout('window.canding = true', 10000); //lazy load quick fix window.titleorig = document.title; function Unding { //for fixing the title after you've been dinged document.getElementsByTagName('title')[0].innerHTML = window.titleorig; clearInterval(window.ding); }	$(window).bind('focus', Unding); window.ding = 0; window.staff = []; window.mods = [];

//Make a list of mods and staff NodeRoomController.prototype.onInitial = function(message) { if(!this.isInitialized){ _.each(this.model.chats.models, $.proxy(function(data) { this.model.chats.remove(data); },this)); this.model.chats.trigger('clear'); // On first connection, just update the entire model. this.model.mport(message.data); this.isInitialized = true; } else { // If this is a reconnect... go through the model that was given and selectively, only add ChatEntries that were not already in the collection of chats. var jsonObj = JSON.parse(message.data); var chatEntries = this.model.chats; _.each(jsonObj.collections.chats.models, function(item, index){				var match = chatEntries.get(item.id);				if(typeof match == "undefined"){					$.log("Found a ChatEntry that must have occurred during reconnection. Adding it to the model...");					var additionalEntry = new models.ChatEntry;					additionalEntry.mport( JSON.stringify(item) );					chatEntries.add(additionalEntry);				}			}); // TODO: update the entire userlist (if the server went down or something, you're not going to get "part" messages for the users who are gone). // See BugzId 6107 for more info & partially completed code. }		for(var i in this.afterInitQueue) { this.socket.send(this.afterInitQueue[i]); }

this.afterInitQueue = [];

window.staff = []; window.mods = []; var list = document.getElementById('WikiChatList').getElementsByTagName('li'); for(var i in list) { if(list[i].className) { var classes = list[i].className.split(' '); var user = false; var isstaff = false; var mod = false; for(var j in classes) { if(classes[j] == 'User') {user = true;} if(classes[j] == 'staff') {isstaff = true;} if(classes[j] == 'chat-mod') {mod = true;} }				if(isstaff && user) { staff.push(list[i].getElementsByTagName('span')[0].innerHTML); }				else if(mod && user) { mods.push(list[i].getElementsByTagName('span')[0].innerHTML); }			}		}		//window.canding = true; }

//Add/remove mods when people join/leave NodeChatUsers.prototype.addUser = function (user) { if(user.attributes.isStaff == true) { var isstaff = true; for(var i in staff) {if(staff[i] == user.attributes.name) {isstaff = false;}} if(isstaff == true) {staff.push(user.attributes.name);} }		else if(user.attributes.isModerator == true) { var mod = true; for(var i in mods) {if(mods[i] == user.attributes.name) {mod = false;}} if(mod == true) {mods.push(user.attributes.name);} }		var view = new UserView({model: user}); var list = (user.attributes.isPrivate) ? $('#PrivateChatList') : $('#WikiChatList'); var el = $(view.render.el);

// For private chats, show private headline and possibly select the chat if(user.get('isPrivate')) { $('#Rail h1.private').show; if(user.get('active')) { el.addClass('selected'); }		}		// Add users to list if (list.children.length) { // The list is not empty. Arrange alphabetically. var compareA = el.data('user').toUpperCase; var wasAdded = false; list.children.each(function(idx, itm) {				compareB = $(itm).data('user').toUpperCase;				if (compareA < compareB) {					$(itm).before(el);					wasAdded = true;					return false;				}			}); if (!wasAdded) { list.append(el); }		} else { // The list is empty. Append this user. list.append(el); }		// Scroll the list down if a new private chat is being added if (user.get('isPrivate')) { $.log('UserView SCROLL DOWN!!!'); $('#Rail').scrollTop($('#Rail').get(0).scrollHeight); }		// Only show chevron in public chat if there is anyone to talk to		if (list.children.length > 1) { $('#Rail .public .chevron').show; } else { $('#Rail .public .chevron').hide; }			}	NodeChatUsers.prototype.removeUser = function (user) { for(var i in mods) {if(mods[i] == user.attributes.name) {mods.splice(i, 1);}} for(var i in staff) {if(staff[i] == user.attributes.name) {staff.splice(i, 1);}} var view = new UserView({model: user}); view.getUserElement.remove; }	mainRoom.viewUsers.model.users.unbind('add'); mainRoom.viewUsers.model.users.unbind('remove'); mainRoom.viewUsers.model.users.bind('add', mainRoom.viewUsers.addUser); mainRoom.viewUsers.model.users.bind('remove', mainRoom.viewUsers.removeUser);

//Put images on mod and staff posts when they're added, ping if you've been mentioned NodeChatDiscussion.prototype.addChat = function (chat) { // Determine if chat view is presently scrolled to the bottom var isAtBottom = false; if (( this.chatDiv.scrollTop + 1) >= (this.chatUL.outerHeight - this.chatDiv.height)) { isAtBottom = true; }		// Add message to chat var view = new ChatView({model: chat}); this.chatUL.append(view.render.el); var icon = ''; for(var i in staff) {if(staff[i] == chat.attributes.name) {icon = ' '}} for(var i in mods) {if(mods[i] == chat.attributes.name) {icon = ' '}} if(icon) {active.viewDiscussion.chatUL.children.last.children('.username').html(active.viewDiscussion.chatUL.children.last.children('.username').html + icon);} if(window.canding && chat.attributes.name != wgUserName && !chat.attributes.isInlineAlert) { var pings = document.getElementById('pings').value.split('\n'); for(var i = 0; i < pings.length; i++) { if(chat.attributes.text.toLowerCase.indexOf(pings[i].toLowerCase) != -1) { $('#sound').html(''); if(document.hasFocus == false) {window.ding = setInterval('FlashTitle', 500);} NodeChatHelper.scrollToBottom; var text = chat.attributes.text; var ref = text.toLowerCase.indexOf(pings[i].toLowerCase); var phrase = text.slice(ref, ref + pings[i].length); $('#Chat ul li:last-child .message').html(text.replace(phrase, ' ' + phrase + ' ')); }			}		}		// Scroll chat to bottom if (chat.attributes.name == wgUserName || isAtBottom) { this.scrollToBottom; }	}	mainRoom.viewDiscussion.model.chats.unbind('afteradd'); mainRoom.viewDiscussion.model.chats.bind('afteradd', $.proxy(mainRoom.viewDiscussion.addChat, mainRoom.viewDiscussion)); //Add a custom inline alert that tells you when your status changes NodeChatController.prototype.setAway = function (msg){ if(!msg) {var msg = '';} $.log("Attempting to go away with message: " + msg); var setStatusCommand = new models.SetStatusCommand({			statusState: STATUS_STATE_AWAY,			statusMessage: msg		}); this.inlineAlert('You are now away.'); this.socket.send(setStatusCommand.xport); }	NodeChatController.prototype.setBack = function { if( ! this.comingBackFromAway){ // if we have sent this command (but just haven't finished coming back yet), don't keep spamming the server w/this command $.log("Telling the server that I'm back."); this.comingBackFromAway = true; var setStatusCommand = new models.SetStatusCommand({				statusState: STATUS_STATE_PRESENT,				statusMessage: ''			}); this.inlineAlert('You are no longer away.'); this.socket.send(setStatusCommand.xport); }	}	//Parse messages before they're sent NodeChatController.prototype.sendMessage = function (event) { if(!this.active) { return true; }		if (event.which == 13 && !event.shiftKey) { event.preventDefault; mainRoom.resetActivityTimer; var inputField = this.viewDiscussion.getTextInput; var send = Preparse.call(this, inputField.val); if (inputField.val && send) { var chatEntry = new models.ChatEntry({roomId: this.roomId, name: wgUserName, text: inputField.val}); if( this.mainController != null ) { //is prive if( this.afterInitQueue.length < 1 || this.model.users.length < 2 ){ this.mainController.socket.send( this.model.privateRoom.xport ); }					if( !this.isInitialized ) { this.afterInitQueue.push(chatEntry.xport); //temp chat entry in case of slow connection time chatEntry.set({temp : true, avatarSrc: wgAvatarUrl }); this.model.chats.add(chatEntry); } else { this.socket.send(chatEntry.xport); }				} else { this.socket.send(chatEntry.xport); }				event.preventDefault; }			inputField.val(''); $.log('submitting form'); inputField.focus; }	}	mainRoom.viewDiscussion.unbind('sendMessage'); mainRoom.viewDiscussion.bind('sendMessage', $.proxy(mainRoom.sendMessage, mainRoom)); }

NodeChatController.prototype.inlineAlert = function(text) { this.viewDiscussion.chatUL.append('' + text + ''); this.viewDiscussion.scrollToBottom; }

function FlashTitle { if(document.getElementsByTagName('title')[0].innerHTML == window.titleorig) { document.getElementsByTagName('title')[0].innerHTML = 'Activity - ' + wgSiteName + ' chat'; }	else { document.getElementsByTagName('title')[0].innerHTML = window.titleorig; } }

function Preparse(input) { //Parse slash commands. Technically the breaks aren't necessary, but it makes it easier to read var text = input.split(' '); if(text[0]) {text[0] = text[0].toLowerCase;} switch(text[0]) { case '/away': case '/afk': //Toggle away status if($('#ChatHeader .User').hasClass('away') == false) {toggleAway;} //if you're away, hitting enter has already sent you back return false; break; case '/clear': //Clear the active chat window this.clearWindow; return false; break; case '/kick': case '/kickban': //Kickban a user var mod = new models.KickBanCommand({userToBan: build(text, 1)}); this.socket.send(mod.xport); return false; break; case '/mod': //Make a user a mod var giveChatModCommand = new models.GiveChatModCommand({userToPromote: build(text, 1)}); this.socket.send(giveChatModCommand.xport); return false; break; case '/block': //Block a user from private chatting you this.blockPrivate({name: build(text, 1)}); return false; break; case '/unblock': //Unblock a user from private chatting you - should merge this into a toggle this.allowPrivate({name: build(text, 1)}); return false; break; case '/self': //Send a message to yourself if(text[1] != undefined) { str = build(text, 1); newstr = ''; for(var i = 0; i < str.length; i++) { switch(str.charAt(i)) { case '[': if(str.charAt(i + 1) == '[') { var k = str.indexOf(']', i); if(str.charAt(k + 1) == ']') { var substr = str.substring(i + 2, k); if(substr.indexOf('|') != -1) { var link = substr.substring(0, substr.indexOf('|')); var linktext = substr.substring(substr.indexOf('|') + 1, substr.length); }									else { var link = substr; var linktext = link; }									var linkspl = link.split(' '); link = ''; for(var j in linkspl) {link += linkspl[j] + '_';} link = link.substring(0, link.length - 1); newstr += '' + linktext + ''; i = k + 1; }							}							else if(str.substring(i + 1, i + 8) == 'http://') { var k = str.indexOf(']', i); var splstr = str.substring(i + 1, k).split(' '); if(splstr == str.substring(i + 1, k)) { var text = '[1]'; }								else { var text = build(splstr, 1); }								newstr += '' + text + ''; i = k;							} break; default: newstr += str.charAt(i); break; }				}				this.viewDiscussion.chatUL.append('<img class="avatar" src="' + $('#ChatHeader .User img').attr('src') + '"> ' + wgUserName + ' (self) ' + newstr + ' </li>'); }			else { this.viewDiscussion.chatUL.append('<img class="avatar" src="' + $('#ChatHeader .User img').attr('src') + '"> ' + wgUserName + ' (self) </li>'); }			this.viewDiscussion.scrollToBottom; return false; break; case '/me': //Emote if(text[1] != undefined) {$('#Write [name="message"]').val('* ' + wgUserName + ' ' + build(text, 1));} else {$('#Write [name="message"]').val('* ' + wgUserName);} return true; break; case '/room': case '/private': //Invoke a private room var array = build(text, 1).split('|'); for(var i in array) {array[i] = array[i].removeTrailing(' ');} mainRoom.openPrivateChat(array) return false; break; case '/help': //Walrus if(text[1]) {text[1] = text[1].toLowerCase;} switch(text[1]) { case 'me': this.inlineAlert('Emote yourself. Similar to IRC\'s /me.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/me likes chat hacks\')">/me likes chat hacks '); break; case 'away': case 'afk': this.inlineAlert('Toggles your away status.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/afk\')">/afk '); break; case 'clear': this.inlineAlert('Clears the currently open chat window.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/clear\')">/clear '); break; case 'private': case 'room': this.inlineAlert('Invokes a private room.'); //this.inlineAlert('Names must be separated by a pipe (|).'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/room ExampleUser\')">/room ExampleUser '); break; case 'mod': this.inlineAlert('Gives the specified user chat mod status.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/mod ExampleUser\')">/mod ExampleUser '); break; case 'block': this.inlineAlert('Blocks the specified user from private chatting you.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/block ExampleUser\')">/block ExampleUser '); break; case 'unblock': this.inlineAlert('Unblocks the specified user from private chatting you.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/unblock ExampleUser\')">/unblock ExampleUser '); break; case 'kick': case 'kickban': this.inlineAlert('Kickbans the specified user.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/kickban ExampleUser\')">/kickban ExampleUser '); break; case 'help': this.inlineAlert('Displays information about other commands.'); this.inlineAlert('Example: <span onclick="mainRoom.viewDiscussion.getTextInput.val(\'/help\')">/help '); break; default: this.inlineAlert('Commands: '); this.inlineAlert('<span onclick="Preparse.call(active, \'/help me\')">me, <span onclick="Preparse.call(active, \'/help afk\')">afk , <span onclick="Preparse.call(active, \'/help clear\')">clear , <span onclick="Preparse.call(active, \'/help room\')">room ,'); this.inlineAlert('<span onclick="Preparse.call(active, \'/help mod\')">mod, <span onclick="Preparse.call(active, \'/help block\')">block , <span onclick="Preparse.call(active, \'/help unblock\')">unblock , <span onclick="Preparse.call(active, \'/help kickban\')">kickban '); break; }			return false; break; default: return true; break; } }

//Parser helping function - takes an array of text created with .split(' '), //and an index number for where to start, then rebuilds the string. //Can also accept an index value for where to stop. function build(text, index, stop) { var newtext = ''; if(stop == undefined || stop == 0) {stop = text.length} else if(stop < 0) {stop += text.length} if(index == stop || index == text.length - 1) {return text[index];} for(var i = index; i < stop; i++) { newtext += text[i] + ' '; }	return newtext; }

function toggleAway(msg) { if(!msg) {var msg = '';} if($('#ChatHeader .User').hasClass('away') == true) { mainRoom.setBack; }	else { mainRoom.setAway(msg); } } toggleAway.back = function { //Force back status if($('#ChatHeader .User').hasClass('away') == true) {mainRoom.setBack;} } toggleAway.away = function(msg) { //Force away status if(!msg) {var msg = '';} if($('#ChatHeader .User').hasClass('away') == false) {mainRoom.setAway(msg);} }

NodeChatController.prototype.clearWindow = function { this.viewDiscussion.chatUL.html(''); this.inlineAlert('Window cleared.'); }

function active { //Returns the NodeChatController for the active window if(mainRoom.activeRoom && mainRoom.activeRoom != 'main') {return mainRoom.chats.privates[mainRoom.activeRoom];} else {return mainRoom;} }

String.prototype.removeTrailing = function(char) { //Remove extraneous characters var str = this; while(str.charAt(0) == char) {str = str.substring(1, str.length);} while(str.charAt(str.length - 1) == char) {str = str.substring(0, str.length - 1);} return str; }

function createCookie(name,value,days) { if (days) { var date = new Date; date.setTime(date.getTime+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString; }	else var expires = ""; document.cookie = name+"="+value+expires+"; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); }	return null; }