MediaWiki:Chat.js

/*	Codes on this page will affect Special:Chat. For stylesheets, use MediaWiki:Chat.css

console.log("--"); console.info("## MediaWiki:Chat.js - loaded"); console.log("--");

/* chat &lt;title&gt; modification */ $("head title").html("Wiki Chat - Club Penguin Wiki");

/* allow further interwiki links */

// define Widget Widget = typeof Widget === "undefined" ? {} : Widget;

// define interwiki links feature Widget.iwi = {};

// list of checked messages Widget.iwi.checked = [];

// list links Widget.iwi.sites = { "mw": "http://www.mediawiki.org", "mediawikiwiki": "http://www.mediawiki.org", "commons": "http://commons.wikimedia.org", "wikimedia": "http://wikimediafoundation.org", "wikipedia": "http://en.wikipedia.org" };

// apply check on a given node Widget.iwi.apply = function(node) { $(node).find("a").each(function {		console.log(this);		var href = $(this).attr("href"),			regex = location.origin.replace(/[\.\/\:\-]/g, "\\$&") + "\\/wiki\\/(.+?)\\:(.+)",			m = href.match(new RegExp(regex));		if (m) {			var domain = Widget.iwi.sites[m[1].toLowerCase];			if (domain) {				$(this).attr("href", domain + "/wiki/" + m[2]);			}		}	}); }

// observer Widget.iwi.observer = new MutationObserver(function(mutations) {	for (var i in mutations) {		for (var j in mutations[i].addedNodes) {			var curr = mutations[i].addedNodes[j];			if (curr.nodeType == 1) {				// html element node				if (curr.nodeName.toLowerCase == "li" && $(curr.parentNode.parentNode).hasClass("Chat") && !$(curr).hasClass("inline-alert")) {					Widget.iwi.apply(curr);				}			}		}	} });

// first check all existing messages when joining chat - usually 10 unless chat's dead (or usually < 10 unless chat's alive, if you know what i mean) $(".Chat li").each(function {	Widget.iwi.apply(this); });

// start observing Widget.iwi.observer.observe(document.body, {	childList: true,	subtree: true });

/*	auto refresh emoticons also load emoticons from MediaWiki:Emoticons/DataURI and css from MediaWiki:ChatResizeEmoticons.css

$(function {	var gap = 30,		chatResizeEmoticons = $('');	$(chatResizeEmoticons).appendTo("head");	function request {		$.getJSON("/api.php?action=query&format=json&prop=revisions&titles=MediaWiki:Emoticons|MediaWiki:Emoticons/DataURI|MediaWiki:ChatResizeEmoticons.css&rvprop=content&cb=" + new Date.getTime, function(data) { var a = data.query.pages, content = {emoticons: [], css: ""}; for (var pageid in a) { if (a[pageid].title == "MediaWiki:ChatResizeEmoticons.css") { $(chatResizeEmoticons).html(a[pageid].revisions[0]["*"]); } else { content.emoticons.push(a[pageid].revisions[0]["*"]); }			}			mw.config.set("EMOTICONS", content.emoticons.join("\n\n")); });	}	// make first request when joining the room	request;	// request again every every 'gap' seconds	setInterval(request, gap * 1000); });

/* cp json lib - from MediaWiki:Common.js */

// global objects $cp = {}; $cp.json = {};

// values $cp.json.val = {};

// get function $cp.json.get = function(a) { var lang = a.lang == "pt" ? "pt" : a.lang == "fr" ? "fr" : a.lang == "es" ? "es" : a.lang == "de" ? "de" : a.lang == "ru" ? "ru" : "en", file = a.file; url = "http://media1." + (["characters", "worlds"].indexOf(a.file) > -1 ? "friends.go.com/content/disney-land-clubpenguin/" + lang + "/" : ["lands", "text"].indexOf(a.file) > -1 ? "friends.go.com/content/" + lang + "/" : ["markup", "images"].indexOf(a.file) > -1 ? "friends.go.com/content/" : "clubpenguin.com/play/" + lang + "/web_service/game_configs/") + a.file + ".jsonp?" + new Date.getTime, cb = ["lands", "characters", "text", "markup", "images", "worlds"].indexOf(a.file) > -1 ? a.file + "Data" : "cp_" + a.file, onSuccess = typeof a.success === "function" ? a.success : function {console.log("Finished loading '" + file + "' file as JSONP (" + lang + ")");}; if (typeof $cp.json.val[lang] === "undefined") { // if language object is missing $cp.json.val[lang] = {}; }	if (typeof $cp.json.val[lang][file] === "undefined") { $.ajax({			url: url,			dataType: "jsonp",			jsonpCallback: cb,			success: function(data) {				$cp.json.val[lang][file] = data;				onSuccess(data);			}		}); } else { // file has already been loaded - execute onSuccess without reloading resource onSuccess($cp.json.val[lang][file]); } }

// get multiple resources $cp.json.multi = function(a, b) { var arg = Array.prototype.slice.apply(arguments), toget = arg[0], onDone = arg[1]; function get { if (toget.length > 0) { var newReq = toget.shift; newReq.success = function { if (toget.length == 0) { if (typeof onDone === "function") { onDone; }				} else { get; }			}			$cp.json.get(newReq); }	}	get; }

/* resize emoticons */

importStylesheet("MediaWiki:ChatResizeEmoticons.css");

/* import scripts */

importScriptPage("MediaWiki:Chat.js/pin.js"); importScriptPage("MediaWiki:Common.js/crestriction.js");

/* cookie setting */ /* script from the W3School */

// set cookie function setCookie(c_name,value,exdays) { var exdate = new Date; exdate.setDate(exdate.getDate + exdays); var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString); document.cookie = c_name + "=" + c_value; } // get cookie function getCookie(c_name) { var c_value = document.cookie; var c_start = c_value.indexOf(" " + c_name + "="); if (c_start == -1) { c_start = c_value.indexOf(c_name + "="); }	if (c_start == -1) { c_value = null; } else { c_start = c_value.indexOf("=", c_start) + 1; var c_end = c_value.indexOf(";", c_start); if (c_end == -1) { c_end = c_value.length; }		c_value = unescape(c_value.substring(c_start,c_end)); }	return c_value; }

/* chat entrance policy agreement */ /*	alerts a message that asks the user to confirm that he \/ she have read the wiki's policy and agree to follow the rules. reset every 2 months or when the user is kicked \/ banned

// set \/ check Widget Widget = typeof Widget === "undefined" ? {} : Widget;

// create object for the warning widget Widget.policyWarning = {};

// markup Widget.policyWarning.markup = ' \n\ \n\ \t<div style=\"width: 370px; margin: auto; padding: 3px 7px; background: #f6f6f6; background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed)); background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%); background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%); border: 1px solid #cccccc; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px;\">\n\ \t\tPlease read carefully before proceeding!  \n\ \t\t\n\ \t\t\tBy entering chat, you agree to follow the Club Penguin Wiki\'s policy and its chat policy. \n\ \t\t\t\n\ \t\t \n\ \t \n\ '; // do this when pressing "agree" Widget.policyWarning.agree = function { if (confirm("I've read the policy and promise to follow the wiki's rules") === true) { // agree - close notice and disable message for the next 60 days setCookie("chatPolicy", "agree", 60); $('#policy-notice-blackout, #policy-notice').remove; Widget.policyWarning.clear; } else { // disagree - close window and try again later window.close; } }

// trigger if (typeof getCookie("chatPolicy") !== "string") { $("body").append(Widget.policyWarning.markup); }

// reset if the user is kicked\/banned Widget.policyWarning.clear = function { $("body").on("DOMNodeInserted", "section.WikiaPage .Chat li", function(ev) {		if ( $(ev.target).hasClass("inline-alert") && typeof getCookie("chatPolicy") === "string" && (				$(ev.target).html.indexOf("You have been kicked by ") > -1 ||				$(ev.target).html.indexOf("You have been banned by ") > -1			) ) {			document.cookie = "chatPolicy" + "=; expires=Thu, 01-Jan-70 00:00:01 GMT;";		}	}); } if (typeof getCookie("chatPolicy") === "string") { Widget.policyWarning.clear; }

/*	the following script blocks certain words in certain conditions regex reduction suggested by Callofduty4 http://community.wikia.com/wiki/Thread:628738#7

$(function {	var count = 0,		limit = 3,		filter = {};

// simple phrases filter.plain = [ "ass", "asses", "bastard", "bitch", "bitches", "bitchy", "blowjob", "boner", "boob", "boobs", "cock", "cum", "cunt", "dick", "douche", "douchebag", "dumbass", "faggot", "fuck", "fucker", "fucking", "hentai", "hoe", "horny", "jizz", "lesbian", "masturbate", "masturbation", "masturbator", "molest", "molested", "molester", "motherfucker", "nigga", "niggas", "nigger", "niggers", "penis", "penises", "piss", "pisses", "porn", "porno", "pussy", "pussies", "queer", "rape", "raping", "rapist", "sex", "sexy", "shit", "shitty", "slut", "sperm", "tits", "twat", "wanker", "whore", "whores", "vagina" ];

// phrases as regular expressions filter.regex = [ "callum lives?(?: (?:in|on|at))?", "ember[a-z]* is (?:f.t|fa.)", "touch(?:es|ing)? (?:(?:him|her|it)self|themselves|(?:his|her|it'?s|their) parts?)", "bemrose" ];

// the evaluated phrases filter.evaluated = []; for (var i in filter.plain) { filter.evaluated.push(filter.plain[i].replace(/[\\\/\{\}\,\[\-\]\(\|\)\.\,\?\!\=\*\+\^\$]/g, "\\$&")); }	for (var i in filter.regex) { filter.evaluated.push(filter.regex[i]); }

// regex object var regex = new RegExp(		"(?:^| |\\.|\\,|\\\\|\\/|\\[|\\]|_|\\-|\\(|\\)|\\{|\\}|\'|\"|\\|?\\!)(?:" +		filter.evaluated.join("|") +		")(?: |\\.|\\,|\\\\|\\/|\\[|\\]|_|\\-|\\(|\\)|\\{|\\}|\'|\"|\\?|\\!|$)",		"i" // case insensitive	);

// add digit suffix (e.g. "9001" -> "9001st"	function parseTime(n) {		var s = String(n),			lastD = s.match(/[1-3]$/),			suffixes = {				"1": "st",				"2": "nd",				"3": "rd"			};		return s + (lastD ? suffixes[lastD[0]] : "th");	}	$('textarea[name="message"]').keydown(function(e) { if (e.keyCode == 13) { if ($(this).val.match(regex)) { $(this).val("") count++; if (count < limit) { alert("Warning! You were caught using inappropriate language and your message has been blocked.\nThis is the " + parseTime(count) + " time you are caught doing so. If this number reaches " + limit + ", you would be kicked."); } else { location.href = "/"; window.close; }			}		}	}); });

/* shorten long external url links */

$("body").on("DOMNodeInserted", function(el) {	$(el.target).find(".message a").each(function { if ($(this).text.length > 150 && $(this).attr("href").indexOf(location.origin) != 0) { // external link with over 150 characters var a = $(this).text; $(this).text(a.substr(0,50) + "\u2026" + a.substr(a.length - 51, 50)) }	}); });

/* bot command blocker */

$(function {	var restrictedPhrases = [			"!tell Polo_Field "		],		regexString = [];	for (var i in restrictedPhrases) {		regexString.push("^" + restrictedPhrases[i].replace(/[\\\/\.\,\?\!\(\|\)\:\{\}\[\]\+\*\=\^\$]/g, "\\$&"));	}	var regex = new RegExp(regexString.join("|"));	NodeChatDiscussion.prototype.getTextInput.keydown(function(e) { if (e.keyCode == 13 && !e.shiftKey) { if (this.value.search(regex) == 0) { e.preventDefault; }		}	}); });

/* sockpuppet detection for block-evading accounts - against the wiki rules - http://clubpenguin.wikia.com/wiki/Club_Penguin_Wiki:Policy/Accounts */ /* a note: this feature does NOT collect data or ip addresses about users - only to enforce our policy */

if (true) { $.getJSON("/api.php?action=query&format=json&meta=userinfo&uiprop=rights&cb=" + new Date.getTime, function(data) {		var a = data.query.userinfo.rights,			isBypassBlock = false;		for (var i in a) {			if (a[i] === "ipblock-exempt") {				isBypassBlock = true;			}		}		if (!isBypassBlock) {			// user is not in group that allows evading ip blocks (e.g. not sysop, staff or vstf), check if ip is blocked			$.ajax({ url: "/api.php?action=query&format=json&meta=userinfo&uiprop=blockinfo&callback=cb", dataType: "jsonp", jsonpCallback: "cb", success: function(data) { if (typeof data.query.userinfo.blockedby !== "undefined") { // ip is blocked mainRoom.socket.send(							new models.ChatEntry({ roomId: mainRoom.roomId, name: mw.config.get("wgUserName"), text: "Error #sppt_id" + mainRoom.roomId + " at unix" + new Date.getTime + ": please notify an admin and include the username of this message's poster and the message's content" }).xport						); localStorage.chatIpBlockedDetected = new Date.getTime; }				}			});		}	}); }

/* iDot (apparently iPeriod was stolen by Mapple) */ /* prevent some floods */

$(function {	var log = [],		timeout = 10000;	$('[name="message"]').keydown(function(e) { if (e.keyCode == 13 && !e.shiftKey && $(this).val.replace(/[ \n\t]/g, "").match(/^\.+$/)) { log.push(new Date.getTime); if (log.length >= 3) { if (log[log.length - 1] - log[log.length - 4] < timeout) { $(this).val("").attr("disabled", "disabled"); $("body").addClass("dot-flood-blocked"); setTimeout(function {						$('[name="message"]').removeAttr("disabled");						$("body").removeClass("dot-flood-blocked");					}, timeout); }			}		}	});	mw.util.addCSS( 'body.dot-flood-blocked [name="message"] {\n' + '\tcursor: wait;\n' + '}'	);	/* useless per support but still cool to keep around ^ ^	var svg = ( 'data:image/svg+xml;base64,' + btoa(			'' +				'"']/g, function(m) { var a = {"&": "amp", "<": "lt", ">": "gt", "\"": "quot", "'": "apos"};						return a[m];					}) +				'" />' +				'' +				'' +			' '		) );	*/ });

/* add clock */ /* set main object */ window.Widget = window.Widget || {};

Widget.clock = { fn :{}, data: {}, core: {} };

/* markup */ Widget.clock.core.xml = $('\n' +	'\t \n' +		'\t\t\n' +		'\t\t<g id="cpw-clock-hrcirc">\n' +			'\t\t\t<circle cx="0" cy="0" r="1" />\n' +		'\t\t</g>\n' +		'\t\t\n' +		'\t\t<g id="cpw-clock-hrcirc-pair">\n' +			'\t\t\t<use xlink:href="#cpw-clock-hrcirc" x="1" y="1" />\n' +			'\t\t\t<use xlink:href="#cpw-clock-hrcirc" x="1" y="29" />\n' +		'\t\t</g>\n' +		'\t\t\n' +		'\t\t<g id="cpw-clock-hrcirc-pair-0">\n' +			'\t\t\t<use xlink:href="#cpw-clock-hrcirc-pair" style="fill: #c00; stroke: #0cc; stroke-width: 0.5;" />\n' +		'\t\t</g>\n' +		'\t\t\n' +		'\t\t<g id="cpw-clock-hrcirc-pair-1">\n' +			'\t\t\t<use xlink:href="#cpw-clock-hrcirc-pair" fill="#803300" />\n' +		'\t\t</g>\n' +	'\t \n' +	'\t\n' +	'\t<g>\n' +		'\t\t<circle cx="20" cy="20" r="20" fill="#e66" />\n' + '\t\t<circle cx="20" cy="20" r="17" style="fill: #fff; stroke: #ffd5d5; stroke-width: 2;" />\n' + '\t\t\n' + '\t\t<text x="1000" y="30" font-family="helvetica, arial, sans" font-size="9" id="cpw-clock-zonetxt" fill="#bbb">unset \n' + '\t\t\n' + '\t\t<use xlink:href="#cpw-clock-hrcirc-pair-0" x="19" y="5" fill="#c00" />\n' + '\t\t<use xlink:href="#cpw-clock-hrcirc-pair-1" x="19" y="5" transform="rotate(30 20 20)" />\n' + '\t\t<use xlink:href="#cpw-clock-hrcirc-pair-1" x="19" y="5" transform="rotate(60 20 20)" />\n' + '\t\t<use xlink:href="#cpw-clock-hrcirc-pair-0" x="19" y="5" transform="rotate(90 20 20)" />\n' + '\t\t<use xlink:href="#cpw-clock-hrcirc-pair-1" x="19" y="5" transform="rotate(120 20 20)" />\n' + '\t\t<use xlink:href="#cpw-clock-hrcirc-pair-1" x="19" y="5" transform="rotate(150 20 20)" />\n' + '\t\t\n' + '\t\t\t\n' + '\t\t<line x1="20" y1="20" x2="20" y2="5" id="cpw-clock-hands-s" transform="rotate(0 20 20)" style="stroke: #000; stroke-width: 0.5;" />\n' + '\t\t\t\n' + '\t\t<line x1="20" y1="20" x2="20" y2="7" id="cpw-clock-hands-m" transform="rotate(60 20 20)" style="stroke: #d33; stroke-width: 1;" />\n' + '\t\t\t\n' + '\t\t<line x1="20" y1="20" x2="20" y2="11" id="cpw-clock-hands-h" transform="rotate(305 20 20)" style="stroke: #a1f; stroke-width: 1;" />\n' + '\t\t\n' + '\t\t<circle cx="20" cy="20" r="1" fill="#000" />\n' + '\t</g>\n' + ' '); $("#ChatHeader .wordmark").append(Widget.clock.core.xml); // seems to work for now, otherwise add proper xml parsing

/* data */ // dimensions Widget.clock.data.dimensions = [40, 40];

// dimensions Widget.clock.data.zones = { pst: -7, utc: 0, _offset: new Date.getTimezoneOffset / 60 } // wanted mode Widget.clock.data.mode = "pst";

/* functions */ // get transform rotation value Widget.clock.fn.getRotation = function(deg) { var a = Widget.clock.data.dimensions; return "rotate(" + (deg % 180 === 0 && deg % 360 !== 0 ? "179.99" /* per stupid displays in paths, though lines seem to be fine */ : deg) + " " + (a[0] / 2) + " " + (a[1] / 2) + ")"; }

Widget.clock.fn.setHands = function(h, m, s) { var a = { h: document.querySelector("#cpw-clock-hands-h"), m: document.querySelector("#cpw-clock-hands-m"), s: document.querySelector("#cpw-clock-hands-s") }	// set seconds a.s.setAttributeNS(null, "transform", Widget.clock.fn.getRotation(s * 6)); // set minutes a.m.setAttributeNS(null, "transform", Widget.clock.fn.getRotation(m * 6)); // set hours a.h.setAttributeNS(null, "transform", Widget.clock.fn.getRotation(h * 30 + m * 0.5)); }

// adjust the clock Widget.clock.fn.setClock = function { // mode is the given time zone var a = new Date.getTime, b = new Date(a + (Widget.clock.data.zones[Widget.clock.data.mode] + Widget.clock.data.zones._offset) * 3600000); Widget.clock.fn.setHands(		b.getHours,		b.getMinutes,		b.getSeconds	); }

// set zone Widget.clock.fn.setZone = function(mode) { // mode is the given time zone if (Widget.clock.data.zones.hasOwnProperty(mode) && mode.indexOf("_") !== 0) { // listed zone Widget.clock.data.mode = mode; var txt = document.querySelector("#cpw-clock-zonetxt"); txt.innerHTML = mode.toUpperCase; txt.setAttributeNS(null, "x", (Widget.clock.data.dimensions[1] - txt.getBoundingClientRect.width) / 2); } }

/* implement */ Widget.clock.fn.setZone("pst"); Widget.clock.fn.setClock; Widget.clock.core.to = setInterval(	Widget.clock.fn.setClock,	1000 );