#!/usr/bin/perl

#
# Currently supported by: Quentin Smith   (quentins@comclub.org)
# Main Code/Ideas:     Andrew J. Tosh     (innercircle13@yahoo.com)
#                      Quentin Smith      (quentins@comclub.org)
# Math/Support:        Ramsey Hazbun      (fozzytbear@yahoo.com)
#                      LionMan (The real Genius)
# Mods for HeX:        Andrew Medico      (amedico@calug.net)
# CGI:                 Quentin Smith      (quentins@comclub.org)
# Alice AI:            The ALICE Project  (www.alicebot.org)
#
# Yeah, Yeah, its GPL V2
#
# Special thanks to the Korean Broadcast Service for providing background
# vision without annoying English words to distract me while coding :)
# -AJT

use Carp;
use EventServer;
use Net::AIM;
use Chatbot::Eliza;
use Data::Dumper;
use BSD::Resource;
use vars qw/$aim $conn %skip/;
require "NSH.pm";
if ($ARGV[0] eq "") {
	print "Usage: akira [SCREENNAME] [PASSWORD]\n\nBugs/suggestions to akira\@comclub.org\n";
	exit(1);
}
#chdir "akiradata";
$conversations=0;
$messages=0;
$|=1;
$startdate=localtime();
$aim = new Net::AIM;
$aim->debug(1);

$conn = $aim->newconn (
		Screenname => "$ARGV[0]",
		Password =>"$ARGV[1]",
		TocServer => "toc-m08.blue.aol.com",
#		TocServer => "login.oscar.aol.com",
	) or die "FATAL connection to server failed\n";
$conn = $aim->getconn;
#print $conn;
#print $main::conn;

sub aimerror {
	print STDERR "Something broke:", Dumper(@_);
	print STDERR "";
	}
sub aimim {
	$messages+=1;
	($self, $event) = @_;
	$nick = $event->from;
	@string=@{$event->args};
	#print @string;
        $message=$string[2];
	$normalized_nick = $nick;$normalized_nick =~ s/ //g;$normalized_nick =~ tr/A-Z/a-z/;
	$string[2]=~s/\<[^>]*\>//g;
	Log::User::init($normalized_nick, $nick);
        Log::User::msg($normalized_nick, 'I', $message);
	$ENV{UNAME} = $nick;
	$ENV{UHOME} = "/home/akira/uhomes/$normalized_nick/";

	if(!(-e $ENV{UHOME} and -d $ENV{UHOME})) {
		mkdir($ENV{UHOME}, 0_700);
		NSH::processInput("init");
		#my $greeting = "Hello, I see we haven't met before, my homepage is http://akira.comclub.org/, my owners SN is Power iMac, say hi to him if he's on, the bot will start talking now. Oh, if you want to know what commands are available, you can say !help to me. The most commonly used command is !bot. Do !help bot for more.";
		my $greeting = "Hello, I see we haven't met before, my homepage is http://akira.comclub.org/, my owner's SN is iamtheqdawg, say hi to him if he's on, the bot will start talking now. Oh, if you want to know what commands are available, you can say !help to me.";
		$self->send_im($nick, $greeting) unless exists $skip{$nick};
		Log::User::msg($normalized_nick, 'O', $greeting) unless exists $skip{$nick};
	}
	if ($string[2] =~ /^!(.*)/) {
		#It's a command.
		#I'll link this up to NSH soon.
		#For now, just return
		my $command = $1;
		#this makes sure it doesn't syntax error
		$command =~ s/\\/\\\\/g;
		$command =~ s/\'/\\\'/g;
		my $results = grab_output("NSH::processInput(\'$command\')");
		chomp $results->[0];
		$results->[0] =~ s/\n/<BR>/g;
		$self->send_im($nick, $results->[0]) if $results;
	        Log::User::msg($normalized_nick, 'O', $results->[0]);
		return(1);
	}
	
	unless ((NSH::Utilities::getPref("bot.style") ne "eliza") or $elizas{$nick}->[1]) {
		$elizas{$nick}->[1]=new Chatbot::Eliza;
		$elizas{$nick}->[1]->name("Akira");
	}
	if ($elizas{$nick}->[2]) {
		
		
		cancel_registration ($elizas{$nick}->[2]);

	}
	
	my $style = NSH::Utilities::getPref("bot.style");
	my $reply;
	if ($style eq "eliza") {
        $reply=$elizas{$nick}->[1]->transform($string[2]);
	} elsif ($style eq "alice") {
	#chdir "/home/akira/";
	#$reply=`./comalice -o -u "$nick" "$string[2]"`;
	my $pid = open(ALICE, "-|");
	unless($pid) {
	    #safety check
	    $nick =~ s/.*\/([^\/]+?)/$1/;
	    #lockdown the resources
	    setrlimit(RLIMIT_CPU, 10, 10) || die "Couldn't lock down CPU usage.";
	    #setrlimit(RLIMIT_DATA, 10_000, 10_000) || die "Couldn't lock down memory usage.";
	    exec ('/home/akira/comalice', '-o', '-u', '/home/akira/alicedata/'.$nick, $string[2]);
	}
	$reply = join('', <ALICE>);
	close(ALICE);
	$reply =~ s/(<BR>|<br>|\n)$//gs;
	} else {
	$reply="EGADS! You need to change your !bot setting!";
	}
        	
	# AJT 08.07.2000 (Mods for CGI Part 1)
	#message=~s/\t//g;
	#reply=~s/\t//g;
	#nless (-e "akiradata/conversations/$normalized_nick.dat") {
	#	$conversations+=1;
	#	open(USERLOG, ">> akiradata/conversations/$normalized_nick.dat");
	#	$date=localtime();
	#	print USERLOG "$date\n";
	#	close USERLOG;
	#
	#open(USERLOG, ">> akiradata/conversations/$normalized_nick.dat");
	#og::User::msg($normalized_nick, "$reply\t$message\n");
        Log::User::msg($normalized_nick, 'O', $reply);
	#close (USERLOG);
	# End (Mods for CGI Part 1)
	print "Nick: $nick\nMessage: $message\nReply: $reply\n\n";
	while (length $reply > 1500) {
	    #sleep 1;
	    $self->send_im($nick, substr($reply,0,1500));
	    $reply = substr($reply, 1500);
	    print $reply;
	    $aim->do_one_loop();
	    #select(undef, undef, undef, 1.5);
	    execute_in_array_context_with_timeout(1.5, 1, 0, sub {while (1) {}}, []);
	}
	$self->send_im($nick, $reply);#)}, $self, $nick, $reply);
        $elizas{$nick}->[2]=register_timed_client ($aim, 630, \&clear_old_eliza, $nick) if (NSH::Utilities::getPref("bot.style") eq "eliza");
	open(STATS, ">akiradata/stats.dat");
	print STATS "$conversations\n$messages\n$startdate";
	close(STATS);
    }
	
sub sendmsg {
    ($self, $nick, $reply) = @_;
    $self->send_im($nick, $reply);
}
my $sentinfo = 0;
sub aimnick {
	($self, $event)=@_;
	$self->set_info("I am an AIM bot by Quentin Smith, see
http://akira.comclub.org/ for info or IM Power iMac") unless $sentinfo;
	$sentinfo = 1;
	$self->set_idle(0);
	$self->send_im("Power iMac", "Howdy, I just signed on.");
	$self->add_buddy("Masters", "Power iMac");
        }
sub clear_old_eliza {
	my $nickname=@_[3];
	$elizas{$nickname}->[1]=null;
	$elizas{$nickname}->[2]=null;
}
sub aimeviled {
	($self, $event) = @_;
	($level, $user) = @{$event->args};
	my $result = $self->evil($user);
	$self->send_im("Power iMac", "$user warned up, our level is $level. Warned back: $result.");
	my $normalized_nick = $user;$normalized_nick =~ s/ //g;$normalized_nick =~ tr/A-Z/a-z/;
	Log::User::msg($normalized_nick, 'W', "Warned up to $level. Warned back to $result.\n");
	Log::System::msg("$user warned up, our level is $level. Warned back: $result.\n");
	}
sub aimdisconnect {
	($self, $event) = @_;
	print STDERR "We got disconnected, retrying in 5";
	sleep 5;
	$self->connect();
	}
$conn->set_handler('error', \&aimerror);
$conn->set_handler('im_in', \&aimim);
$conn->set_handler('nick', \&aimnick);
$conn->set_handler('eviled', \&aimeviled);
$conn->set_handler('disconnect', \&aimdisconnect);
print register_interval_client($aim,1,\&process_aim_events);
#process_aim_events(); # Just to get things started.

print start_server();

sub process_aim_events {
my $aim = shift;
$aim->do_one_loop;
}

# grab_output()
# 
# Eval some code and return what was printed to stdout and stderr.
# 
# Parameters: string of code to eval
# 
# Returns: listref of [ stdout text, stderr text ]
# 
sub grab_output($) {
    die 'usage: grab_stderr(string to eval)' if @_ != 1;
    my $code = shift;
    require POSIX;
    my $tmp_o = POSIX::tmpnam(); my $tmp_e = POSIX::tmpnam();
    local *OLDOUT, *OLDERR;
    
    # Try to get a message to the outside world if we die
    local $SIG{__DIE__} = sub { print $_[0]; die $_[0] };

    open(OLDOUT, ">&STDOUT") or die "can't dup stdout: $!";
    open(OLDERR, ">&STDERR") or die "can't dup stderr: $!";
    open(STDOUT, ">$tmp_o")  or die "can't open stdout to $tmp_o: $!";
    open(STDERR, ">$tmp_e")  or die "can't open stderr to $tmp_e: $!";
    eval $code;
    # Doubtful whether most of these messages will ever be seen!
    close(STDOUT)            or die "cannot close stdout opened to $tmp_o: $!";
    close(STDERR)            or die "cannot close stderr opened to $tmp_e: $!";
    open(STDOUT, ">&OLDOUT") or die "can't dup stdout back again: $!";
    open(STDERR, ">&OLDERR") or die "can't dup stderr back again: $!";

    die $@ if $@;

    local $/ = undef;
    open (TMP_O, $tmp_o) or die "cannot open $tmp_o: $!";
    open (TMP_E, $tmp_e) or die "cannot open $tmp_e: $!";
    my $o = <TMP_O>; my $e = <TMP_E>;
    close TMP_O   or die "cannot close filehandle opened to $tmp_o: $!";
    close TMP_E   or die "cannot close filehandle opened to $tmp_e: $!";
    unlink $tmp_o or die "cannot unlink $tmp_o: $!";
    unlink $tmp_e or die "cannot unlink $tmp_e: $!";

    return [ $o, $e ];
}

package Log::User;

sub init($$) {
# Initializes the per-user log.
# Preconditions: $normnick is the normalized nick, and $nick is the display name.
# Postconditions: The appropriate logfile has been created, if necessary.
    my ($normnick, $nick) = @_;
    if (!$normnick) { $normnick = "anonymous"; }
    if (!$nick) { $nick = "Anonymous"; }
    if (-e "akiradata/conversations/$normnick.aklog2") {
	my @stat = stat("akiradata/conversations/$normnick.aklog2");
	if (time()-$stat[9] > (60*15)) {
	    warn "Moving file $normnick.aklog2.";
	    if (!-d "akiradata/conversations/archives/$normnick/")
	    {
		warn "Creating archive for user $normnick";
		mkdir "akiradata/conversations/archives/$normnick/", 0755;
		open ARCHIVEINFO, "> akiradata/conversations/archives/$normnick/buddy.info";
		print ARCHIVEINFO $nick, "\n";
		close ARCHIVEINFO;
	    }
	    my @info = localtime($stat[9]);
	    my $filename = sprintf("%04d-%02d-%02d-%02d%02d%02d.aklog2",
				   $info[5]+1900, $info[4]+1, $info[3], $info[2], $info[1], $info[0]);
	    warn "Moving to $filename";
	    rename "akiradata/conversations/$normnick.aklog2", "akiradata/conversations/archives/$normnick/$filename";
	    open USERLOG, ">> akiradata/conversations/archives/$normnick/$filename";
	    print USERLOG time(), "\tEND", "\n";
	    close USERLOG;
	}
    }
    if (!-e "akiradata/conversations/$normnick.aklog2") {
	open(USERLOG, "> akiradata/conversations/$normnick.aklog2");
	print USERLOG "$nick\n";
	print USERLOG time(), "\tSTART", "\n";
	close(USERLOG);
    }
}

sub msg($@) {
    my ($user, @msg) = @_;
    foreach my $msg (@msg) {
	$msg =~ s/\n/<br>/g;
    }
    if (!-e "akiradata/conversations/$user.aklog2") {
	#I don't want to do this, but it's the only choice.
	init($user, $user);
    }
    if (!$user) {
	$user = "anonymous";
    }
    open(USERLOG, ">> akiradata/conversations/$user.aklog2");
    print USERLOG join("\t", time(), @msg), "\n";
    close (USERLOG);
}

package Log::System;

sub msg(@) {
    my (@msg) = @_;
    foreach my $msg (@msg) {
	$msg =~ s/\n/<BR>/g;
    }
    
    open(SYSLOG, ">> akiradata/systemlog.dat");
    print SYSLOG time(), "\t@msg\n";
    close (SYSLOG);
}