emjack

some crap you may find useful

2016-05-09 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name			emjack
// @version			4.3
// @description		some crap you may find useful
// @match			https://epicmafia.com/game/*
// @namespace https://greasyfork.org/users/4723
// ==/UserScript==

// welcome back
function emjack() {

	// yadayada
	var	alive=true,
		afk=false,
		meet="",
		master="",
		autobomb="",
		roulette=0,
		kicktimer=0,
		keys=0,
		auth=false,
		notes=null,
		users={};
	var	ANTIDC=0x0001,
		AUKICK=0x0002,
		AUWILL=0x0004,
		AUBOMB=0x0008,
		OBEYME=0x0010,
		UNOTES=0x0020,
		DEVLOG=0x0040,
		JEEVES=0x0080;
	var	K_DEBUG=0x0004;

	// public
	window.ej={
		name: "emjack",
		version: 4.3,
		cmds: {},
		notes: localStorage.notes
			? JSON.parse(localStorage.notes)
			: {},
		users: users,
		settings: +localStorage.ejs || AUKICK | AUWILL | UNOTES,
		};
	notes=ej.notes;
	afk=(ej.settings & JEEVES)===JEEVES;

	// plug in
	var	sock={ socket: null },
		postjackl=[];
	function postjack() {
		var	args=[];
		for(var i=0; i<arguments.length-1; i++) {
			args[i]=arguments[i];
			}
		postjackl.push(args, arguments[i]);
		};
	WebSocket.prototype.send=function(initial) {
		return function() {
			if(this!==sock.socket) {
				sock.build(this);
				}
			if(ej.settings & 0x0100) {
				arguments[0]=sock.intercept(arguments[0]);
				initial.apply(this, arguments);
				}
			else {
				(WebSocket.prototype.send=initial).apply(this, arguments);
				}
			};
		}(WebSocket.prototype.send);
	sock.build=function(socket) {
		this.socket=socket;
		if(window._emotes) {
			var	emotes=Object.keys(_emotes);
			log("", "pull-right emote emote-"+
				_emotes[emotes[Math.random()*emotes.length|0]]
				);
			}
		log(ej.name+ej.version+" connected", "pull-right");
		log((ej.settings|65536).toString(2).substring(1));
		socket.onmessage=function(initial) {
			return function(event) {
				sock.handle(event.data);
				if(alive) {
					initial.apply(this, arguments);
					setTimeout(function() {
						while(postjackl.length) {
							postjackl.pop().apply(null, postjackl.pop());
							}
						});
					}
				};
			}(socket.onmessage);
		};

	// socket
	sock.handle=function(data) {
		try {
			data=JSON.parse(data);
			}
		catch(error) {
			data=null;
			}
		if(data) for(var i=0, real=null; i<data.length; i++) {
			real=sock.parseShort(data[i][0], data[i][1]);
			if(ej.settings & DEVLOG) {
				console.log(" > %s:", real[0], real[1]);
				}
			if(ej.cmds[real[0]]) {
				ej.cmds[real[0]].call(ej, real[1]);
				}
			}
		};
	sock.intercept=function(data) {
		if(data[0]==="[") {
			try {
				data=JSON.parse(data);
				}
			catch(error) {
				return data;
				}
			if(ej.settings & DEVLOG) {
				console.log(" < %s:", data[0], data[1]);
				}
			if(ej.cmdi[data[0]]) {
				return JSON.stringify([data[0],
					ej.cmdi[data[0]](data[1])
					]);
				}
			return JSON.stringify(data);
			}
		return data;
		}
	sock.parseShort=function(cmd, data) {
		var	rfmt=this.short[cmd];
		if(rfmt) {
			if(data) for(key in rfmt.data) {
				data[key]=data[rfmt.data[key]] || data[key];
				// delete data[rfmt.data[key]];
				}
			return [rfmt.cmd, data];
			}
		else {
			return [cmd, data];
			}
		};
	sock.short=function(short) {
		var	rfmt={};
		for(i=0, data=null; i<short.length; i++) {
			data=short[i];
			rfmt[data.alias || data.cmd]={
				cmd: data.cmd || data.alias,
				data: data.data
				};
			}
		return rfmt;
		}(window.shorten);
	sock.cmd=function(cmd, data) {
		if(sock.socket) {
			sock.socket.send(
				JSON.stringify([cmd, data])
				);
			}
		};
	sock.chat=function(message, data) {
		if(typeof data==="object") {
			data.msg=message;
			data.meet=data.meet || meet;
			sock.cmd("<", data);
			}
		else sock.cmd("<", {
			meet: meet,
			msg: data ? "@"+data+" "+message : message
			});
		};
	sock.vote=function(vote, meeting) {
		sock.cmd("point", {
			meet: meeting || meet,
			target: vote
			});
		};
	sock.dcthen=function(callback) {
		alive=false;
		ej.redirect_back=callback;
		sock.cmd("leave");
		};

	// packets
	ej.cmdi={
		"$MODIFY": function(data) {
			for(key in data) {
				data[key]=prompt(key, data[key]);
				}
			return data;
			},
		"join": function(data) {
			if(keys & K_DEBUG) {
				keys^=K_DEBUG;
				return ej.cmdi.$MODIFY(data);
				}
			return data;
			}
		};
	ej.cmds={
		"auth": function(data) {
			var	ofg=document.querySelector("#option_fastgame"),
				ons=document.querySelector("#option_nospectate");
			if(ofg && !ofg.classList.contains("sel")) {
				ofg.classList.add("sel");
				sock.cmd("option", {
					field: "fastgame"
					});
				}
			if(ons && !ons.classList.contains("sel")) {
				ons.classList.add("sel");
				sock.cmd("option", {
					field: "nospectate"
					});
				}
			postjack(data, function(data) {
				auth=true;
				ku.send(0, Math.round(ej.version*10-42));
				});
			},
		"round": function(data) {
			for(var x in users) {
				users[x].voted=false;
				}
			if(auth && data.state===1 && ej.settings & AUWILL) {
				postjack(data, function(data) {
					log("Wrote will.", "lastwill");
					sock.cmd("will", {
						msg: user+"."+u(user).role
						});
					});
				}
			},
		"users": function(data) {
			for(var x in data.users) {
				u.make(data.users[x]);
				}
			setTimeout(function() {
				var	node;
				for(x in data.users) {
					node=document.querySelector("[data-uname="+x+"]");
					if(node) {
						node.setAttribute("title", notes[x] || "no notes for "+x);
						}
					}
				}, 2000);
			},
		"left": function(data) {
			for(var i=0; i<data.left.length; i++) {
				u(data.left[i]).dead=true;
				}
			},
		"anonymous_players": function(data) {
			for(var x in users) {
				delete users[x];
				}
			for(var i=0; i<data.players.length; i++) {
				u.make(data.players[i]);
				}
			},
		"anonymous_reveal": function(data) {
			if(data.user===user) {
				u.make(u(this.mask));
				}
			},
		"join": function(data) {
			u.make(data.data);
			log(data.user+" has joined");
			if(ej.settings & AUKICK && /autokick/.test(notes[data.user])) {
				sock.cmd("ban", {
					uid: data.id
					});
				}
			else {
				var	user=this.user;
				requestAnimationFrame(function() {
					$(".user_li.ng-scope[data-uname="+user+"]")
						.attr("title", notes[user]||"no notes for "+user);
					});
				}
			},
		"leave": function(data) {
			if(!data.user) {
				data.user=data.u;
				}
			log(data.user+" has left");
			delete users[data.user];
			},
		"kick": function(data) {
			u(data.user).dead=true;
			},
		"kill": function(data) {
			u(data.target).dead=true;
			},
		"k": function(data) {
			ku.recv(u(data.user || data.u), 1, Date.now());
			},
		"u": function(data) {
			ku.recv(u(data.user || data.u), 0, Date.now());
			},
		"<": function(data) {
			if(auth && ej.settings & OBEYME) {
				if(data.msg[0]==="@" && !ranked) {
					var	target=data.msg.replace(/@(\w+).+/, "$1"),
						message=data.msg.replace(/@\w+ (.+)/, "$1");
					if(target===user) {
						ej.run(message, ej.lbot, data);
						}
					}
				}
			},
		"speech": function(data) {
			if(data.type==="contact") {
				postjack(data, function(data) {
					log("The roles are... "+data.data.join(", "));
					});
				}
			},
		"meet": function(data) {
			if(data.say) {
				meet=data.meet;
				for(var i=0; i<data.members.length; i++) {
					u(data.members[i]).meet=data.meet;
					}
				}
			if(ej.settings & 0x0100) {
				for(var x in data.disguise) {
					postjack(data, name, function(data, name) {
						log(name+" is "+data.disguise[name]);
						});
					}
				}
			switch(data.meet) {
				case "mafia":
					if(auth && !data.disguise && !ranked) {
						if(ej.settings & OBEYME && false) {
							postjack(user, function(data) {
								sock.chat(u(data).role, {
									meet: "mafia"
									});
								});
							}
						}
				case "thief":
					u(user).mafia=true;
					for(var x in users) {
						if(!data.choosedata[x] && !u(x).dead) {
							u(x).mafia=true;
							postjack(x, function(data) {
								log(data+" is your partner!");
								document.querySelector("[data-uname="+data+"] .username")
									.textContent=data+" (partner)";
								});
							}
						}
					break;
				}
			if(afk) {
				sock.vote(data.basket[0], data.meet);
				}
			},
		"end_meet": function(data) {
			if(data.say) {
				for(var x in users) {
					if(users[x].meet===data.meet) {
						users[x].meet=null;
						}
					}
				}
			},
		"point": function(data) {
			u(data.user).voted=!data.unpoint;
			},
		"reveal": function(data) {
			u(data.user).role=data.data;
			if(!u(data.user).dead) {
				if(data.user===user) {
					postjack(data, function(data) {
						log(data.user===user
							? "Your role is now "+data.data
							: data.user+" is a "+data.data
							);
						});
					}
				}
			},
		"countdown": function(data) {
			clearTimeout(kicktimer);
			kicktimer=setTimeout(function() {
				sock.cmd("kick");
				}, data.totaltime);
			},
		"start_input": function(data) {
			if(afk || ej.settings & AUBOMB) {
				postjack(data, Object.keys(users), function(data, names) {
					sock.cmd("input", {
						id: data.id,
						input: {
							player: names[Math.random()*names.length|0]
							}
						});
					});
				}
			},
		"redirect": function(data) {
			if(!alive) {
				ej.redirect_back();
				}
			}
		};

	// kucode
	var	ku={};
	ku.send=function(op, code) {
		code+=op<<6;
		if(ej.settings & DEVLOG) {
			log(" * "+user+": "+(code|1024).toString(2));
			}
		setTimeout(function() {
			for(var i=9; i>=0; i--) {
				sock.cmd(code>>i & 1 ? "k" : "u");
				}
			}, 200);
		};
	ku.recv=function(u, bit, time) {
		if(time-u.kuclock > 100) {
			u.kucode=1;
			u.kuclock=Infinity;
			}
		else {
			u.kucode<<=1;
			u.kucode|=bit;
			if(u.kucode & 1024) {
				if(ej.settings & DEVLOG) {
					log(" * "+u.name+": "+u.kucode.toString(2));
					}
				if(ku.op[u.kucode>>6 & 15]) {
					ku.op[u.kucode>>6 & 15]
						(u, u.kucode & 63);
					}
				u.kucode=1;
				u.kuclock=Infinity;
				}
			else {
				u.kuclock=time;
				}
			}
		};
	ku.op=[
		function(u, code) {
			if(!u.emjack) {
				u.emjack=(42+code)/10;
				ku.send(0, Math.round(ej.version*10-42));
				}
			},
		null,
		function(u, code) {
			log(u.name+" sent "
				+(code|64).toString(2).substring(1)
				+":"+code.toString()
				+":"+String.fromCharCode(code+96)
				);
			},
		function(u, code) {
			log("You've been poked!");
			}
		];

	// chat base
	ej.run=function(input, list, data) {
		for(var i=0, match=null; i<list.length; i++) {
			match=list[i].regex.exec(input);
			if(match!==null && input===match[0]) {
				data ? match[0]=data : match.shift();
				list[i].callback.apply(list[i], match);
				break;
				}
			}
		};

	// chat commands
	ej.lchat=[
		{
			name: "Scriptcheck",
			regex: /sc|scriptcheck/,
			callback: function() {
				log(ej.name+ej.version);
				}
			},
		{
			name: "EM commands",
			regex: /(me .+)/,
			callback: function(msg) {
				sock.chat("/"+msg);
				}
			},
		{
			name: "About",
			regex: /info|help|about/,
			callback: function() {
				log(ej.name+ej.version, "bold");
				log("Type /cmdlist for a list of commands");
				}
			},
		{
			name: "Evaluate",
			regex: /eval (.+)/,
			callback: function(input) {
				log(JSON.stringify(eval(input)) || "undefined");
				}
			},
		{
			name: "Clear chat/logs",
			regex: /clear( logs)?/,
			callback: function(_logs) {
				var	nodelist=_logs
					? document.querySelectorAll("#window .emjack")
					: chat.children;
				for(var i=0; i<nodelist.length; i++) {
					nodelist[i].parentElement.removeChild(nodelist[i]);
					}
				}
			},
		{
			name: "Get metadata",
			regex: /meta(?:data)?/,
			callback: function() {
				for(var param in ej.meta) {
					log("@"+param+": "+ej.meta[param]);
					}
				}
			},
		{
			name: "Get whois",
			regex: /whois (.+)/,
			callback: function(name) {
				if(users[name]) {
					log(users[name].name+" ("+users[name].id+") "+(
						users[name].emjack ? "ej"+users[name].emjack : ""
						), "bold");
					log("emotes: "+(
						users[name].emotes
							? Object.keys(users[name].emotes).join(" ") || "none found"
							: "does not own"
						));
					}
				else {
					log("Can't find '"+name+"'");
					}
				}
			},
		{
			name: "Get emotes",
			regex: /emotes/,
			callback: function() {
				log("Sitewide emotes", "bold");
				log(Object.keys(_emotes || {}).join(" ") || "none found");
				log("Lobby emotes", "bold");
				log(Object.keys(lobby_emotes || {}).join(" ") || "none found");
				}
			},
		{
			name: "Get command list",
			regex: /cmdlist ?(bot)?/,
			callback: function(_bot) {
				var	data=(_bot ? ej.lbot : ej.lchat);
				for(var i=0; i<data.length; i++) {
					log(data[i].name+" ~ "+data[i].regex);
					}
				}
			},
		{
			name: "Toggle afk",
			regex: /afk( on)?/,
			callback: function(_on) {
				if(_on) {
					ej.settings^=JEEVES;
					afk=(ej.settings & JEEVES)===JEEVES;
					}
				else {
					afk=!afk;
					}
				log(afk
					? "Jeeves will handle your affairs."
					: "Jeeves has been dismissed."
					);
				}
			},
		{
			name: "Toggle autowill",
			regex: /aw|autowill/,
			callback: function() {
				ej.settings^=AUWILL;
				log(ej.settings & AUWILL
					? "Name & role will be written in will by default."
					: "Disabled autowill."
					);
				}
			},
		{
			name: "Toggle autokick",
			regex: /ak|autokick/,
			callback: function() {
				ej.settings^=AUKICK;
				log(ej.settings & AUKICK
					? "Note-based autokick enabled."
					: "Disabled autokick."
					);
				}
			},
		{
			name: "Toggle dev logs",
			regex: /dev/,
			callback: function() {
				ej.settings^=DEVLOG;
				log(ej.settings & DEVLOG
					? "Logging debug data."
					: "Logging disabled."
					);
				}
			},
		{
			name: "Toggle slavemode",
			regex: /slave/,
			callback: function() {
				ej.settings^=OBEYME;
				log(ej.settings & OBEYME
					? "You're a naughty girl."
					: "You found Jesus."
					);
				}
			},
		{
			name: "Toggle roulette",
			regex: /roulette/,
			callback: function() {
				roulette=roulette?0:6;
				if(roulette) {
					sock.chat("Reloaded the revolver. Who's next?");
					}
				}
			},
		{
			name: "Poke",
			regex: /poke/,
			callback: function() {
				ku.send(3, 0);
				}
			},
		{
			name: "Ping",
			regex: /ping ?(.+)?/,
			callback: function(code) {
				ku.send(2, code
					? parseInt(code, 2) || +code || code.charCodeAt(0)-96
					: 0);
				}
			},
		{
			name: "Send message",
			regex: /say (\w+)/,
			callback: function(msg) {
				sock.chat(msg);
				}
			},
		{
			name: "Send whisper",
			regex: /w (\w+) ?(\w+)?/,
			callback: function(to, msg) {
				sock.chat(msg, {
					whisper: true,
					target: user
					});
				}
			},
		{
			name: "Send ping",
			regex: /ping ?(all)?/,
			callback: function(all) {
				var	pingees=[];
				for(var x in users) {
					if(!users[x].dead && !users[x].voted && users[x].meet===meet) {
						pingees.push(x);
						}
					}
				sock.chat(pingees.join(" "));
				}
			},
		{
			name: "Send kick",
			regex: /kick (\w+)/,
			callback: function(name) {
				sock.cmd("ban", {
					uid: u(name).id
					});
				}
			},
		{
			name: "Send vote",
			regex: /vote ?(no one)?(\w+)?/,
			callback: function(none, name) {
				sock.vote(
					none!==undefined?
						"*":
						name!==undefined?
							name:
							user
							);
				}
			},
		{
			name: "Send vote (gun)",
			regex: /shoot (\w+)/,
			callback: function(name) {
				sock.vote(name, "gun");
				}
			},
		{
			name: "Lobby host",
			regex: /host ?(.+)?/,
			callback: function(title) {
				sock.dcthen(function() {
					$.getJSON("/game/add/mafia", {
						setupid: setup_id,
						ranked: false,
						add_title: title===undefined?0:1,
						game_title: title
						}, function(json) {
							location.href="/game/"+json.table;
							});
					});
				}
			},
		{
			name: "Lobby games",
			regex: /games/,
			callback: function() {
				$.ajax({
					url: "/game/find?page=1",
					method: "get"
					}).success(function(json) {
						var	a, div, games=JSON.parse(json[1]);
						games.data.forEach(function(table) {
							if(table.status_id!==0 || table.password!==false) {
								return;
								}
							a=document.createElement("a");
							a.textContent="Table "+table.id;
							a.addEventListener("click", function(event) {
								sock.dcthen(function() {
									location.href="/game/"+table.id;
									});
								});
							div=document.createElement("div");
							div.appendChild(a);
							div.appendChild(
								document.createTextNode(" - "+table.numplayers+" / "+table.target+" players")
								);
							if(table.id===game_id) {
								div.appendChild(
									document.createTextNode(" (you)")
									);
								}
							log(div);
							});
						});
				}
			},
		{
			name: "[Naughty] Will",
			regex: /will (.+)/,
			callback: function(will) {
				if(ranked) {
					log("Disabled in ranked games.");
					}
				else if(ej.settings & 0x0100) {
					log("You revised your will.", "lastwill");
					sock.cmd("will", {
						msg: will
						});
					}
				}
			},
		{
			name: "[Naughty] Dethulu",
			regex: /(?:dt|thulu) (.+)/,
			callback: function(message) {
				if(ranked) {
					log("Disabled in ranked games.");
					}
				else if(true || ej.settings & 0x0100) {
					sock.cmd("<", {
						meet: meet,
						msg: "\u200B",
						quote: true,
						target: message
						});
					}
				}
			},
		{
			name: "[Naughty] Fakequote",
			regex: /(?:fq|quote) (\w+) (.+)/,
			callback: function(who, message) {
				if(ranked) {
					log("Disabled in ranked games.");
					}
				else if(true || ej.settings & 0x0100) {
					sock.cmd("<", {
						meet: meet,
						msg: message,
						quote: true,
						target: who
						});
					}
				}
			},
		{
			name: "[Naughty] Autobomb",
			regex: /(?:ab|autobomb) ?(\w+)?/,
			callback: function(name) {
				if(ranked) {
					log("Disabled in ranked games.");
					}
				else if(ej.settings & 0x0100) {
					if(name) {
						autobomb=name;
						ej.settings|=AUBOMB;
						log("Passing the bomb to "+name);
						}
					else {
						autobomb="";
						ej.settings^=AUBOMB;
						log(ej.settings & AUBOMB
							? "You're now an anarchist!"
							: "You're now a tree."
							);
						}
					}
				}
			},
		{
			name: "[Naughty] Fake Sysmessage",
			regex: /f(s)? ?(\w+)? ?(.+)?/,
			callback: function(send, id, input) {
				if(ranked) {
					log("Disabled in ranked games.");
					}
				else if(true || ej.settings & 0x0100) {
					var	output=this.messages[id];
					if(!output) {
						log("System messages: "+Object.keys(this.messages).join(", "));
						}
					else {
						input=input ? input.split(" ") : output.default;
						(send ? sock.chat : log)(output.msg.replace(/\%(\d+)/g, function(match, i) {
							return input[+i]
								? +i+1===output.default.length
									? input.slice(i).join(" ")
									: input[+i]
								: output.default[+i];
							}));
						}
					}
				},
			messages: {
				angel: {
					msg: "You feel an overwhelming, unconditional love for %0. "
						+"You feel you must protect %0 with your life.",
					default: [user]
					},
				auto: {
					msg: "There might be an autocrat among you...",
					default: []
					},
				bleed: {
					msg: "You start to bleed...",
					default: []
					},
				bomb: {
					msg: "%0 rushes at %1 and reveals a bomb!",
					default: [user, user]
					},
				carol: {
					msg: "You see a merry Caroler outside your house! "
						+"They sing you a Carol about %0, %1, %2. At least one of which is the Mafia!",
					default: [user, user, user]
					},
				chef: {
					msg: "You find yourself in a dimly lit banquet! "
						+"You sense the presence of a masked guest. The guest appears to be a %0.",
					default: ["ninja"]
					},
				cm: {
					msg: "You glance at your watch. The time is now %0 o'clock.",
					default: ["11"]
					},
				cmlife: {
					msg: "Your watch whispers to you. You have one extra life.",
					default: []
					},
				confess: {
					msg: "At the confessional tonight, a %0 had visited you to confess their sins.",
					default: ["survivor"]
					},
				cop: {
					msg: "After investigations, you suspect that %0 is sided with the %1.",
					default: [user, "mafia"]
					},
				cry: {
					msg: "Someone cries out | %0",
					default: [""]
					},
				det: {
					msg: "Through your detective work, you learned that %0 is a %1!",
					default: [user, "ninja"]
					},
				disc: {
					msg: "You discover that %0 is the %1!",
					default: [user, "interceptor"]
					},
				dream: {
					msg: "You had a dream... where at least one of %0, %1, %2 is a mafia...",
					default: [user, user, user]
					},
				fire: {
					msg: "Somebody threw a match into the crowd! "+
						"%0 suddenly lights on fire and burns to a crisp!",
					default: [user]
					},
				firefail: {
					msg: "Somebody threw a match into the crowd!",
					default: []
					},
				guise: {
					msg: "You are now disguised as %0.",
					default: [user]
					},
				guised: {
					msg: "%0 has stolen your identity!",
					default: [user]
					},
				gun: {
					msg: "You hear a gunshot!",
					default: []
					},
				gunfail: {
					msg: "%0 reveals a gun! The gun backfires!",
					default: [user]
					},
				gunhit: {
					msg: "%0 reveals a gun and shoots %0!",
					default: [user, user]
					},
				hit: {
					msg: "A bullet hits your vest! You cannot survive another hit!",
					default: []
					},
				invis: {
					msg: "Someone whispers %0",
					default: [""]
					},
				item: {
					msg: "You received a %0!",
					default: ["key"]
					},
				jail: {
					msg: "You have been blindfolded and sent to jail!",
					default: []
					},
				jan: {
					msg: "While cleaning up the mess, you learned that %0 was a %1.",
					default: [user, "cop"]
					},
				janday: {
					msg: "%0 is missing!",
					default: [user]
					},
				learn: {
					msg: "You learn that %0 is a %1",
					default: [user, "cop"]
					},
				lm: {
					msg: "A loud voice was heard during the night: \"Curses! %0 woke me from my slumber!\"",
					default: [user]
					},
				lonely: {
					msg: "You spent a silent and lonely night at church. No one came to visit you.",
					default: []
					},
				love: {
					msg: "During the night, you fall in love with %0 after a romantic conversation!",
					default: [user]
					},
				lynch: {
					msg: "You feel very irritated by %0.",
					default: [user]
					},
				matin: {
					msg: "Penguins be matin'",
					default: []
					},
				message: {
					msg: "You received a message: %0",
					default: [""]
					},
				mfail: {
					msg: "No matter how much you worked your magic, %0 and %1 refuses to fall in love!",
					default: [user, user]
					},
				mlove: {
					msg: "You cast a Christmas spell on %0 and %1... they are now in love!",
					default: [user, user]
					},
				mm: {
					msg: "There might be a mastermind among you...",
					default: []
					},
				mort: {
					msg: "You learned that %0 is a %1!",
					default: [user, "villager"]
					},
				party: {
					msg: "You find yourself at a vibrant party!",
					default: []
					},
				pengi: {
					msg: "During the night a fluffy penguin visits you and tells you that "+
						"%0 is carrying a %1.",
					default: [user, user]
					},
				pengno: {
					msg: "During the night a fluffy penguin visits you and tells you that "+
						"%0 has taken no action over the course of the night.",
					default: [user]
					},
				poison: {
					msg: "You feel sick, as though you had been poisoned!",
					default: []
					},
				pop: {
					msg: "%0 feels immensely frustrated!",
					default: [user]
					},
				psy: {
					msg: "You read %0's mind... they are thinking %1 thoughts.",
					default: [user, "evil"]
					},
				psyfail: {
					msg: "You tried to read %0's mind, but something distracted you.",
					default: [user]
					},
				pvisit: {
					msg: "During the night a fluffy penguin visits you and tells you that "+
						"%0 visited %1.",
					default: [user, "no one"]
					},
				pevisited: {
					msg: "During the night a fluffy penguin visits you and tells you that "+
						"%0 was visited by %1.",
					default: [user, "no one"]
					},
				santa: {
					msg: "After going out on your sleigh, you find that %0 is %1!",
					default: [user, "neither naughty nor nice"]
					},
				snoop: {
					msg: "After some snooping, you find out %0 is carrying %2 %1.",
					default: [user, "gun", "1"]
					},
				snoop0: {
					msg: "After some snooping, you find out %0 is not carrying any items..",
					default: [user]
					},
				stalk: {
					msg: "Through stalking, you learned that X is a R!",
					default: [user, "cop"]
					},
				thulu: {
					msg: "You were witness to an unimaginable evil... you cannot forget... "
						+"your mind descends into eternal hell.",
					default: []
					},
				track: {
					msg: "You followed %0 throughout the night. %0 visited %1.",
					default: [user, "no one"]
					},
				track: {
					msg: "You became a tree!",
					default: []
					},
				trust: {
					msg: "You had a dream... you learned you can trust %0...",
					default: [user]
					},
				virgin: {
					msg: "The virgin has been sacrified!",
					default: []
					},
				voodoo: {
					msg: "%0 suddenly feels a chill and falls to the ground!",
					default: [user]
					},
				watch: {
					msg: "You watched %0 throughout the night. %1 has visited %0.",
					default: [user, "No one"]
					},
				will: {
					msg: "You read the will of %0, it reads: %1",
					default: [user, ""]
					},
				ww: {
					msg: "You devoured a human and feel very powerful... "
						+"as though you are immortal for the day!",
					default: []
					}
				}
			}
		];

	// this is a sin
	ej.lbot=[
		{
			name: "Scriptcheck",
			regex: /sc|scriptcheck/,
			callback: function(data) {
				sock.chat(ej.name+ej.version, data.user);
				}
			},
		{
			name: "Echo",
			regex: /(?:echo|say) (.+)/,
			callback: function(data, what) {
				// sock.chat(what);
				}
			},
		{
			name: "Do Command",
			regex: /docmd (.+)/,
			callback: function(data, what) {
				ej.run(what, ej.lchat);
				}
			},
		{
			name: "Say hi",
			regex: /hi+|hey+|hel+o+/,
			callback: function(data) {
				sock.chat(
					this.responses[Math.random()*this.responses.length|0],
					data.user
					);
				},
			responses: [
				"hi...", "h-hi", "hey..."
				]
			},
		{
			name: "Say hi 2",
			regex: /(?:wh?at'?)s ?up+/,
			callback: function(data) {
				sock.chat(
					this.responses[Math.random()*this.responses.length|0],
					data.user
					);
				},
			responses: [
				"hi...", "h-hi", "hey...",
				"nothing", "something", "everything", "im dead inside"
				]
			},
		{
			name: "Notice me",
			regex: /\<3|i (?:like|love) you|(?:ur|you?'re?) cute/,
			callback: function(data) {
				sock.chat(
					this.responses[Math.random()*this.responses.length|0]
					);
				},
			responses: [
				"o-oh...", "/me blushes", "/me blushes..."
				]
			},
		{
			name: "Roulette",
			regex: /roulette/,
			callback: function(data) {
				if(roulette) {
					var	user=data.user,
						message1=this.message1[Math.random()*this.message1.length|0],
						message2=this.message2[Math.random()*this.message2.length|0],
						message3=this.message3[Math.random()*this.message3.length|0];
					sock.chat(roulette+" chambers left. You put the gun to your head...", user);
					setTimeout(function() {
						if(Math.random()*roulette>1) {
							roulette--;
							sock.chat(message1+", "+message2+", and nothing happens.", user);
							}
						else {
							roulette=0;
							sock.chat(message1+", "+message2+", and "+message3+".", user);
							sock.vote(user, "gun");
							}
						}, 3000);
					}
				},
			message1: [
				"Wet your pants", "Gulp", "Get ready", "Say your prayers"
				],
			message2: [
				"pull it", "pull the trigger", "let it rip", "gently tug"
				],
			message3: [
				"die instantly", "kiss the bullet", "meet Jesus"
				]
			},
		{
			name: "Bomb fight",
			regex: /fight ?me/,
			callback: function(data) {
				autobomb=data.user;
				sock.chat("ok", data.user);
				}
			},
		{
			name: "Obey",
			regex: /obey me|be my bi?t?ch/,
			callback: function(data) {
				if(!master) {
					master=data.user;
					sock.chat("yes master...", data.user);
					}
				else {
					sock.chat("i belong to "+master, data.user);
					}
				}
			},
		{
			name: "Roll over",
			regex: /roll ?over/,
			callback: function(data) {
				sock.chat(data.user===master
					? "/me rolls over for senpai"
					: "you're not the boss of me"
					);
				}
			},
		{
			name: "Bow",
			regex: /bow(?: ?down)?/,
			callback: function(data) {
				sock.chat(data.user===master
					? "/me bows for her master..."
					: "you're not the boss of me"
					);
				}
			},
		{
			name: "Beg",
			regex: /beg(?: for (.+))?/,
			callback: function(data, what) {
				if(data.user===master) {
					var	output=this.responses[Math.random()*this.responses.length|0];
					if(what) {
						output+=" for "+what;
						}
					else if(Math.random()>0.5) {
						output+=" master";
						}
					sock.chat(output);
					}
				else {
					sock.chat("blow me");
					}
				},
			responses: [
				"/me gets on her knees and begs",
				"/me gets on all fours and begs"
				]
			},
		{
			name: "Vote",
			regex: /vote (\w+)/,
			callback: function(data, who) {
				if(data.user===master) {
					sock.vote(who, data.meet);
					}
				}
			},
		{
			name: "Shoot",
			regex: /shoot (\w+)/,
			callback: function(data, who) {
				if(data.user===master) {
					sock.vote(who, "gun");
					}
				}
			},
		{
			name: "Claim",
			regex: /claim(?: to me)?/,
			callback: function(data) {
				if(data.user===master) {
					sock.chat(u(user).role+"...", {
						whisper: true,
						target: data.user
						});
					}
				}
			}
		];

	// utility
	function u(name) {
		return users[name || user] || u.make({
			id: 0,
			username: name || user
			});
		};
	u.make=function(data) {
		data.name=data.username || data.user;
		data.emjack=0;
		data.role=null;
		data.meet=meet;
		data.mafia=false;
		data.dead=false;
		data.voted=false;
		data.kucode=1;
		data.kuclock=Infinity;
		users[data.name]=data;
		if(data.emotes) {
			data.emotes=JSON.parse(data.emotes);
			}
		return data;
		};
	function log(message, classes) {
		var	node=document.createElement("div");
		node.className=classes ? "log emjack "+classes : "log emjack";
		typeof message==="string"
			? node.textContent=message
			: node.appendChild(message);
		if(chat.scrollTop>=chat.scrollHeight-chat.clientHeight) {
			requestAnimationFrame(function() {
				chat.scrollTop=chat.scrollHeight;
				});
			}
		chat.appendChild(node);
		};

	// keep chat
	document.querySelector("#speak_container")
		.style.cssText="display: initial !important";
	
	// townie input
	var	chat=document.querySelector("#window"),
		typebox=document.querySelector("#typebox"),
		notebox=document.querySelector("textarea.notes");
	typebox.addEventListener("keydown", function(event) {
		if(event.which===13 && this.value[0]==="/") {
			ej.run(this.value.substring(1), ej.lchat);
			this.value="";
			}
		});
	notebox.addEventListener("focus", function(event) {
		this.value=notes[document.querySelector(".user_header > h2").textContent];
		});
	notebox.addEventListener("keyup", function(event) {
		notes[document.querySelector(".user_header > h2").textContent]=this.value;
		});

	// clean up
	var	last_error=null;
	window.addEventListener("error", function(event) {
		var	message=event.error.message;
		if(message!==last_error) {
			log(message, "pull-right");
			log("You've got error!", "bold");
			last_error=message;
			}
		});
	window.addEventListener("beforeunload", function(event) {
		localStorage.ejs=ej.settings;
		if(ej.settings & UNOTES) {
			localStorage.notes=JSON.stringify(notes);
			}
		});
	window.addEventListener("keydown", function(event) {
		if(event.ctrlKey) {
			switch(event.which) {
				case 66:
					sock.cmd("option", {
						field: "fastgame"
						});
					sock.cmd("option", {
						field: "nospectate"
						});
					break;
				case 81:
					sock.cmd("<", {
						meet: meet,
						msg: typebox.value,
						quote: true,
						target: user
						});
					typebox.value="";
					break;
				}
			}
		else if(event.target.value===undefined) {
			if(event.which===192) {
				keys|=K_DEBUG;
				}
			typebox.focus();
			}
		});

	}

// syringe
function inject(fn) {
	var	script=document.createElement("script");
	script.type="text/javascript";
	script.textContent="("+String(fn)+")()";
	document.body.appendChild(script);
	}

// jack in
setTimeout(function() {
	inject(emjack);
	document.body.addEventListener("contextmenu", function(event) {
		event.stopPropagation();
		}, true);
	});