All pastes #1208488 Raw Edit

TiddleScript

public text v1 · immutable
#1208488 ·published 2008-09-22 17:38 UTC
rendered paste body
use strict;
use Data::Dumper;
use Socket;
use constant { EAT => 1, PASS => 2, EAT_KILL => 3, PASS_KILL => 4 };
use Encode;
use utf8;

### --- Start Global Declarations --- ###
my %config = ();

# Okay, the idea here is that if every time we need to send something and associate the reply with something.
# We add a request entry here ( a hashref ) and then when we get stuff in then it's given to the entries in here oldest-first.
# Not sure how this will work, pass a function reference? maybe
my @requests = ();

# Used for mapping prefixes (@%+) to modes +ohv and vice versa
my %modemap = (
	'~' => 'q',
	'q' => '~',
	'&' => 'a',
	'a' => '&',
	'@' => 'o',
	'o' => '@',
	'%' => 'h',
	'h' => '%',
	'+' => 'v',
	'v' => '+',
);

# Used for working out if a user already has a certain access level
my %modelevels = ();
$modelevels{'o'} = 100;
$modelevels{'h'} = 90;
$modelevels{'v'} = 80;
$modelevels{''} = 0;

### --- End Global Declarations --- ###

### --- Start Configuration --- ###
$config{script}			= "Xchat Utility Script";
$config{version}		= "0.1";
$config{prefix}			= "\cC5*\cC\cC4*\cC\cC8*\cC\cC8 ";
$config{whoistimeout}	= 3;
$config{whoiswidth}		= 100;
$config{identtimeout}	= 5;
$config{invitetimeout}	= 10;
$config{nickserv}		= "NickServ";
$config{chanserv}		= "ChanServ";
$config{globalstab}		= "(globals)";
$config{highlighttab}	= "(highlight)";
$config{globaldebug}	= "Debug";
$config{networkdevs}	= [ 'eth0', 'eth1', 'ra0' ];
### --- End Configuration --- ###

### --- Start Setup --- ###
Xchat::register($config{script}, $config{version}, "Om's general script for Xchat", \&on_unload);

Xchat::hook_command("WHOIS", \&handle_whois, { help_text => "/whois <nick>\n  Get's information about the user" });
Xchat::hook_command("WHOWAS", \&handle_whowas, { help_text => "/whowas <nick>\n  Get's information about a user that is no longer online.\n  This version will use a local cache"});

Xchat::hook_command("BADWORD", \&handle_badword, { help_text => "/badword text [text] [...]\n  Set chmode +g on the words given." });
Xchat::hook_command("GOODWORD", \&handle_goodword, { help_text => "/goodword text [text] [...]\n  Set chmode -g on the words given." });

Xchat::hook_command("MOP", \&handle_mmode, { help_text => "/mop [channel]\n  Ops all users without ops on the current channel.\n  Or if a parameter is given, that channel."});
Xchat::hook_command("MHOP", \&handle_mmode, { help_text => "/mhop [channel]\n  Halfops all users without halfops on the current channel.\n  Or if a parameter is given, that channel."});
Xchat::hook_command("MVOP", \&handle_mmode, { help_text => "/mvop [channel]\n  Voices all users without voice on the current channel.\n  Or if a parameter is given, that channel."});
Xchat::hook_command("MDEOP", \&handle_mmode, { help_text => "/mdeop [channel]\n  De-Ops all users with ops on the current channel.\n  Or if a parameter is given, that channel."});
Xchat::hook_command("MDEHOP", \&handle_mmode, { help_text => "/mdehop [channel]\n  De-Halfops all users with halfops on the current channel.\n  Or if a parameter is given, that channel."});
Xchat::hook_command("MDEVOP", \&handle_mmode, { help_text => "/mdevop [channel]\n  De-Voices all users with voice on the current channel.\n  Or if a parameter is given, that channel."});
Xchat::hook_command("MVOPNG", \&handle_mvopng, { help_text => "/mvopng [channel]\n  Voices all users without voice on the current channel, excludes users with Guest* nicks.\n  Or if a parameter is given, use that channel."});

Xchat::hook_command("TKB", \&handle_timedkb, { help_text => "/tkb <nick> <duration> [reason]\n  Kickbans the use for the specified duration.\n  Length should be specified in 5h4m3s format for five hours, four minutes and three seconds."});
Xchat::hook_command("TKBLIST", \&handle_timedkblist, { help_text => "/tkblist\n  Lists all the timed kickbans active on the current channel."});
Xchat::hook_command("TKBREM", \&handle_timedkbremove, { help_text => "/tkbrem <num>\n  Remove the timed kickban with the identifier 'num', as shown after /tkb and in /tkblist."});

Xchat::hook_command("NP", \&handle_audio_nowplaying, { help_text => "/np\n  Shows the currently playing song - with colours/bold/excessiveness."});
Xchat::hook_command("NPB", \&handle_audio_nowplaying, { help_text => "/np\n  Shows the currently playing song - without colours/bold/excessiveness."});
Xchat::hook_command("NPL", \&handle_audio_nowplaying, { help_text => "/np\n  Shows the currently playing song - with colours/bold."});
Xchat::hook_command("NPLB", \&handle_audio_nowplaying, { help_text => "/np\n  Shows the currently playing song - without colours/bold."});

Xchat::hook_command("GFXINFO", \&handle_sysinfo_gfx, { help_text => "/gfxinfo\n  Shows information on your system's graphics."});
Xchat::hook_command("AUDINFO", \&handle_sysinfo_aud, { help_text => "/audinfo\n  Shows information on your system's audio status."});
Xchat::hook_command("MBINFO", \&handle_sysinfo_mb, { help_text => "/mbinfo\n  Shows information on your system's motherboard."});
Xchat::hook_command("KERNINFO", \&handle_sysinfo_kern, { help_text => "/kerninfo\n  Shows information on your system's kernel."});
Xchat::hook_command("CPUINFO", \&handle_sysinfo_cpu, { help_text => "/cpuinfo\n  Shows information on your system's CPU(s)."});
Xchat::hook_command("RAMINFO", \&handle_sysinfo_ram, { help_text => "/raminfo\n  Shows information on your system's RAM."});
Xchat::hook_command("DISKINFO", \&handle_sysinfo_disk, { help_text => "/diskinfo\n  Shows information on your system's Disk(s)."});
Xchat::hook_command("NETINFO", \&handle_sysinfo_net, { help_text => "/netinfo\n  Shows information on your system's network."});
Xchat::hook_command("TEMPINFO", \&handle_sysinfo_temp, { help_text => "/tempinfo\n  Shows information on your system's temperature."});
Xchat::hook_command("SYSINFO", \&handle_sysinfo_all, { help_text => "/sysinfo\n  Shows all information about your system."});

Xchat::hook_command("ADDPRERAW", \&handle_perform_preraw_add, { help_text => "/addpreraw\n  Adds a raw irc string to the current network's channel autojoin list.\n  To be executed immediately on connect"});
Xchat::hook_command("DELPRERAW", \&handle_perform_preraw_del, { help_text => "/delpreraw\n  Deletes a raw irc string from the current network's channel autojoin list.\n  To be executed immediately on connect"});
Xchat::hook_command("LSPRERAWS", \&handle_perform_preraw_list, { help_text => "/lspreraws\n  Lists all the raw irc strings on the current network's autojoin list.\n  To be executed immediately on connect"});
Xchat::hook_command("ADDCHAN", \&handle_perform_add, { help_text => "/addchan [channel]\n  Adds the current channel, or the one specified, to the current network's channel autojoin list"});
Xchat::hook_command("DELCHAN", \&handle_perform_del, { help_text => "/delchan [channel]\n  Deletes the current channel, or the one specified, from the current network's channel autojoin list"});
Xchat::hook_command("LSCHANS", \&handle_perform_list, { help_text => "/lschans\n  Lists all the channels on the current network's autojoin list"});
Xchat::hook_command("ADDRAW", \&handle_perform_raw_add, { help_text => "/addraw\n  Adds a raw irc string to the current network's channel autojoin list.\n  To be executed after identification has timed out or succeeded"});
Xchat::hook_command("DELRAW", \&handle_perform_raw_del, { help_text => "/delraw\n  Deletes a raw irc string from the current network's channel autojoin list.\n  To be executed after identification has timed out or succeeded"});
Xchat::hook_command("LSRAWS", \&handle_perform_raw_list, { help_text => "/lsraws\n  Lists all the raw irc strings on the current network's autojoin list.\n  To be executed after identification has timed out or succeeded"});
Xchat::hook_command("SETPASS", \&handle_perform_pass_set, { help_text => "/setpass\n  Set the nickserv password for this network"});
Xchat::hook_command("GETPASS", \&handle_perform_pass_get, { help_text => "/getpass\n  Show the nickserv password for this network"});
Xchat::hook_command("IDENT", \&handle_identify, { help_text => "/ident\n  Identify to nickserv on the current network"});
Xchat::hook_command("DOPFM", \&handle_doperform, { help_text => "/dopfm\n  Execute all perform commands on the current network, 'raws' and 'preraws'"});

Xchat::hook_command("SLAP", \&handle_slap, { help_text => "/slap <target>\n  Slaps target around violently with various aquatic (and not) creatures."});

Xchat::hook_command("DECODE", \&handle_decode, { help_text => "/decode <string>\n  Decodes a hex-encoded string to an IP in form a.b.c.d"});
Xchat::hook_command("SHOWREQS", \&handle_showreqs, { help_text => "/showreqs\n  Shows all currently active requests" });
Xchat::hook_command("SCRIPTDEBUG", \&handle_scriptdebug, { help_text => "/scriptdebug <on|off>\n  Turns script debugging output on or off, 'off' is the default" });
Xchat::hook_command("TEST", \&handle_test, { help_text => "/test\n  Random test command" });

Xchat::hook_command("", \&handle_enteredline);

Xchat::hook_print("Join", \&on_join);								# For clonescanning
Xchat::hook_print("Channel Msg Hilight", \&on_highlight, 'msg');	# Highlight redirection
Xchat::hook_print("Channel Action Hilight", \&on_highlight, 'me');	# Highlight redirection

Xchat::hook_server("001", \&handle_connect);	# For on-connect stuff ;p
Xchat::hook_server("221", \&on_raw);			# Modes (from /mode)
Xchat::hook_server("301", \&on_raw);			# Away status
Xchat::hook_server("307", \&on_raw);			# Registered
Xchat::hook_server("310", \&on_raw);			# Helpop
Xchat::hook_server("311", \&on_raw);			# Displayed host
Xchat::hook_server("312", \&on_raw);			# Server (whois and whowas)
Xchat::hook_server("313", \&on_raw);			# Oper type
Xchat::hook_server("314", \&on_raw);			# Whowas host
Xchat::hook_server("317", \&on_raw);			# Idle time
Xchat::hook_server("318", \&on_raw);			# End of /whois
Xchat::hook_server("319", \&on_raw);			# Channels
Xchat::hook_server("320", \&on_raw);			# SSL and/or swhois
Xchat::hook_server("369", \&on_raw);			# End of /whowas
Xchat::hook_server("378", \&on_raw);			# Real host
Xchat::hook_server("379", \&on_raw);			# Modes (On Unreal, anyway)
Xchat::hook_server("401", \&on_raw);			# No such nick/channel
Xchat::hook_server("402", \&null);				# No such server (On Unreal, anyway)
Xchat::hook_server("406", \&on_raw);			# There was no such user
Xchat::hook_server("473", \&on_invite);			# For auto /cs inviting
Xchat::hook_server("671", \&on_raw);			# SSL (On Unreal, retarded alert)
Xchat::hook_server("NOTICE", \&on_raw);			# For catching /ns info results
Xchat::hook_server("NOTICE", \&on_notice);		# For blocking annoying /ns info messages :\
Xchat::hook_server("INVITE", \&on_raw);			# For catching /cs invite results
Xchat::hook_server("WALLOPS", \&on_wallops);	# For redirecting wallops

on_load();
### --- End Setup --- ###

### --- Start helper code --- ###
sub xcprint
{
	my($data, $chan, $net) = @_;

	if($net and (!$chan or ($net eq $chan)))
	{
		my @chanlist = Xchat::get_list("channels");
	
		for my $tab (@chanlist)
		{
			$chan = $tab->{channel} if(($tab->{network} eq $net) and ($tab->{type} == 1));
		}
	}
	
	
	if(ref($data) eq "ARRAY")
	{
		Xchat::print($config{prefix} . $_, $chan, $net) for (@{$data});
	}
	else
	{
		Xchat::print($config{prefix} . $data, $chan, $net);
	}
}

sub debug
{
	xcprint(shift(@_), undef, $config{globaldebug});
}

sub mychomp
{
	my $f = shift;
	chomp $f;
	return $f;
}

sub handle_scriptdebug
{
	# /newserver -noconnect Debug
	
	my $context = Xchat::find_context(undef, $config{globaldebug});
	my $oldcontext = Xchat::get_context();
	
	if($_[0][1] eq "on")
	{
		unless($context)
		{
			# XXX - Xchat bugged it seems, this should have -noconnect
			Xchat::command("newserver $config{globaldebug}");
			Xchat::command("settab Debug", undef, $config{globaldebug});
			Xchat::command("quit", undef, $config{globaldebug});
			Xchat::set_context($oldcontext);
		}
	}
	else
	{
		if(defined($context))
		{
			Xchat::set_context($context);
			Xchat::command("close");
			Xchat::set_context($oldcontext);
		}
	}
	
	return Xchat::EAT_ALL;
}

sub on_load
{
	xcprint "Loading $config{script} version $config{version}";
	$ENV{PATH} .= ":/usr/sbin:/sbin:/usr/local/sbin";
}

sub on_unload
{
	xcprint "$config{script} $config{version} unloading...";
}

sub ts2str
{
	my $time	= shift;
	my $days	= int($time/(3600*24));
	$time		= $time % (3600*24);
	my $hours	= int($time/3600);
	$time		= $time % 3600;
	my $mins	= int($time/60);
	$time		= $time % 60;
	
	my $out = $days . " days " if($days);
	
	return($out . sprintf("%02d:%02d:%02d", $hours, $mins, $time)); 
}

sub displength
{
	return length(scalar(Xchat::strip_code(shift(@_))));
}

sub request
{
	# request("server", \%hashref, \&callback, $timeout);
	# Add a request associated with the given server, the hashref given will be stored with the request and passed to the callback
	my($server, $hashref, $callback, $timeout, $timercb, $tagstr) = @_;
	debug("Error: request() server parameter was undefined") unless($server);
	debug("Error: request() got a non-hashref as a parameter") unless(ref($hashref) eq "HASH");
	debug("Error: request() got a non-callback as a data callback parameter") unless(ref($callback) eq "CODE");
	debug("Error: request() got a non-callback as a timer callback parameter") unless(ref($timercb) eq "CODE");
	debug("Error: request() timeout parameter was undefined") unless($timeout);
	debug("Warning: request() hashref parameter was undefined") and ($hashref = { }) unless($hashref);
	debug("Warning: request() timeout parameter was sub-zero") and ($timeout = 0) unless($timeout >= 0);
	debug("Warning: request() tag parameter was undefined") and ($tagstr = "None") unless($tagstr);
	debug("Warning: request() \$hashref->{_network} was already defined as '$hashref->{_network}', will be overwritten") if(defined($hashref->{_network}));
	debug("Warning: request() \$hashref->{_callback} was already defined as '$hashref->{_callback}', will be overwritten") if(defined($hashref->{_callback}));
	debug("Warning: request() \$hashref->{_timeout} was already defined as '$hashref->{_timeout}', will be overwritten") if(defined($hashref->{_timeout}));
	debug("Warning: request() \$hashref->{_timercb} was already defined as '$hashref->{_timercb}', will be overwritten") if(defined($hashref->{_timercb}));
	debug("Warning: request() \$hashref->{_start} was already defined as '$hashref->{_start}', will be overwritten") if(defined($hashref->{_start}));
	$hashref->{_network} = $server;
	$hashref->{_callback} = $callback;
	$hashref->{_timercb} = $timercb;
	$hashref->{_timeout} = $timeout;
	$hashref->{_start} = time;
	$hashref->{_orignet} = Xchat::get_info("network");
	$hashref->{_origchan} = Xchat::get_info("channel");
	$hashref->{_tag} = $tagstr;
	push(@requests, $hashref);
	
	debug("Adding request '$tagstr' on $server ($timeout secs)");
	($config{_timer} = Xchat::hook_timer(1000, \&do_timeouts)) and (debug("Hooking timer...")) unless($config{_timer});
}

sub removerequest
{
	my($server, $tag) = @_;
	debug("Error: removerequest() server parameter was undefined") unless($server);
	debug("Error: removerequest() tag parameter was undefined") unless($tag);	
	
	for(my $i = 0; $i < @requests; $i++)
	{
		if(($requests[$i]->{_network} eq $server) and ($requests[$i]->{_tag} =~ /$tag/i))
		{
			splice(@requests, $i, 1);
			return 1;
		}
	}
	
	return 0;
}

sub do_timeouts
{
	my $time = time;
	my @culls = ();
	
	for(my $i = 0; $i < @requests; $i++)
	{
		my $request = @requests[$i];
		
		if(($request->{_timeout} != 0) and ($time >= ($request->{_start} + $request->{_timeout})))
		{
			debug("Timing out request ($request->{_tag}) ...");
			$request->{_timercb}($request);
			push(@culls, $i);
		}
	}

	splice(@requests, $_, 1) for(@culls);
	
	if($config{_timer} and (@requests == 0))
	{
		debug("Unhooking timer...");
		delete $config{_timer};
		return Xchat::REMOVE;
	}
	
	return Xchat::KEEP;
}

sub on_raw
{
	my $network = Xchat::get_info("network");
	my $time = time;
	my $return = Xchat::EAT_NONE;
	my @culls = ();
	
	for(my $i = 0; $i < @requests; $i++)
	{
		my $request = @requests[$i];
		
		if($network eq $request->{_network})
		{
			my @args = @_;
			$args[2] = $request;
			my $r = $request->{_callback}(@args);
						
			if(($r == EAT_KILL) or ($r == PASS_KILL))
			{
				# xcprint("Killing request by request, raw was: " . join(' ', @{$_[0]}));
				push(@culls, $i);
			}
			
			# xcprint("Passing event onto next request: " . join(' ', @{$_[0]})) if($r == PASS);
			
			if(($r == EAT_KILL) or ($r == EAT))
			{
				$return = Xchat::EAT_XCHAT;
				last;
			}
		}
	}
	
	splice(@requests, $_, 1) for(@culls);
		
	return $return;
}

sub handle_showreqs
{
	for my $req (@requests)
	{
		Xchat::print(scalar(Dumper($req)));
	}
	
	return Xchat::EAT_ALL;
}

sub null
{
	return Xchat::EAT_XCHAT;
}

sub get_maxmodes
{
	my($channel, $network) = shift;
	$channel = Xchat::get_info("channel") unless($channel);
	$network = Xchat::get_info("network") unless($network);
	my @list = Xchat::get_list('channels');
	for my $chan (@list)
	{
		if(($chan->{channel} eq $channel) && ($chan->{network} eq $network))
		{
			return $chan->{maxmodes};
		}
	}

	return 3;
}

sub send_massmodes
{
	my($modes, $params, $chan, $net) = @_;
	debug("Error: send_massmodes() didn't get an arrayref for \$modes") unless(ref($modes) eq "ARRAY");
	debug("Error: send_massmodes() didn't get an arrayref for \$params") unless(ref($params) eq "ARRAY");
	$chan = Xchat::get_info("channel") unless($chan);
	$net = Xchat::get_info("network") unless($net);
	my $maxmodes = get_maxmodes($chan, $net);
	
	my $modestr = "";
	my $paramstr = "";
	my $direction = "";
	my $modecount = 0;
	
	debug("Setting massmodes on channel $chan, maxmodes is $maxmodes");
	
	for(my $i = 0; ($i < @{$modes}) and ($i < @{$params}); $i++)
	{
		my $newdirection = substr($modes->[$i], 0, 1);
		my $mode = substr($modes->[$i], 1);
		
		if(($modecount >= $maxmodes) or (length($modestr)+length($paramstr)+length("MODE $chan ") > 510))
		{
			# Limit is 512, but there's a \r\n to go on the end yet
			debug("Mode string would be over 512 characters, flushing now.");
			Xchat::command("raw MODE $chan $modestr" . $paramstr, $chan, $net);
			$direction = $modestr = $paramstr = "";
			$modecount = 0;
		}
		
		$modestr .= $newdirection and $direction = $newdirection unless($newdirection eq $direction);
		$modestr .= $mode;
		$modecount++;
		$paramstr .= " $params->[$i]" if(length($params->[$i]));
	}
	
	Xchat::command("raw MODE $chan $modestr" . $paramstr, $chan, $net) if(length($modestr));
}

### --- End helper code --- ###

### --- Load extra modules (now we have xcprint) --- ###
use File::Copy;

eval { require File::Slurp; };

if($@)
{
	xcprint "File::Slurp not found :( Rolling our own (this is fine, but probably less efficient than if you have the File::Slurp module installed)";
	
	sub read_file
	{
		my $out = "";		
		open(FH, "< " . shift);
		$out .= $_ while(<FH>);
		close(FH);
		return $out;
	}
}

### --- End extra modules --- ###

### --- Start /whois code --- ###
sub handle_whois
{
	shift @{$_[0]}; # Lose the "whois"
	
	my $server = Xchat::get_info("network");
	
	xcprint "/whois on undefined server, output may be misdirected." unless($server);
	
	for my $nick (@{$_[0]})
	{	
		# Send out the /whois 
		Xchat::command("raw MODE $nick");
		Xchat::command("raw WHOIS $nick $nick");
		Xchat::command("raw PRIVMSG $config{nickserv} :info $nick");
	
		# Initialise the structure we store the info in until it's printed.
		my $data = {
			nick => $nick,
			server => $server,
		};
		
		request($server, $data, \&on_whoisraw, $config{whoistimeout}, \&do_print_whois, "Whois on $nick");
	}
	
	return Xchat::EAT_ALL;
}

sub on_whoisraw
{	
	my $num = $_[0][1];
	my $nick = $_[0][3];
	my $data = $_[2];
	
	if($num eq "NOTICE")
	{
		return on_whoisnotice(@_);
	}
	
	$nick = $_[0][2] if($num eq "221");
	
	unless(Xchat::nickcmp($nick, $data->{nick}) == 0)
	{
		return PASS;
	}
	
	# :rtfm.inspircd.org 221 Brain :+xrowsgi
	if($num eq "221")
	{
		$data->{modes} = substr($_[0][3], 1);
	}
	elsif($num eq "301")
	{
		# :rtfm.inspircd.org 301 Om LaptopOm :multiword test
		$data->{away} = substr($_[1][4], 1);
	}
	elsif($num eq "307")
	{
		# :irc.leachim.info 307 t LeaChim :is a registered nick
		$data->{registered} = $_[1][4];
	}
	elsif($num eq "310")
	{
		# :irc.leachim.info 310 t LeaChim :is available for help.
		$data->{helpop} = $_[1][4];
	}
	elsif($num eq "311")
	{
		# :rtfm.inspircd.org 311 Om LaptopOm ~om supporter.inspircd.org * :Mr. Tiddles
		$data->{ident} = $_[0][4];
		$data->{dhost} = $_[0][5];
		$data->{gecos} = substr($_[1][7], 1);
	}
	elsif($num eq "312")
	{
		# :rtfm.inspircd.org 312 Om LaptopOm rtfm.inspircd.org :InspIRCd Support Server
		$data->{servername} = $_[0][4];
		$data->{serverdesc} = substr($_[1][5], 1);
	}
	elsif($num eq "313")
	{
		# :rtfm.inspircd.org 313 Om LaptopOm :is a Supporter on InspIRCd
		$data->{oper} = substr($_[1][4], 1);
	}
	elsif($num eq "317")
	{
		# :rtfm.inspircd.org 317 Om LaptopOm 21215 1140968241 :seconds idle, signon time
		$data->{idle} = $_[0][4];
		$data->{signon} = $_[0][5];
	}
	elsif($num eq "318")
	{
		# :rtfm.inspircd.org 318 Om LaptopOm :End of /WHOIS list.
		$data->{whoisdone} = 1;
		
		if($data->{nsinfodone})
		{
			# xcprint "And /ns info was finished too, print 'er out";
			do_print_whois($data);
			return EAT_KILL;
		}
	}
	elsif($num eq "319")
	{
		# :rtfm.inspircd.org 319 Om LaptopOm :@#inspircd
		$data->{chans} = "" unless($data->{chans});
		$data->{chans} .= substr($_[1][4], 1);
	}
	elsif(($num eq "320") or ($num eq "671"))
	{
		# :rtfm.inspircd.org 320 Om LaptopOm :is using a secure connection
		if($_[1][3] =~ /secure connection/i)
		{
			$data->{ssl} = substr($_[1][4], 1);
		}
		else
		{
			$data->{swhois} = substr($_[1][4], 1);
		}
	}
	elsif($num eq "378")
	{
		# :rtfm.inspircd.org 378 Om LaptopOm :is connecting from *@wlupton.gotadsl.co.uk 81.6.252.165
		if($_[1][4] =~ /connecting\s+from\s+([^@]*)@(\S+)\s+(\S+)/)
		{
			$data->{host} = $2;
			$data->{ip} = $3;
		}
		else
		{
			debug("Couldn't parse string into host/ip: " . $_[1][4]);
		}
	}
	elsif($num eq "379")
	{
		# :irc.leachim.info 379 t LeaChim :is using modes +iowghraAsxNWztp +kcfFjveGnNqSsoJO
		if($_[1][4] =~ /modes\s+(\S+)\s+(\S+)/i)
		{
			$data->{modes} = $1;
			$data->{snomask} = $2;
		}
		else
		{
			debug("Couldn't parse string into modes/snomask: " . $_[1][4]);
		}
	}
	elsif($num eq "401")
	{
		# :rtfm.inspircd.org 401 Om Blibble :No such nick/channel
		$data->{offline} = substr($_[1][4], 1);
		$data->{whoisdone} = 1;
		
		if($data->{nsinfodone})
		{
			do_print_whois($data);
			return EAT_KILL;
		}
	}
	elsif($num eq "406")
	{
		# :stitch.chatspike.net 406 Omster dsfds :There was no such nickname
		$data->{offline} = substr($_[1][4], 1);
		$data->{whoisdone} = 1;
		
		if($data->{nsinfodone})
		{
			do_print_whowas($data);
			return EAT_KILL;
		}
	}
	
	return EAT;
}

sub on_whoisnotice
{
	my $msg = substr($_[1][3], 1);
	my $data = $_[2];
	
	# :NickServ!services@chatspike.net NOTICE Om :Om is Mr. Tiddles
	# :NickServ!services@chatspike.net NOTICE Om :   Last seen time: Feb 27 18:21:59 2006 UTC
	# :NickServ!services@chatspike.net NOTICE Om :  Time registered: May 08 23:08:36 2004 UTC
	# :NickServ!services@chatspike.net NOTICE Om :Last quit message: Ping timeout
	# :NickServ!services@chatspike.net NOTICE Om :              URL: http://www.inspircd.org/
	# :NickServ!services@chatspike.net NOTICE Om :      Information: Blibble?
	# :NickServ!services@chatspike.net NOTICE Om :          Options: Kill protection, Security, Private
	if($_[0][0] =~ /$config{nickserv}!.*/i)
	{
		unless($data->{nsinfodone})
		{
			if($msg =~ /\s*Last\s+seen\s+time:\s+(.*)/i)
			{
				$data->{lastseen} = $1;
			}
			elsif($msg =~ /\s*Last\s+quit\s+message:\s+(.*)/i)
			{
				$data->{lastquit} = $1;
			}
			elsif($msg =~ /\s*Time\s+registered:\s+(.*)/i)
			{
				$data->{reggedon} = $1;
			}
			elsif($msg =~ /\s*URL:\s+(.*)/i)
			{
				$data->{url} = $1;
			}
			elsif($msg =~ /\s*Information:\s+(.*)/i)
			{
				$data->{info} = $1;
			}
			elsif($msg =~ /\s*E-mail\s+address:\s+(\S+)/i)
			{
				$data->{email} = $1;
			}
			elsif($msg =~ /\s*Nickname\s+(\S+)\s+isn't\s+registered.*/i)
			{
				$data->{nsinfodone} = 1;
				
				if($data->{whoisdone})
				{
					do_print_whois($data);
					return EAT_KILL;
				}				
			}
			elsif($msg =~ /\s*Options:\s+(.*)/i)
			{
				$data->{options} = $1;
				$data->{nsinfodone} = 1;
				
				if($data->{whoisdone})
				{
					do_print_whois($data);
					return EAT_KILL;
				}
			}
		}
		else
		{
			return PASS;
		}

		return EAT;
	}
	else
	{
		return PASS;
	}
}

sub handle_enteredline
{
	if(Xchat::get_info("channel") eq $config{globalstab})
	{
		Xchat::command("raw WALLOPS :$_[1][0]");
		return Xchat::EAT_XCHAT;
	}
	else
	{
		return Xchat::EAT_NONE;
	}
}

sub on_notice
{
	my $msg = substr($_[1][3], 1);
	my $nick = substr($_[0][0], 1, index($_[0][0], "!")-1);
	my $server = Xchat::get_info("network");
		
	if($nick =~ /Global/i)
	{
		Xchat::command("query $config{globalstab}");
		Xchat::print("\t - \0034\002Services Global\002\003 $msg", $config{globalstab});
		return Xchat::EAT_XCHAT;
	}
	elsif(substr($_[0][2], 0, 2) eq '$*')
	{
		Xchat::command("query $config{globalstab}");
		Xchat::print("\t - \0034\002Global Notice\002\003 From $nick: $msg", $config{globalstab});
		return Xchat::EAT_XCHAT;
	}
	elsif($msg =~ /\s*For\s+more\s+information,\s+type:\s+\002\/msg\s+NickServ\s+INFO\s+\S+\s+ALL.*/i)
	{
		return Xchat::EAT_XCHAT;
	}
	elsif(Xchat::nickcmp($nick, $config{nickserv}) == 0)
	{
		if (($msg =~ /This nickname is registered/i) or ($msg =~ /This nickname is owned by someone else./i))
		{
			do_identify($server);
			debug("Got identify request from NickServ ($config{nickserv}) on '$server', hooking $config{identtimeout} second timer for auto-perform");
			debug("Unhooked identify timeout too") if(removerequest($server, "Ident request"));
			request($server, { server => $server }, sub { return PASS; }, $config{identtimeout}, \&do_perform_timeout, "Perform request");
			return Xchat::EAT_XCHAT;
		}
	
		if (($msg =~ /Password accepted/i) or ($msg =~ /you are now recognized/i) or ($msg =~ /you are now identified/i))
		{
			do_perform($server);
			debug("Got ident confirmation from NickServ ($config{nickserv}) on '$server', executed perform commands");
			debug("Unhooked perform timeout too") if(removerequest($server, "Perform request"));
			return EAT_KILL;
		}
	}
	# :kohola.northshore.hawaii NOTICE Omster :*** Om removed you from #services with the message: Rawr
	elsif((index($nick, '.') >= 0) and ($msg =~ /\*\*\*\s+(\S+)\s+removed\s+you\s+from\s+(\S+)\s+with\s+the\s+message:\s+(\S+)/i))
	{
		my($nick, $chan, $reason) = ($1, $2, $3);
		xcprint("You were removed from $chan by $nick", undef, $server);
		Xchat::command("join $chan");
	}
	
	return Xchat::EAT_NONE;
}

sub do_print_whowas
{
	return do_print_whois(shift(@_), "whowas");
}

sub do_print_whois
{
	my($data, $title) = @_;
	$title = "whowas" if(!length($title) and ($data->{offline} =~ /was/i));
	$title = "/whois" unless(length($title));
	
	print_whois_start($title);
	print_whois_value("Nickname", $data->{nick});
	print_whois_value("Hostname", $data->{ident} . "@" . $data->{dhost}) if($data->{ident} or $data->{dhost});
	print_whois_value("Real Host/IP", $data->{host} . " / " . $data->{ip}) if($data->{host} or $data->{ip});
	print_whois_value("Real Name", $data->{gecos});
	print_whois_value("Modes", $data->{modes});
	print_whois_value("Channels", $data->{chans});
	print_whois_value("Away", $data->{away});
	print_whois_value("Oper", "$data->{nick} $data->{oper}") if($data->{oper});
	print_whois_value("SSL", "$data->{nick} $data->{ssl}") if($data->{ssl});
	print_whois_value("Extra", "$data->{nick} $data->{swhois}") if($data->{swhois});
	print_whois_value("Idle", ts2str($data->{idle})) if($data->{idle});
	print_whois_value("Offline", $data->{offline});
	print_whois_value("Signon", scalar(localtime($data->{signon}))) if($data->{signon});
	print_whois_value("Server", $data->{servername} . " (" . $data->{serverdesc} . ")") if($data->{servername} or $data->{serverdesc});
	print_whois_value("Options", $data->{options});
	print_whois_value("Last quit message", $data->{lastquit});
	print_whois_value("Registered", $data->{reggedon});
	print_whois_value("URL", $data->{url});
	print_whois_value("Info", $data->{info});
	print_whois_end($title);
	
	return 0;
}

sub print_whois_start
{
	my $title = shift;
	my $start = "\cC6\2.\2";
	my $mid = "\cC\2[\2 \cC3" . $title . "\cC \2]\2\cC6";
	my $end = "\cC6\2.\2\cC";
	my $glue = "";
	my $onoff = 0;
	
	for(my $i = 0; $i < (($config{whoiswidth} - displength($start) - displength($mid) - displength($end))/2); $i++)
	{
		$glue .= ($i % 2) ? "\cC6-" : "\cC13-";
	}
	
	Xchat::print $start . $glue . $mid . $glue . $end;
}

sub print_whois_end
{
	print_whois_start(@_);
}

sub print_whois_value
{
	my($key, $value) = @_;
	my $line = "";
	my $prefix = "";
	my $postfix = "";
	
	return unless($value);
	
	$config{whoisdepth} = 0 unless(defined($config{whoisdepth}));
	$config{whoisdepth} = 0 if($config{whoisdepth} > 3);

	$prefix = ($config{whoisdepth} % 2) ? "\cC13" : "\cC6";
	$prefix .= ($config{whoisdepth} < 2) ? "\2|\2\cC " : "|\cC ";
	$line = $postfix = $prefix;	

	$config{whoisdepth}++;
	
	for(my $i = 0; $i < $config{whoisdepth}; $i++)
	{
		$line .= " ";
	}
	
	$line .= "\2" . substr($key, 0, 1) . "\2" . substr($key, 1) . ": ";
	
	# pad_and_append - append as much of possible of $value onto $line, add $postfix and print.
	# return how much of $value was printed.
	my $prevaluelen = displength($line);
	$value = substr($value, pad_and_append($line, $value, $postfix));
	
	$line = $prefix;
	for(my $i = 0; $i < $prevaluelen; $i++)
	{
		$line .= " ";
	}
	
	while(length($value) > 0)
	{
		$value = substr($value, pad_and_append($line, $value, $postfix));
	}
}

sub pad_and_append
{
	my($line, $value, $postfix) = @_;
	my $vlen = length($value);
	my $dlen = displength($line);
	my $spacefor = $config{whoiswidth} - $dlen - displength($postfix);
	my $valueseg = rindex($value, " ", $spacefor);
	
	$valueseg = $vlen if($vlen < $spacefor);
	$line .= substr($value, 0, $valueseg);
	
	$dlen = displength($line);
	for(my $i = 0; $i < ($config{whoiswidth} - $dlen - displength($postfix)); $i++)
	{
		$line .= " ";
	}
	
	$line .= $postfix;
	
	Xchat::print $line;

	return $valueseg;
}

### --- End /whois code --- ###

### --- Start /whowas code --- ###
sub handle_whowas
{
	shift @{$_[0]}; # Lose the "whowas"
	
	my $server = Xchat::get_info("network");
	
	xcprint "/whowas on undefined server, output may be misdirected." unless($server);
	
	for my $nick (@{$_[0]})
	{	
		# Send out the /whois 
		Xchat::command("raw WHOWAS $nick $nick");
		Xchat::command("raw PRIVMSG $config{nickserv} :info $nick");
	
		# Initialise the structure we store the info in until it's printed.
		my $data = {
			nick => $nick,
			server => $server,
		};
		
		request($server, $data, \&on_whoisraw, $config{whoistimeout}, \&do_print_whowas, "Whowas on $nick");
	}
	
	return Xchat::EAT_ALL;
}
### --- End /whowas code --- ###

### --- Start mass mode code --- ###
sub do_massmode
{
	my($chan, $char, $direction) = @_;
	my $maxmodes = get_maxmodes();
	my @users = Xchat::get_list("users");
	
	my $modes = [];
	my $params = [];
	
	for my $user (@users)
	{
		debug("$user->{nick} has prefix $user->{prefix}");
		
		if($direction eq '+')
		{
			next if($modelevels{$modemap{$user->{prefix}}} >= $modelevels{$char}); # Don't set modes on users who already have the same or higher access
		}
		else
		{
			next if($modemap{$user->{prefix}} ne $char); # Don't try and remove the mode on users we don't see as having it.
			next if(Xchat::nickcmp($user->{nick}, Xchat::get_info("nick")) == 0); # Don't remove modes on ourself
		}
		
		push(@{$modes}, $direction . $char);
		push(@{$params}, $user->{nick});
	}
	
	send_massmodes($modes, $params, $chan);
}

sub handle_mmode
{
	my $cmd = $_[0][0];
	my $chan = $_[0][1];
	my @mode = ();
	$chan = Xchat::get_info("channel") unless($chan);
		
	@mode = ('o', '+') if($cmd =~ /mop/i);
	@mode = ('h', '+') if($cmd =~ /mhop/i);
	@mode = ('v', '+') if($cmd =~ /mvop/i);
	@mode = ('o', '-') if($cmd =~ /mdeop/i);
	@mode = ('h', '-') if($cmd =~ /mdehop/i);
	@mode = ('v', '-') if($cmd =~ /mdevop/i);
	
	do_massmode($chan, @mode);
	return Xchat::EAT_ALL;
}

sub handle_mvopng
{
	my $chan = $_[0][1];
	$chan = Xchat::get_info("channel") unless($chan);
		
	my @users = Xchat::get_list("users");
	
	my $modes = [];
	my $params = [];
	
	for my $user (@users)
	{
		next if($user->{nick} =~ /Guest([0-9]+).*/i);		
		next if(length($modemap{$user->{prefix}}));
		
		push(@{$modes}, '+v');
		push(@{$params}, $user->{nick});
	}
	
	send_massmodes($modes, $params, $chan);
	
	return Xchat::EAT_XCHAT;
}
### --- End mass mode code --- ###

### --- Start timed ban code --- ###
sub handle_timedkb
{
	
}

sub handle_timedkblist
{
	
}

sub handle_timedkbremove
{
	
}
### --- End timed ban code --- ###

### --- Start media control code --- ###
sub handle_audio_nowplaying
{
	my $colourful = ($_[0][0] =~ /^np[l]?$/i) ? 1 : 0;
	my $verbose = ($_[0][0] =~ /^npl[b]?$/i) ? 0 : 1;
	my @dests = split(/ /, $_[1][1]);
	my $player = 'audacious';
	
	my($path, $artist, $album, $song, $title, $done, $length, $bitrate, $bps, $freq, $filename, $version, $comment, $genre, $extension, $replaygain, @meta);
	
	sub amk
	{
		use URI;
	
		my $a = shift;
		
		if($player eq 'amarok')
		{
			return mychomp(qx(dcop amarok player $a));
		}
		elsif($player eq 'audacious')
		{
			my %map = (	'currentTime'	=> 'current-song-output-length',
						'path'			=> 'current-song-filename',
						'artist'		=> 'current-song-tuple-data artist',
						'album'			=> 'current-song-tuple-data album',
						'title'			=> 'current-song-tuple-data title',
						'totalTime'		=> 'current-song-length',
						'sampleRate'	=> 'current-song-frequency-khz',
						'comment'		=> 'current-song-tuple-data comment',
						'genre'			=> 'current-song-tuple-data genre',
						'bitrate'		=> 'current-song-bitrate-kbps'
						);
						
			my $out = mychomp(qx(audtool $map{$a}));
			
			if($a eq 'path')
			{
				$out = URI->new($out)->file;
			}
			
			return $out;
		}
		else
		{
			return '';
		}
	};
	sub r { my($a, @b) = @_; return "" unless($a); my $r = ((int(rand(3)) == 2) ? $a . "\017" : "\017" . $a); $r .= r(@b); return $r }
	sub b { my($a, @b) = @_; return "" unless($a); my $r = "\002" . $a . "\002"; $r .= b(@b); return $r; }
	
	$path = amk 'path';
	
	if($path and ($path ne 'call failed'))
	{
		$done		= amk 'currentTime';
		$extension  = uc substr($path, rindex($path, ".")+1);
	
		if($extension eq 'FLAC')
		{
			my $str = `metaflac --show-bps --show-sample-rate --show-total-samples --show-tag=ALBUM --show-tag=ARTIST --show-tag=DESCRIPTION --show-tag=GENRE --show-tag=TITLE --show-tag=REPLAYGAIN_ALBUM_GAIN --show-tag=COMMENT "$path"`;
			my($rawbps, $rawfreq, $samples, @tagdata) = split(/[\n\r]/, $str);
			my %tags = (
				'album'					=> [],
				'title'					=> [],
				'comment'				=> [],
				'description'			=> [],
				'genre'					=> [],
				'title'					=> [],
				'replaygain_album_gain'	=> []
			);
			
			for(@tagdata)
			{
				if(/^([^=]+)=(.*?)$/)
				{
					push(@{$tags{lc $1}}, $2);
				}
			}
			
			$artist = $tags{'artist'}[0] or '';
			$album = $tags{'album'}[0] or '';
			$song = $tags{'title'}[0] or '';			
			$length = sprintf("%d:%02d", int(($samples)/(60*$rawfreq)), int(($samples%($rawfreq*60))/$rawfreq));
			$bitrate = int(((-s $path)*$rawfreq)/($samples*128));
			$bps = $rawbps;
			$freq = $rawfreq/1000;
			$comment = join('; ', @{ $tags{'comment'} }, @{ $tags{'description'} });
			$genre = $tags{'genre'}[0] or '';
			$replaygain = $tags{'replaygain_album_gain'}[0] or '';
		}
		elsif($extension eq 'MP3')
		{
			if(`mp3gain -s c "$path"` =~ /^Recommended "Album" dB change: (.*?)$/m)
			{
				$replaygain = sprintf("%.2f dB", $1);
				$replaygain = '+' . $replaygain unless(substr($replaygain, 0, 1) eq '-');
			}

			$artist		= amk 'artist';
			$album		= amk 'album';
			$song		= amk 'title';		
			$length		= amk 'totalTime';
			$freq		= amk('sampleRate')/1000;
			$comment	= amk 'comment';
			$genre		= amk 'genre';
			$bitrate	= amk 'bitrate';
		}
		
		$title = join(' - ', $artist, $album, $song);		
	}
	else
	{
		return Xchat::EAT_ALL;
	}
	
	push(@meta, "$bitrate kbit") if($bitrate > 320);
	push(@meta, "$bps bps") if($bps and (int($bps) > 16));
	push(@meta, "$freq kHz") if($freq != 44.1);
	push(@meta, "$extension") if($extension);
	push(@meta, "($replaygain ReplayGain)") if($replaygain);
	push(@meta, "($genre)") if($genre);
	push(@meta, "($comment)") if($comment);
		
	my $command = join(' ', 'me', r(qw(n p :)), b('['), $title, b(']'), b('['), $done, '/', $length, b(']'));
	
	if($verbose)
	{
		$command .= join(' ', '', b('['), @meta, b(']'));
	}
	
	unless($colourful)
	{
		$command =~ s/(\002|\017)//g;
	}
	
	# Remove possibility of executing extra commands by putting a newline in a tag field
	$command =~ s/(\n|\r)//g;
	
	if(@dests)
	{
		Xchat::command($command, split(/,/, $_, 2)) for(@dests);
	}
	else
	{
		Xchat::command($command);
	}		
	
	# system('amsn-psm-trigger.pl "(8)' . $song . ' - ' . $artist . '(8)"');
	
	return Xchat::EAT_ALL;
}
### --- End media control code --- ###

### --- Start abuse code --- ###
sub handle_slap
{
	my $target = $_[1][1];
	my $trout = undef;
	
	my @action = ("slaps", "slashes", "murders", "attacks", "mutilates", "bitchslaps", "pokes", "stabs", "spanks", "whips", "thumps", "whacks", "wallops", "baseball-bats", "beautifies", "crushes", "crunches", "squashes", "quashes", "tickles");
	my @sizes = ("a large", "a big", "a humongous", "an enormous", "a huge", "a monstrous", "a smelly", "a giant", "a large, smelly", "a bloody", "a disgusting", "a bloody, disgusting", "a large, smelly, disgusting", "a", "a dead", "a mutilated", "a wet and mouldy", "a slimy");
	my @amounts = ("a bit", "a lot", "a few times", "several times", "many times", "infinitely", "repeatedly", "continuously", "forever");
	my @how = ("with", "against", "on top of", "inside", "against the outside of", "underneath");

	my $index = rand @amounts;
	my $amount = @amounts[$index];
	my $index = rand @sizes;
	my $size = @sizes[$index];
	my $index = rand @action;
	my $action = @action[$index];
	my $index = rand @how;
	my $how = @how[$index];

	my $rand = int(rand 10);
	my $dir = Xchat::get_info("xchatdir");
	
	my $file = '< ' . $dir . '/data/' . (($rand == 7) ? 'latin.txt' : 'fish.txt');

	open(FH, $file) or debug("/me has no fish :(");
	rand($.) < 1 && ($trout = $_) while <FH>;
	close(FH);
	chomp $trout;

	Xchat::command(join(' ', "me", $action, $target, $amount, $how, $size, $trout));
	return Xchat::EAT_ALL;
}
### --- End abuse code --- ###

### --- Start autojoin code --- ###
sub read_perform_file
{
	my $network = shift;
	my $dir = Xchat::get_info("xchatdir") . "/perform/";
	$network = Xchat::get_info("network") unless($network);
	$network = lc($network);
	my $path = $dir . $network . ".txt";
	
	# Nuke current data, should have called write_perform_file() if you wanted it saved :P
	my $data = {};
	
	open(FH, "< $path");
	
	while(my $line = <FH>)
	{
		if($line =~ /([a-z]+)=(.*)/i)
		{
			my $key = lc($1);
			my $value = $2;
			
			$data->{$key} = [] unless(ref($data->{$key}) eq "ARRAY");
			
			push(@{ $data->{$key} }, $value);
		}
	}
	
	close(FH);
	
	return $data;
}

sub write_perform_file
{
	my $data = shift;
	my $network = shift;
	my $dir = Xchat::get_info("xchatdir") . "/perform/";
	$network = Xchat::get_info("network") unless($network);
	$network = lc($network);
	my $path = $dir . $network . ".txt";
	my $backuppath = $dir . "/backups/" . join('.', $network, time, "txt");
	
	copy($path, $backuppath) or debug("Writing backup of perform file failed: $!");
	
	open(FH, "> $path");
	
	while(my($key, $value) = each(%{$data}))
	{
		for my $val (@{$value})
		{
			print FH "$key=$val\n";
		}
	}
	
	close(FH);
}

sub handle_perform_pass_set
{
	my $pass = $_[1][1];
	(xcprint("No parameter given") and (return Xchat::EAT_ALL)) unless($pass);
		
	my $data = read_perform_file();
	$data->{password} = [ $pass ];
	write_perform_file($data);
	
	xcprint "Set password for " . Xchat::get_info("network");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_pass_get
{
	my $data = read_perform_file();
	my $net = Xchat::get_info("network");
	
	xcprint(($data->{password}[0]) ? "Password for $net is $data->{password}[0]" : "No password found");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_preraw_add
{
	my $raw = $_[1][1];
	(xcprint("No parameter given") and (return Xchat::EAT_ALL)) unless($raw);
		
	my $data = read_perform_file();
	$data->{preraw} = [] unless(ref($data->{preraw}) eq "ARRAY");
	push(@{ $data->{preraw} }, $raw);
	write_perform_file($data);
	
	xcprint "Added line to pre-autoperform list for " . Xchat::get_info("network");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_preraw_del
{
	my $raw = $_[1][1];
	(xcprint("No parameter given") and (return Xchat::EAT_ALL)) unless($raw);
		
	my $data = read_perform_file();
	@{ $data->{preraw} } = grep { $_ ne $raw } @{ $data->{preraw} };
	write_perform_file($data);
	
	xcprint "Removed line from pre-autoperform list for " . Xchat::get_info("network");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_preraw_list
{
	my $data = read_perform_file();
	xcprint "Pre-autoperform commands:";
	xcprint "\t$_" for(@{ $data->{preraw} });
	
	return Xchat::EAT_ALL;
}

sub handle_perform_add
{
	my $channel = $_[0][1];
	$channel = Xchat::get_info("channel") unless($channel);
		
	my $data = read_perform_file();
	$data->{chan} = [] unless(ref($data->{chan}) eq "ARRAY");
	push(@{ $data->{chan} }, $channel);
	write_perform_file($data);
	
	xcprint "Added $channel to autojoin list for " . Xchat::get_info("network");
	Xchat::command("join $channel");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_del
{
	my $channel = $_[0][1];
	$channel = Xchat::get_info("channel") unless($channel);
		
	my $data = read_perform_file();
	@{ $data->{chan} } = grep { $_ ne $channel } @{ $data->{chan} };
	write_perform_file($data);
	
	xcprint "Removed $channel from autojoin list for " . Xchat::get_info("network");
	Xchat::command("part $channel Channel removed from autojoin list");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_list
{
	my $data = read_perform_file();
	xcprint "Autojoin channels:";
	xcprint "\t$_" for(@{ $data->{chan} });
	
	return Xchat::EAT_ALL;
}

sub handle_perform_raw_add
{
	my $raw = $_[1][1];
	(xcprint("No parameter given") and (return Xchat::EAT_ALL)) unless($raw);
		
	my $data = read_perform_file();
	$data->{raw} = [] unless(ref($data->{raw}) eq "ARRAY");
	push(@{ $data->{raw} }, $raw);
	write_perform_file($data);
	
	xcprint "Added line to autoperform list for " . Xchat::get_info("network");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_raw_del
{
	my $raw = $_[1][1];
	(xcprint("No parameter given") and (return Xchat::EAT_ALL)) unless($raw);
		
	my $data = read_perform_file();
	@{ $data->{raw} } = grep { $_ ne $raw } @{ $data->{raw} };
	write_perform_file($data);
	
	xcprint "Removed line from autoperform list for " . Xchat::get_info("network");
	
	return Xchat::EAT_ALL;
}

sub handle_perform_raw_list
{
	my $data = read_perform_file();
	xcprint "Autoperform commands:";
	xcprint "\t$_" for(@{ $data->{raw} });
	
	return Xchat::EAT_ALL;
}

### --- End autojoin code --- ###

### --- Start auto-identify code --- ###
sub handle_connect
{
	my $server = Xchat::get_info("network");	
	my $data = read_perform_file();
	
	debug("Executing first on-connect commands for '$server' and hooking ident request");
	
	Xchat::command($_, undef, $server) for(@{ $data->{preraw} });
	
	request($server, { server => $server }, sub { return PASS; }, $config{identtimeout}, \&do_ident_timeout, "Ident request");
	
	return Xchat::EAT_NONE;
}

sub do_ident_timeout
{
	my $server = $_[0]->{server};
	debug("Ident request on '$server' timed out, identing and starting timer to execute perform commands");
	do_identify($server);
	request($server, { server => $server }, sub { return PASS; }, $config{identtimeout}, \&do_perform_timeout, "Perform request");
	return PASS_KILL;
}

sub do_perform_timeout
{
	my $server = $_[0]->{server};
	debug("Ident confirmation request on '$server' timed out, executing perform commands");
	do_perform($server);
	return PASS_KILL;
}

sub do_identify
{
	my $net = shift;
	my $nick = Xchat::get_info("nick");
	debug("do_identify() wasn't given a network") unless($net);
		
	my $data = read_perform_file($net);

	if((ref($data->{password}) eq "ARRAY") and @{$data->{password}})
	{
		xcprint("Identifying for " . $nick . "'s nickname on '$net'", $net, $net);
		Xchat::command("raw PRIVMSG NickServ :identify " . $data->{password}[0], undef, $net);
	}
	else
	{
		xcprint("No password found for " . $nick . "'s nickname on '$net'", $net, $net);
	}
}

sub do_perform
{
	my $net = shift;
	debug("do_perform() wasn't given a network") unless($net);
		
	my $data = read_perform_file($net);

	if((ref($data->{raw}) eq "ARRAY") and @{$data->{raw}})
	{
		xcprint("Executing raw commands for $net", $net, $net);
		Xchat::command($_, undef, $net) for(@{$data->{raw}});
	}
	
	if((ref($data->{chan}) eq "ARRAY") and @{$data->{chan}})
	{
		# XXX - Check that the /join sent isn't over 512 characters
		xcprint("Joining channels for $net", $net, $net);
		
		
		Xchat::command("join " . join(',', @{$data->{chan}}), undef, $net);
	}
}

sub handle_doperform
{
	Xchat::command($_, undef, Xchat::get_info("network")) for(@{ read_perform_file->{preraw} });
	
	do_perform(Xchat::get_info("network"));
	return Xchat::EAT_ALL;
}

sub handle_identify
{
	do_identify(Xchat::get_info("network"));
	return Xchat::EAT_ALL;
}

### --- End auto-identify code --- ###

### --- Start auto-invite code --- ###

sub on_invite
{
	# :megumi.chatspike.net 473 Omster #fuck :Cannot join channel (+i)
	my $chan = $_[0][3];
	my $net = Xchat::get_info("network");
	
	debug("Sending /cs invite for $chan on $net");
	Xchat::command("raw PRIVMSG $config{chanserv} :invite $chan", undef, $net);
	request($net, { server => $net, channel => $chan, nick => Xchat::get_info("nick") }, \&on_csinvite, $config{invitetimeout}, sub { }, "/cs invite on $chan/$net" );
	
	return Xchat::EAT_NONE;
}

sub on_csinvite
{
	# :ChanServ!services@chatspike.net INVITE Omster :#fuck
	if(lc($_[0][1]) eq lc("INVITE"))
	{
		my($nick) = split(/!/, substr($_[0][0], 1));
		my $chan  = substr($_[0][3], 1);
		my $data = $_[2];
		
		if((Xchat::nickcmp($nick, $config{chanserv}) == 0) and (Xchat::nickcmp($_[0][2], $data->{nick}) == 0) and ($chan eq $data->{channel}))
		{
			Xchat::command("JOIN $chan", undef, $data->{server});
			return EAT_KILL;
		}
	}
	
	return PASS;
}

### --- End auto-invite code --- ###

### --- Start message redirection code --- ###
sub on_wallops
{
	my $sender  = substr($_[0][0], 1);
	my $message = substr($_[1][2], 1);
	
	Xchat::command("query $config{globalstab}");
	Xchat::set_context($config{globalstab}, Xchat::get_info("network"));
	Xchat::emit_print("Receive Wallops", $sender, $message);
	return Xchat::EAT_ALL;
}

sub on_highlight
{
	my $net = Xchat::get_info("network");
	my $chan = Xchat::get_info("channel");
	my $nick = $_[0][0];
	my $msg = $_[0][1];
	
	Xchat::command("query $config{highlighttab}") unless(Xchat::find_context($config{highlighttab}, $net));
	
	if($_[1] eq "msg")
	{
		Xchat::print("(\0034" . $nick . "/" . $chan . "\003)\t" . $msg, $config{highlighttab}, $net);
	}
	else
	{
		Xchat::print(" \0034*" . $nick . "/" . $chan . " " . $msg, $config{highlighttab}, $net);
	}
	return Xchat::EAT_NONE;
}
### --- End message redirection code --- ###

### --- Start +g support code --- ###

sub handle_badword
{
	my(undef, @words) = @{$_[0]};
	
	my $modes = [];
	my $params = [];
	
	foreach (@words)
	{
		push(@{$modes}, '+g');
		push(@{$params}, $_);
	}
	
	send_massmodes($modes, $params);
	
	return Xchat::EAT_XCHAT;
}

### --- End +g support code --- ###

sub on_join
{
	my $nick = $_[0][0];
	my $chan = $_[0][1];
	my $host = $_[0][2];
	my $flag = $_[0][3];
	
	unless($flag)
	{
		Xchat::emit_print("Join", $nick, $chan, $host, 1);
		
		my @clones = ();
		
		for my $user (Xchat::get_list("users"))
		{
			my(undef, $uhost) = split(/@/, $user->{host}, 2);
			my(undef, $jhost) = split(/@/, $host, 2);
			
			if($uhost and $jhost and (lc($uhost) eq lc($jhost)))
			{
				push(@clones, $user->{prefix} . $user->{nick});
			}
		}

		Xchat::print("  -\x{00BB} Clones on \cC12\002(\002\cC" . $chan . "\cC12\002)\002\cC for \cC12\002(\002\cC" . $nick . "\cC12\002)\002\cC are \cC12\002(\002\cC" . join(' ', @clones) . "\cC12\002)\002\cC") if(@clones);
		
		return Xchat::EAT_XCHAT;
	}
	else
	{
		return Xchat::EAT_NONE;
	}
}

sub handle_test { Xchat::EAT_NONE; }

sub handle_decode
{
	my $coded = $_[0][1];
	
	my $ip = join('.', hex(substr($coded,0,2)), hex(substr($coded,2,2)), hex(substr($coded,4,2)), hex(substr($coded,6,2)));
	my $host = gethostbyaddr(inet_aton($ip), AF_INET);
	
	$host = "Unresolvable" unless(length($host));

	xcprint("Decoded $coded to $ip (\002$host\002)");
	
    return Xchat::EAT_ALL;
}

sub handle_sysinfo_gfx
{
	my $xinfo = `xdpyinfo`;
	my $glxinfo = `glxinfo`;

	my($xtitle, $xversion, $res, $card, $bits);
	
	if($xinfo =~ /^\s*(X.*?) version:\s+(.*)$/im)
	{
		$xtitle = $1;
		$xversion = $2;
	}
	else
	{
		$xtitle = "X11";
		$xversion = "Unknown";
	}
	
	if($glxinfo and ($glxinfo =~ /^\s*OpenGL renderer string:\s+(.*)/im) and ($1 !~ /Mesa GLX Indirect/i))
	{
		$card = $1;
	}
	else
	{
		$card = (`lspci` =~ /^.*?VGA compatible controller:\s*(.*)$/im) ? $1 : "Unknown";
	}
	
	$res = ($xinfo =~ /^\s+dimensions:\s+(\S+).*$/im) ? $1 : "Unknown";
	$bits = (`xwininfo -root` =~ /^\s+Depth: (.+)/im) ? $1 : "Unknown";
	
	my $str = "(\002GFX\002):\002[\002 $card \002]\002 (\002$xtitle\002):\002[\002$xversion\002]\002 (\002Res\002):\002[\002$res @ $bits bits\002]\002";
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}


sub handle_sysinfo_aud
{
	my $lspci = `lspci`;
	
	my $sndcard = ($lspci =~ /^.*Multimedia audio controller:\s*(.*)$/im) ? $1 : undef;
	my $inpdev = ($lspci =~ /^.*?Input device controller:\s*(.*?)$/im) ? $1 : undef;
	
	my $str = "";
	$str .= "(\002SoundCard\002):\002[\002$sndcard\002]\002" if($sndcard);
	$str .= " " if($sndcard and $inpdev);
	$str .=	"(\002Input Device Controller\002):\002[\002$inpdev\002]\002" if($inpdev);
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_mb
{
	my $isabrg = (`lspci` =~ /^.*ISA bridge:\s*(.*)$/im) ? $1 : "Unknown";
	
	my $str  = "";#"(\002ISA Bridge\002):\002[\002$isabrg\002]\002";
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_kern
{
	chomp(my $ker = `uname -srm`);
	my $str = "(\002Kernel\002):\002[\002$ker\002]\002";
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_cpu
{
	my $cpuinfo = read_file('/proc/cpuinfo');
	
	my $model = ($cpuinfo =~ /^\s*model name\s*:\s*(.*)$/im) ? $1 : "Unknown";
	my $freq = ($cpuinfo =~ /^\s*cpu MHz\s*:\s*(.*)$/im) ? int($1) : "Unknown";
	my $cache = ($cpuinfo =~ /^\s*cache size\s*:\s*(.*)$/im) ? $1 : "Unknown";
	my $bogomips = ($cpuinfo =~ /^\s*bogomips\s*:\s*(.*)$/im) ? $1 : "Unknown";
	my $vendor = ($cpuinfo =~ /^\s*vendor_id\s*:\s*(.*)$/im) ? $1 : "Unknown";

	my $str = "(\002CPU\002):\002[\002$model @ $freq MHz\002]\002 \002Cache\002: $cache";

	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_disk
{
	my($total, $used, $free) = (0, 0, 0);
	my @pci = `lspci`;
	my %ints = ();
	
	foreach (@pci)
	{
		if(/^.*IDE interface:\s*(.*)$/i)
		{
			$ints{$1} = 1 unless($ints{$1});
			$ints{$1}++;
		}
	}

	my @tmp = ();	
	push(@tmp, $_ . (($ints{$_} > 1) ? " x $ints{$_}" : "")) foreach(keys(%ints));

	foreach(`df`)
	{
		if(/(\S+)\s+(\d+)\s+(\d+)\s+(\d+).*/)
		{
			$total += $2;
			$used += $3;
			$free += $4;
		}
	}
	
	my $str = "(\002Mounted Disks\002):\002[\002 Total: " . int($total/1024/1024) . " GiB, Free: " . int($free/1024/1024) . " GiB\002]\002";

	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say (\002Disk Controllers\002):\002[\002" . join(', ', @tmp) . "\002]\002 $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_net
{
	my($in, $out) = (0, 0);
	
	foreach my $dev (@{$config{networkdevs}})
	{
		if(`ifconfig $dev` =~ /^\s*RX bytes:([0-9]+)\s+\(([0-9\.]+) Mb\)\s+TX bytes:([0-9]+)\s+\(([0-9\.]+) Mb\)/im)
		{
			$in += $1;
			$out += $3;
		}
	}

	my $str = "(\002Network\002):\002[\002" . int($in/1024/1024) . " MiB In, " . int($out/1024/1024) . " MiB Out\002]\002";
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_temp
{
	my @sens = `sensors`;
	my @res = ();
	
	foreach(@sens)
	{
		push(@res, "$1 Temp $2") if(/(.*?)\s+Temp:\s+\+(\S+)/i);
	}
	
	my $str = "(\002Temperature\002): \002[\002" . join(' ', @res) . "\002]\002";
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_ram
{
	my @tmp = ();
	
	foreach(`free -b`)
	{
		if(/^\s*(Mem|Swap):\s+(\d+)\s+(\d+)\s+(\d+).*$/i)
		{
			my $n = ($1 eq 'Mem') ? 'RAM' : $1;
			push(@tmp, "(\002$n\002):\002[\002" . int($3/1024/1024) . "/" . int($2/1024/1024) . " MiB used\002]\002");
		}
	}
	
	my $str = join(' ', @tmp);
	
	return $str unless(ref($_[0]) eq 'ARRAY');
	Xchat::command("say $str");
	return Xchat::EAT_ALL;
}

sub handle_sysinfo_all
{
	my $header =  length(Xchat::get_info("channel")) + length("PRIVMSG  :\r\n");
	my @parts = (handle_sysinfo_kern(), handle_sysinfo_cpu(), handle_sysinfo_misc(), handle_sysinfo_gfx(), handle_sysinfo_mb(), handle_sysinfo_ram(), handle_sysinfo_disk(), handle_sysinfo_net(), handle_sysinfo_temp());
	my $out;
	
	foreach my $part (@parts)
	{
		if((length($out) + length($part) + $header) < 400)
		{
			debug((length($out) + length($part) + $header) . " is less than 400, appending");
			$out = length($out) ? "$out $part" : $part;
		}
		else
		{
			Xchat::command("say $out");
			$out = $part;
		}
	}
	
	Xchat::command("say $out") if(length($out));
	return Xchat::EAT_ALL;
}
	
sub handle_sysinfo_misc
{
	chomp(my $procs = `ps ax | wc -l`);

	my($uptime, $load) = (`uptime` =~ /^.*?up\s+(.*?),\s+\d+\s+users,\s+load average:\s+([\d\.]+).*$/im) ? ($1, $2) : ("Unknown", "Unknown");
	
	return "(\002Uptime\002):\002[\002$uptime\002]\002 (\002Load\002):\002[\002$load\002]\002 (\002Processes\002):\002[\002$procs\002]\002";
}