### WARNING! THIS VERSION HAS BEEN SUPERSEDED BY VERSION 4 ###
 
# Copyright (c) 2010, Insomnia 24/7 All rights reserved. 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
 
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer. Redistributions in binary
# form must reproduce the above copyright notice, this list of conditions and
# the following disclaimer in the documentation and/or other materials
# provided with the distribution. Neither the name of Insomnia 24/7 nor
# the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
 
#!/usr/local/bin/perl
 
package nanobot;
 
use IO::Socket::INET6;
use Module::Load;
 
$version = "Nanobot 3.0";
$server = 'irc.server.tld'; # Hostname, IPv4 or IPv6 address.
$port = 6667;
$sslport = 6697;
$botnick = 'nanobot'; # Bots nickname
$botuser = 'nanobot'; # Bots username
$nsp = ''; # NickServ pasword (if not registered, leave empty)
@channels = ("#bot", "#yourchan");
@opers = ("insomnia247.nl", "another.oper.com", "127.0.0.1"); # Oper(s) hostmask(s)
$modchan = '#yourchan';
$datadir = 'botdata';
$moddir = 'modules';
@autoload = (); # List modules to load on startup. Example: @autoload("mymodule", "kickban");
$wisecrack_seen_botnick = "DURP!?";
$wisecrack_seen_self = "I can see you! You're right there! That's right, I can see.";
$wait_for_ping = 0; # Set to 1 if your network requires a ping reply before allowing to join channels.
$connect_timeout = 120; # Seconds to wait before giving up connnecting to the IRC server.
$ping_timeout = 300; # Seconds to wait before assuming timeout and attempting reconnect.
 
# These are set by the bot itself, do not modify
$logging = 1;
$debug = 0;
$op_all = 0;
$hop_all = 0;
$voice_all = 0;
$botstatus = 1;
$startup = time;
%seenlog = ();
%seentime = ();
$public_modules = 0;
@modules = ();
 
##### Process commandline options #####
foreach $arg (@ARGV) {
	if ($arg eq "-h" or $arg eq "--help") {
		print "options:\n";
		print "  -h or --help		Print this help.\n";
		print "  -v or --version	Print version number and exit.\n";
		print "  -q or --quiet		Activate silent mode (Nothing is printed to the screen.)\n";
		print "  -d or --debug		Enable debugging output. (Use twice for greater effect.\n";
		print "  -s or --ssl		Use ssl.\n";
		&shutd;
	}
 
	if ($arg eq "-v" or $arg eq "--version") { print "version: $version\n"; &shutd;}
	if ($arg eq "-q" or $arg eq "--quiet") {$logging = 0;}
	if ($arg eq "-d" or $arg eq "--debug") {$debug++;}
	if ($arg eq "-s" or $arg eq "--ssl") {
		$ssl = 1;
		$port = $sslport;
		use IO::Socket::SSL;
	}
}
 
##### Kick things off ######
logts("Nanobot is starting...\n");
&directories;
while(1) {
	&connct;
	sleep(2);
}
 
##### Check for data and module directories ######
sub directories {
	logts("Data folder .......... ");
	if (-d $datadir) {
		logts("[OK]\n");
	} else {
		if (mkdir $datadir) {
			logts("[CREATED]\n");		
		} else {
			logts("[FAILED]\n");
		}
	}
 
	logts("Modules folder ....... ");
	if (-d $moddir) {
		logts("[OK]\n");
	} else {
		if (mkdir $moddir) {
			logts("[CREATED]\n");		
		} else {
			logts("[FAILED]\n");
		}
	}
}
 
##### Screen output subroutine #####
sub logts {
	if ($logging == 1){
		print STDOUT "$_[0]";
	}
}
 
##### Debug output subroutine #####
sub debug {
	if ($debug >= 1){
		print STDOUT "$_[0]";
	}
}
sub debug_extra {
	if ($debug == 2){
		($s,$m,$h,$d,$mo) = gmtime( time );
		print STDOUT "[$h:$m:$s] $_[0]";
	}
}
 
##### Connect to server ######
sub connct {
	debug("Attempting connect.\n");
 
	# Connect to server
	logts("Connecting ........... ");
	$sock = IO::Socket::INET6->new(	PeerAddr => $server,
									PeerPort => $port,
									Proto => 'tcp',
									Domain => AF_UNSPEC,
									Timeout => $connect_timeout) or die "Connect error: $!\n";
 
	logts("[OK]\n");
 
	if($ssl) {
		logts("Starting SSL ......... ");
		IO::Socket::SSL->start_SSL( $sock,
									SSL_verify_mode => 0, # Do not verify certificate
									) or die "SSL handshake failed: $SSL_ERROR";
		logts("[OK]\n");
	}
 
	debug("Connected to server: $server\non port: $port\n");
 
	# Set nick and username
	logts("Sending user info .... ");
	snd("NICK $botnick");
	snd("USER $botuser 8 *  :$version");
	logts("[OK]\n");
 
	# Catch SIGALRM from the OS when timeout expired.
	local $SIG{ALRM} = sub {$sock->shutdown(0);};
 
	# Send all incomming data to the parser
	while (<$sock>) {
		eval {
			alarm 0;
			&parse($_);
			alarm $ping_timeout;
		};
	}
 
	debug("Closing socket.\n");
	close $sock;
	logts("Error: Lost connection, reconnecting...\n");
	$login = undef;
}
 
##### Subroutine for sending data to the IRC server #####
sub snd {
	print $sock "$_[0]\n";
	debug_extra("<== $_[0]\n");
}
 
##### Subroutine for sending messages to the IRC server #####
sub msg {
	snd("PRIVMSG $_[0] :$_[1]");
}
 
##### Subroutine for sending notices to the IRC server #####
sub ntc {
	snd("NOTICE $_[0] :$_[1]");
}
 
##### Socket input parser #####
sub parse {
	debug_extra("==> $_");
 
	# Remove /r and /n
	chop($_);
	chop($_);
 
	# Do nickserv auth and channel join
	if(!$login && ($wait_for_ping == 0)) {
		&login;
	}
 
	# Handle PING and rejoin on kick
	if (/^PING \:(.+)/) {
		debug("Received PING request.\n");
		snd("PONG :$1");
 
		if(!$login && ($wait_for_ping == 1)) {
			&login;
		}
 
		debug("Sent PONG reply.\n");
		return;
	} elsif (/^\:(.+?)!(.+?)@(.+?) KICK #(.+?) \Q$botnick\E \:(.+?)/) {
		snd("JOIN #$4");
		debug("Rejoined channel $4 after kick.\n");
		return;
	}
 
	# Hook for modules that want raw data
	foreach $module (@modules) {
		if( $module->can('raw') ) {
			$module->raw($_);
		}
	}
 
	# Process generic NOTICE
	if (/^\:(.+?)!(.+?)@(.+?) NOTICE (.+?) \:(.+)/) {
		# Hook for modules that want all messages
		foreach $module (@modules) {
			if( $module->can('notice') ) {
				$module->notice($1, $2, $3, $4, $modchan, $botnick, $5);
			}
		}
		return;
	}
 
	# Process generic JOIN actions
	if (/^\:(.+?)!(.+?)@(.+?) JOIN \:(.+)/) {
 
		# Parse regex results
		$join{from} = $1;
		$join{user} = $2;
		$join{host} = $3;
		$join{rcpt} = $4;
		$join{text} = $5;
		$args = $join{text};
 
		$from = $join{from};
		$uname = $join{user};
		$host = $join{host};
		$from_chan = $join{rcpt};
 
		# Hook for modules that want join actions
		foreach $module (@modules) {
			if( $module->can('join') ) {
				$module->join($from, $uname, $host, $from_chan, $modchan, $botnick);
			}
		}
	}
 
	# Process autojoin actions for modchan
	if($botstatus == 1){
		if (/^\:(.+?)!(.+?)@(.+?) JOIN \:$modchan/) {
			$join{from} = $1;
			$join{user} = $2;
			$join{host} = $3;
			$join{rcpt} = $4;
			$join{text} = $5;
			$args = $join{text};
			$from = $join{from};
			$send_host = $join{host};
 
			if ($op_all == 1) {
				snd("MODE $modchan +o $1");
			} else {
				foreach $aop (@aop) {
					chomp($aop);
					if ($aop eq $3) {
						snd("MODE $modchan +o $1");
						logts("AOPped $1\n");
					}
				}
			}
 
			if ($hop_all == 1) {
				snd("MODE $modchan +h $1");
			} else {
				foreach $ahop (@ahop) {
					chomp($ahop);
					if ($ahop eq $3) {
						snd("MODE $modchan +h $1");
						logts("AHOPped $1\n");
					}
				}
			}
 
			if ($voice_all == 1) {
				snd("MODE $modchan +v $1");
			} else {
				foreach $av (@av) {
					chomp($av);
					if ($av eq $3) {
						snd("MODE $modchan +v $1");
						logts("AVoiced $1\n");
					}
				}
			}
 
			foreach $ak (@ak) {
				chomp($ak);
				if ($ak eq $3) {
					snd("KICK $modchan $1");
					logts("AKicked $1\n");
				}
			}
			return;
		}
	}
 
	# Process messages
	if (/^\:(.+?)!(.+?)@(.+?) PRIVMSG (.+?) \:(.+)/) {
		$privmsg{from} = $1;
		$privmsg{user} = $2;
		$privmsg{host} = $3;
		$privmsg{rcpt} = $4;
		$privmsg{text} = $5;
		$args = $privmsg{text};
 
		$from = $privmsg{from};
		$uname = $privmsg{user};
		$host = $privmsg{host};
		$from_chan = $privmsg{rcpt};
 
		# Log data for seen log
		if($args !~ /\a/) {
			$seenlog{lc $from} = $args;
			$seentime{lc $from} = time;
			debug("$from_chan <$from> $args\n");
		} else {
			debug("Ignored term bell from $from for seen log.\n");
		}
 
		# Parse commands
		if($args =~ /^!version/) { &version; }
		elsif($args =~ /^!uptime /) { &uptime; }
		elsif($args =~ /^!seen /) { &seen; }
		elsif($args =~ /^!help/) { &help; }
		elsif($args =~ /^!loaded/) { &loaded; }
		elsif($args =~ /^!available/) { &available; }
		elsif($args =~ /^!load /) { if($public_modules == 1) { &loadmodule; } }
		elsif($args =~ /^!unload /) { if($public_modules == 1) { &unloadmodule; } }
		elsif($args =~ /^!reload /) { if($public_modules == 1) { &unloadmodule; &loadmodule; } }
		elsif($args =~ /^!\w/) { &pubcmd;
		} else {
			# Hook for modules that want all messages
			foreach $module (@modules) {
				if( $module->can('mesg') ) {
					$module->mesg($from, $uname, $host, $from_chan, $modchan, $botnick, $args);
				}
			}
		}
 
		# Operator commands
		foreach $oper (@opers) { 
			if ($oper eq $host) { 
				if($args =~ /^!load /) { if($public_modules == 0) { &loadmodule; } }
				elsif($args =~ /^!unload /) { if($public_modules == 0) { &unloadmodule } }
				elsif($args =~ /^!reload /) { if($public_modules == 0) { &unloadmodule; &loadmodule; } }
				elsif($args =~ /^!raw /) { &raw; } 
				elsif($args =~ /^!msg /) { &mesg; } 
				elsif($args =~ /^!quit/) { &botquit; } 
				elsif($args =~ /^!join /) { &joinchan; }
				elsif($args =~ /^!part /) { &partchan; }
 				elsif($args =~ /^!nick /) { &nick; }
 				elsif($args =~ /^!op/) { &oper; }
 				elsif($args =~ /^!deop/) { &deoper; }
 				elsif($args =~ /^!hop/) { &halfoper; }	
				elsif($args =~ /^!dehop/) { &dehalfoper; }
				elsif($args =~ /^!voice/) { &voice; }
 				elsif($args =~ /^!devoice/) { &devoice; } 
				elsif($args =~ /^!kick /) { &kick; }
 				elsif($args =~ /^!ban /) { &ban; } 
				elsif($args =~ /^!unban /) { &unban; }
				elsif($args =~ /^!topic /) { &topic; }
				elsif($args =~ /^!mode /) { &mode; }
				elsif($args =~ /^!loadlist/) { &loadlists; }
 				elsif($args =~ /^!modchan/) { &modchan; } 
				elsif($args =~ /^!bot/) { &botswitch; }
 				elsif($args =~ /^!pubmods/) { &pubmods; }
 				elsif($args =~ /^!admin/) { &admin; } 
				elsif($args =~ /^!all /) { &all; }
 				elsif($args =~ /^!none /) { &none; }
 				elsif($args =~ /^!add /) { &add; }
 				elsif($args =~ /^!\w/) { &admincmd; }
			}
		}
	}
}
 
##### Meta subroutine for initial join ######
sub login {
	debug("Entered initial join loop.\n");
 
	# Attempt nickserv login
	&nickserv;
 
	# Join all listed channels
	&joinlist;
 
	# We've done login and join, no need to do it again next time
	$login = 1;
}
 
##### NickServ AUTH ######
sub nickserv{
	if ($nsp) {
		logts("Identifying nick ..... ");
		msg("NickServ", "identify $nsp");
		logts("[OK]\n");
	}
}
 
##### Join listed channels #####
sub joinlist {
	logts("Joining channel(s) ... ");
	foreach $chan (@channels) {
		snd("JOIN $chan");
	}
	logts("[OK]\n");
 
	# Also call autoload modules now
	&autoload;
}
 
##### Attempt to autoload specified modules #####
sub autoload {
	foreach $loadme (@autoload) {
		&autoloadmodule($loadme);
	}
}
 
##### !version #####
sub version {
	debug("Received \"version\"-command.\n");
	ntc("$from", "Running version: $version");
	my $uptime = &diffString(time - $startup);
	ntc("$from", "Uptime: $uptime");
	logts("Sending version to $from.\n");
}
 
##### !seen #####
sub seen {
	if($botstatus == 1) {
		debug("Received \"seen\"-command.\n");
		if(!substr($args, 6)) {
			ntc("$from", "No user was specified!");
		} else {
			$usr = substr($args, 6);
			$usr =~ s/\s+$//;
 
			if( lc $usr eq lc $botnick ) {
				msg("$from_chan", "$wisecrack_seen_botnick");
			} elsif( lc $usr eq lc $from ) {
				msg("$from_chan", "$wisecrack_seen_self");
			} else {
				# Check if we have a log for this user
				my $seen = 0;
				for my $key (keys(%seenlog)) {
					if ($key eq lc $usr) { $seen = 1; }
	   			}
 
				if ($seen == 1) {
					my $diff = &diffString(time - $seentime{lc $usr});
					msg("$from_chan", "$usr was last seen $diff ago saying: ");
					msg("$from_chan", "$seenlog{lc $usr}");
					logts("Sending seen info for $usr\n");
					debug("$seenlog{lc $usr}");
				} else {
					my $uptime = &diffString(time - $startup);
					ntc("$from", "No log for $usr");
					ntc("$from", "Log goes back $uptime");
					logts("No log entry for $usr found\n");
				}
			}
		}
	}
}
 
##### Translate difference in seconds to human readable string #####
sub diffString {
	($s,$m,$h,$d,$mo) = gmtime( $_[0] );
 
	if( $mo > 0 ) {
		$returnstring = "$mo months, $d days, $h hours, $m minutes and $s seconds";
	} else {
		$d--;
		if( $d > 0 ) {
			$returnstring = "$d days, $h hours, $m minutes and $s seconds";
		} else {
			if( $h > 0 ) {
				$returnstring = "$h hours, $m minutes and $s seconds";			
			} else {
				if( $m > 0 ) {
					$returnstring = "$m minutes and $s seconds";
				} else {
					$returnstring = "$s seconds";
				}
			}
		}
	} 
}
 
##### !help #####
sub help {
	if (substr($args, 6) eq "yes") { 
		debug("Received \"help\"-command.\n");
 
		ntc("$from", "Help for $botnick version $version.");
		ntc("$from", " ");
		ntc("$from", "Public commands:");
		ntc("$from", "!help Get this help.");
		ntc("$from", "!version Get version number.");
		ntc("$from", "!seen [user] Get the last thing a user said.");
		ntc("$from", " ");
		ntc("$from", "Oper only commands:");
		ntc("$from", "!quit [message] Stop bot.");
		ntc("$from", "!join [channel] Join channel.");
		ntc("$from", "!part [channel] Part channel.");
		ntc("$from", "!topic [topic] New topic.");
		ntc("$from", "!mode [user/chan] +/-mode");
		ntc("$from", "!nick [botnick] Change the bots nickname.");
		ntc("$from", "!loadlist Load auto-lists.");
		ntc("$from", "!modchan [channel] Set active channel. Returms current active channel when none is given.");
		ntc("$from", "!bot [on|off] Switch bot on or off.");
		ntc("$from", "!all [op|hop|voice] Give status to every user to enter the channel.");
		ntc("$from", "!none [op|hop|voice] Stop the !all command.");
		ntc("$from", "!add [op|hop|voice|kick] [hostmask] Add hostmask to auto-list.");
		ntc("$from", "![op|deop|hop|dehop|voice|devoice|kick] [nick] Preform direct action.");
		ntc("$from", "![ban|unban] [hostmask] Ban hosts from the active channel.");
		ntc("$from", "!admin [add|del] [hostmask] Control admin access to the bot. (No args returns current list)");
		ntc("$from", "!raw [data] Send raw commands to the IRC server.");
		ntc("$from", " ");
		ntc("$from", "Module commands:");
		ntc("$from", "![load|unload|reload] [module] Load / unload / reload a module.");
		ntc("$from", "!loaded List currently loaded modules.");
		ntc("$from", "!available List all available modules.");
		ntc("$from", "!pubmods [on|off] Switch public usage of modules on or off.");
		ntc("$from", "!module.function Call a loaded modules functions.");
 
		logts("Sent help to $from.\n");
	} else {
		ntc("$from", "This command sends about 30 lines of notices.");
		ntc("$from", "Use \"!help yes\" if you are sure you want to do this. Or visit http://wiki.insomnia247.nl/wiki/Nanobot");
	}
}
 
##### !raw #####
sub raw {
	debug("Received \"raw\"-command.\n");
	my ($cmd,@data) = split(/ /, $args);
	snd("@data");
	logts("Raw command was used by $from.\n");
}
 
##### !msg #####
sub mesg {
	debug("Received \"msg\"-command.\n");
	my ($cmd, $to, @data) = split(/ /, $args);
	snd("PRIVMSG $to :@data");
	logts("Msg command was used by $from.\n");
}
 
##### !join #####
sub joinchan {
	debug("Received \"join\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "No channel was specified!");
	} else {
		$chan = substr($args, 5);
		snd("JOIN $chan");
		logts("Joining $chan...\n");
	}
}
 
##### !part #####
sub partchan {
	debug("Received \"part\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "No channel was specified!");
	} else {
		$chan = substr($args, 5);
		snd("PART $chan");
		logts("Parting $chan...\n");
	}
}
 
##### !nick #####
sub nick {
	debug("Received \"nick\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "No new nick was specified!");
	} else {
		$botnick = substr($args, 5);
		snd("NICK $botnick");
		logts("Changed bot nick to $botnick...\n");
	}
}
 
##### !modchan #####
sub modchan {
	debug("Received \"modchan\"-command.\n");
		if(!substr($args, 9)) {
		debug("command was blank.\n");
		ntc("$from", "Current active channel is: $modchan");
	} else {
		$modchan = substr($args, 9);
		ntc("$from", "Setting active channel to $modchan...");
		logts("Setting active channel to $modchan...\n");
	}
}
 
##### !bot #####
sub botswitch {
	debug("Received \"bot\"-command.\n");
	if (!substr($args, 5)) {
		if($botstatus) {
			ntc("$from", "Bot is enabled.");
		} else {
			ntc("$from", "Bot is disabled.");
		}
	} else {
		$mode = substr($args, 5);
		if ($mode =~ /on/) {
			$botstatus = 1;
			msg("$modchan", "Bot enabled.");
			logts("Bot enabled by $from...\n");
		} else {
			if ($mode =~ /off/) {
				$botstatus = 0;
				msg("$modchan", "Bot disabled.");
				logts("Bot disabled by $from...\n");
			}
		}
	}
}
 
##### !loaded #####
sub loaded {
	snd("NOTICE $from :Loaded modules: @modules");
}
 
##### !available ######
sub available {
	@available = <$moddir/*.pm>;
	my $i = 0;
	foreach $avail (@available) {
		my ($dir, $filename) = split(/\//, $avail);
		my ($modname, $ext) = split(/\./, $filename);
		$available[$i] = $modname;
		$i++;
	}
	snd("NOTICE $from :Available modules: @available");
}
 
##### !load #####
sub loadmodule {
	debug("Received \"load\"-command.\n");
	my ($cmd,$module,@data) = split(/ /, $args);
 
	$i = 0;
	$found = 0;
	while($i <= @modules){
		if($modules[$i] eq $module){
			$found = 1;
		}
		$i++;
	}
 
	if( (-e "$moddir/$module.pm") && ( $found == 0 ) ) {
		my $retval = system( "perl -c $moddir/$module.pm" );
		if( $retval == 0 ) {
			load "$moddir/$module.pm";
			push(@modules,$module);
			ntc("$from", "Inserted: $module");
			logts("Module $module loaded by $from.\n");
		} else {
			ntc("$from", "Could not load module: $module (Not valid Perl)");
		}
	} else {
		if( $found == 1	) {
			ntc("$from", "Could not load module: $module (Module is already loaded)");
		} else {
			ntc("$from", "Could not load module: $module (Cannot find module)");
		}					
	logts("Module $module loaded by $from FAILED.\n");
	}
}
 
##### autoload modules #####
sub autoloadmodule {
	debug("Attempting to load module $_[0].\n");
	logts("Loading module ....... ");
	$module = $_[0];
 
	$i = 0;
	$found = 0;
	while($i <= @modules){
		if($modules[$i] eq $module){
			$found = 1;
		}
		$i++;
	}
 
	if( (-e "$moddir/$module.pm") && ( $found == 0 ) ) {
		my $retval = system( "perl -c $moddir/$module.pm 2> $moddir/temp" );
		unlink "$moddir/temp";
		if( $retval == 0 ) {
			load "$moddir/$module.pm";
			push(@modules,$module);
			logts("[$module OK]\n");
		} else {
			logts("[$module FAILED] (not valid perl)\n");
		}
	} else {
		if( $found == 1	) {
			logts("[$module FAILED] (already loaded)\n");
		} else {
			logts("[$module FAILED] (not found)\n");
		}					
	}
}
 
##### !unload #####
sub unloadmodule {
	debug("Received \"unload\"-command.\n");
	my ($cmd,$module,@data) = split(/ /, $args);
 
	$i = 0;
	$found = 0;
	while($i <= @modules){
		if($modules[$i] eq $module){
			$found = 1;
			delete $INC{"$moddir/$module.pm"};
			delete $modules[$i];
			splice(@modules, $i ,1);
		}
	$i++;
	}
 
	if( $found == 1 ) {
		ntc("$from", "Unloaded module: $module");
		logts("Module $module unloaded by $from.\n");
	} else {
		ntc("$from", "Could not unload module: $module (Module doesn't appear to be loaded)");
		logts("Module $module unload by $from FAILED.\n");
	}
}
 
##### !pubmods #####
sub pubmods {
	debug("Received \"pubmods\"-command.\n");
	if (!substr($args, 9)) {
		if($botstatus) {
			ntc("$from", "Public modules are enabled.");
		} else {
			ntc("$from", "Public modules are disabled.");
		}
	} else {
		$mode = substr($args, 5);
		if ($mode =~ /on/) {
			$public_modules = 1;
			msg("$modchan", "Public modules enabled.");
			logts("Public modules enabled by $from...\n");
		} else {
			if ($mode =~ /off/) {
				$public_modules = 0;
				msg("$modchan", "Pulbic modules disabled.");
				logts("Public modules disabled by $from...\n");
			}
		}
	}
}
 
##### Public module commands #####
sub pubcmd {
	my($command, @data) = split(/ /,$args);
	$command = substr($command, 1);
	my($mod, $cmd) = split(/\./,$command);
 
	if(($mod =~ /^.+/) && ($cmd =~ /^.+/)) {
		$i = 0;
		while($i <= @modules){
			if(($modules[$i] eq $mod) && ( $mod->can($cmd) )){
				if( $public_modules == 1) {
					$mod->$cmd($from, $uname, $host, $from_chan, $modchan, $botnick, @data);
				} elsif( $mod->can('public') ){
					@functions = $mod->public();
					foreach $function (@functions) {
						if( ($function eq $cmd) ) {
							$mod->$cmd($from, $uname, $host, $from_chan, $modchan, $botnick, @data);
						}
					}
				}							
			}
			$i++;
		}
	} elsif((($mod =~ /^.+/) && ($cmd !~ /^.+/)) && ( $mod->can('help') )) {
		$mod->help($from, $uname, $host, $from_chan, $modchan, $botnick, @data);
	}
}
 
##### Admin module commands #####
sub admincmd {
	my($command, @data) = split(/ /,$args);
	$command = substr($command, 1);
	my($mod, $cmd) = split(/\./,$command);
 
	if(($mod =~ /^.+/) && ($cmd =~ /^.+/)) {
		$i = 0;
		while($i <= @modules){
			if(($modules[$i] eq $mod) && ( $mod->can($cmd) )){
				if( $mod->can('public') ) {
					@functions = $mod->public();
					my $notPublic = 1;
					foreach $function (@functions) {
						if( ($function eq $cmd) ) {
							$notPublic = 0;
						}
					}
					if( $notPublic == 1 && $public_modules == 0 ) {
						$mod->$cmd($from, $uname, $host, $from_chan, $modchan, $botnick, @data);
					}
				} elsif ( $public_modules == 0) {
					$mod->$cmd($from, $uname, $host, $from_chan, $modchan, $botnick, @data);
				}							
			}
			$i++;
		}
	}	
}
 
##### !loadlist #####
sub loadlists {
	debug("Received \"loadlist\"-command.\n");
	debug("Using data directory \"$datadir\".\n");
 
	open AOPLIST, "<$datadir/aop";
	@aop = <AOPLIST>;
	close(AOPLIST);
	debug("AOP list loaded.\n");
 
	open AHOPLIST, "<$datadir/ahop";
	@ahop = <AHOPLIST>;
	close(AHOPLIST);
	debug("AHOP list loaded.\n");
 
	open AVLIST, "<$datadir/av";
	@av = <AVLIST>;
	close(AVLIST);
	debug("AVOICE list loaded.\n");
 
	open AKLIST, "<$datadir/ak";
	@ak = <AKLIST>;
	close(AKLIST);
	debug("AKICK list loaded.\n");
 
	msg("$modchan", "Lists loaded.");
	logts("Loaded the lists...\n");
}
 
##### !add #####
sub add {
	debug("Received \"add\"-command ");
	my ($msg,$type,$toadd) = split(/ /, $args);
	debug("of type $type.\n");
 
	if($type =~ /^op/) {
		open AOPLIST, ">>$datadir/aop";
		print AOPLIST "$toadd\n";
		close(AOPLIST);
		msg("$modchan", "$toadd added to auto-op list.");
	} elsif($type =~ /^hop/) {
		open AHOPLIST, ">>$datadir/ahop";
		print AHOPLIST "$toadd\n";
		close(AHOPLIST);
		msg("$modchan", "$toadd added auto-half-op list..");
	} elsif($type =~ /^voice/) {
		open AVLIST, ">>$datadir/av";
		print AVLIST "$toadd\n";
		close(AVLIST);
		ntc("$modchan", "$toadd added auto-voice list.");
	} elsif($type =~ /^kick/) {
		open AKLIST, ">>$datadir/ak";
		print AKLIST "$toadd\n";
		close(AKLIST);
		ntc("$modchan", "$toadd added auto-kick list.");
	}
}
 
##### !op #####
sub oper {
	debug("Received \"op\"-command.\n");
	if(!substr($args, 4)) {
		snd("MODE $from_chan +o $from");
		logts("Opered $from...\n");
	} else {
		$user = substr($args, 4);
		snd("MODE $from_chan +o $user");
		logts("Opered $user...\n");
	}
}
 
##### !deop #####
sub deoper {
	debug("Received \"deop\"-command.\n");
	if(!substr($args, 6)) {
		snd("MODE $from_chan -o $from");
		logts("Deopered $from...\n");
	} else {
		$user = substr($args, 6);
		snd("MODE $from_chan -o $user");
		logts("Deopered $user...\n");
	}
}
 
##### !hop #####
sub halfoper{
	debug("Received \"hop\"-command.\n");
	if(!substr($args, 5)) {
		snd("MODE $from_chan +h $from");
		logts("Half-opered $from...\n");
	} else {
		$user = substr($args, 5);
		snd("MODE $from_chan +h $user");
		logts("Half-opered $user...\n");
	}
}
 
##### !dehop #####
sub dehalfoper {
	debug("Received \"dehop\"-command.\n");
	if(!substr($args, 7)) {
		snd("MODE $from_chan -h $from");
		logts("Dehalf-opered $from...\n");
	} else {
		$user = substr($args, 7);
		snd("MODE $from_chan -h $user");
		logts("Dehalf-opered $user...\n");
	}
}
 
##### !voice #####
sub voice {
	debug("Received \"voice\"-command.\n");
	if(!substr($args, 6)) {
		snd("MODE $from_chan +v $from");
		logts("Voiced $from...\n");
	} else {
		$user = substr($args, 6);
		snd("MODE $from_chan +v $user");
		logts("Voiced $user...\n");
	}
}
 
##### !devoice #####
sub devoice {
	debug("Received \"devoice\"-command.\n");
	if(!substr($args, 8)) {
		snd("MODE $from_chan -v $from");
		logts("Devoiced $from...\n");
	} else {
		$user = substr($args, 8);
		snd("MODE $from_chan -v $user");
		logts("Devoiced $user...\n");
	}
}
 
##### !all #####
sub all {
	debug("Received \"all\"-command ");
	my ($msg,$type) = split(/ /, $args);
	debug("of type $type.\n");
 
	if($type =~ /^op/) {
		msg("$modchan", "Global Oper for $modchan users enabled.");
		$op_all = 1;
		logts("Enabled Op all on $modchan by $from.\n");
	} elsif($type =~ /^hop/) {
		msg("$modchan", "Global Half-op for $modchan users enabled.");
		$hop_all = 1;
		logts("Enabled Half-Op all on $modchan by $from.\n");
	} elsif($type =~ /^voice/) {
		msg("$modchan", "Global Voice for $modchan users enabled.");
		$voice_all = 1;
		logts("Enabled Voice all on $modchan by $from.\n");
	}
}
 
##### !none #####
sub none {
	debug("Received \"none\"-command ");
	my ($msg,$type) = split(/ /, $args);
	debug("of type $type.\n");
 
	if($type =~ /^op/) {
		msg("$modchan", "Global Oper for $modchan users disabled.");
		$op_all = 0;
		logts("Enabled Op all on $modchan by $from.\n");
	} elsif($type =~ /^hop/) {
		msg("$modchan", "Global Half-op for $modchan users disabled.");
		$hop_all = 0;
		logts("Enabled Half-Op all on $modchan by $from.\n");
	}  elsif($type =~ /^voice/) {
		msg("$modchan", "Global Voice for $modchan users disabled.");
		$voice_all = 0;
		logts("Enabled Voice all on $modchan by $from.\n");
	}
}
 
##### !kick #####
sub kick {
	debug("Received \"kick\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "Commmand requires username to kick.");
	} else {
		$user = substr($args, 5);
   	 	snd("KICK $from_chan $user (Requested.)");
		logts("Kicked $user...\n");
	}
}
 
##### !ban #####
sub ban {
	debug("Received \"ban\"-command.\n");
	if(!substr($args, 5)) {
		ntc("$from", "Command requires something to ban.");
	} else {
		$hostmask = substr($args, 5);
		snd("MODE $from_chan +b $hostmask");
		logts("Banned $hostmask...\n");
	}
}
 
##### !unban #####
sub unban {
	debug("Received \"unban\"-command.\n");
	if(!substr($args, 7)) {
		ntc("$from", "Command requires hostname to unban.");
	} else {
		$hostmask = substr($args, 7);
		snd("MODE $from_chan -b $hostmask");
		logts("Unanned $hostmask...\n");
	}
}
 
##### !topic #####
sub topic {
	debug("Received \"topic\"-command.\n");
	if(!substr($args, 7)) {
		ntc("$from", "No new topic specified.");
	} else {
		$new_topic = substr($args, 7);
		snd("TOPIC $from_chan :$new_topic");
		logts("Set topic for $from_chan set to $new_topic\n");
	}
}
 
##### !mode #####
sub mode {
	debug("Received \"mode\"-command.\n");
	if(!substr($args, 6)) {
		ntc("$from", "No arguments specified.");
	} else {
		$modes = substr($args, 6);
		snd("MODE $modes");
		logts("Set modes $modes\n");
	}
}
 
##### !admin #####
sub admin {
	debug("Received \"admin\"-command.\n");
	my ($msg,$type,$hostm) = split(/ /, $args);
 
	if ($type =~ /add/) {
		push(@opers,$hostm);
		ntc("$from", "Added $hostm to temp admin list.");
		logts("Added temp admin $hostm by $from\n");
		debug("Oper list: ");
 
		foreach $oper (@opers) { 
			debug("$oper ");
		}
 
		debug("\n");
	} elsif ($type =~ /del/) {
		$i = 0;
		while($i <= @opers){
			if($opers[$i] eq $hostm){
				while($i < @opers){
					$opers[$i] = $opers[$i+1];
					$i++;
				}
			}
			$i++;
		}
		ntc("$from", "Removed $hostm from temp admin list.");
		logts("Removed temp admin $hostm by $from\n");
		debug("Oper list: ");
 
		foreach $oper (@opers) { 
			debug("$oper ");
		}
	debug("\n");
 
	} else {
		snd("NOTICE $from :Current admins: @opers");
	}
}
 
##### !quit #####
sub botquit {
	debug("Received \"quit\"-command.\n");
	logts("Quit command was issued by $from.\n");
 
	my ($cmd,@msg) = split(/ /, $args);
 
	if($msg[0] eq "") {
		snd("QUIT $botnick was instructed to quit.");
	} else {
		snd("QUIT @msg");
	}
 
	close($sock);
	&shutd;
}
 
##### Process exit subroutine #####
sub shutd {
	logts("Shutting down.\n");
	debug("Final line of code before exit call.\n");
	exit(0);
}