#!/usr/bin/perl -w

#
# this little bot will watch some chans and speak in only one
# at somewhat-random intervals by taking what it's learned
# from everything said in all the chans so far
#
# this is a very ugly and basic and badly coded version
# please don't judge my code on this :)
# (more code at http://code.lucidx.com)
#
# -Samy Kamkar + code from mark_v_shaney_bigram.pl by Rosie Jones
#                http://www-2.cs.cmu.edu/~rosie/yapc/src/

use Socket;
use Storable;
use strict;

my $server	= "irc.openprojects.net";	# server to connect to
my $nnick	= "radagast";			# nickname
my $name	= "radagast";			# real name

my $mainchan	= "#killallhumans";			# chan to talk in
my @chans	= ( "#wireless", "#slackware", "#perl" );	# other chans to JUST watch
my $password	= "morrisminor";		# you can /msg bot password (join|part|quit|mode) ...
my $ratio	= 0;				# 1:$ratio - normal message to /me ratio (random)
my $fewest	= 2;				# wait for $fewest messages to talk (fewest)
my $rrand	= 2;				# also wait for rand($rrand) messages to talk (rand)
my $datafile	= "$nnick.dat";			# data file.
my $cp_freq	= 100;				# how often to save to the data file.

#### subs

sub checkpoint {
    my $seen = shift;
    my $pid  = fork;
    if (defined($pid) and not $pid) {
	store $seen => $datafile;
	exit;
    }
}

sub process {
    my ($seen, $data) = @_;
    $data =~ s/\b\W+\B//g;
    $data = lc($data);
    $data =~ s/://;
    my $lastword = "BEGIN_FILE";
    my @words = split(/\s+/, $data);
    for my $word (@words, "END_FILE") {
     $seen->{$lastword}{$word}++;
     $seen->{$lastword}{WORD_COUNT}++;
     $lastword = $word;
    }
    return @words;
}

sub generate_next_word {
 my ( $seen, $lastword ) = @_;
 return "END_FILE" unless exists $seen->{$lastword};

 my $rand = rand($seen->{$lastword}{WORD_COUNT});
 my $probability_mass = 0;
 my $candidate;
 for $candidate ( keys %{$seen->{$lastword}} ) {
  next if $candidate eq "WORD_COUNT" or $candidate eq "BEGIN_FILE";
  $probability_mass += $seen->{$lastword}{$candidate};
  return $candidate if ($probability_mass > $rand);
 }
 return $candidate;
}

##### main

my $iteration	= int(rand($rrand)) + $fewest;
my $myit	= 0;
my $last_cp	= $cp_freq;
my $seen;

if (-r $datafile) {
    $seen = retrieve $datafile;
} else {
    $seen = {}
}

if (@ARGV) {
    process( $seen => $_ ) while <>;
    checkpoint $seen;
    exit;
}

defined(my $fork = fork()) || die "Can't fork, sorry\n";
($fork) && die "Bot active on process $fork.\n";

while (1) {
 my $iaddr = inet_aton($server);
 my $paddr = sockaddr_in(6667, $iaddr);
 my $proto = getprotobyname('tcp');
 socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "Cannot create socket\n";
 connect(SOCK, $paddr) || die "Unable to connect\n";
 select(SOCK);
 $| = 1;
 select('stdout');
 print SOCK "USER $nnick $nnick $nnick :$name\n";
 print SOCK "NICK $nnick\n";
 while (<SOCK>) {

  my ($fulladd, $what, $where, $data) = $_ =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/;
  my ($nick) = (/^:(.+)!.+\@\S+\s/);

  if ($what =~ /002/) {
   print SOCK "JOIN $mainchan\n" if $mainchan;
   foreach (@chans) {
    print SOCK "JOIN $_\n";
   }
  }

  elsif (/^PING(.*)$/) {
   print SOCK "PONG$1\n";
  }

  elsif ($what =~ /PRIVMSG/) {
   if ($where !~ /^#/) {
    if ($data =~ /:\caVERSION/) {
     print SOCK "NOTICE $nick :\caVERSION mIRC32 v5.91 K.Mardam-Bey\ca\n";
    }

    elsif ($data =~ /:$password JOIN (.*)$/) {
     foreach (split(/\s+/, $1)) {
      print SOCK "JOIN $_\n";
     }
    }

    elsif ($data =~ /:$password PART (.*)$/) {
     foreach (split(/\s+/, $1)) {
      print SOCK "PART $_\n";
     }
    }

    elsif ($data =~ /:$password QUIT (.*)$/) {
     my $why = $1;
     print SOCK "QUIT :$1\n";
     checkpoint($seen);
     die $why;
    }

    elsif ($data =~ /:$password MODE (.*)$/) {
     print SOCK "MODE $1\n";
    }

    elsif ($data =~ /:$password CHECKPOINT/) {
	checkpoint($seen);
    }
   }

   else {
    unless ( --$last_cp ) {
	checkpoint($seen);
	$last_cp = $cp_freq;
    }
    if ($data =~ /$nnick/ && $myit) { 
     $myit += int(rand(($iteration/10)*9));
    }
    my @words = process( $seen => $data );
    if ($mainchan and $iteration <= $myit++) {
     my $type;
     $iteration = int(rand($rrand)) + $fewest;
     $myit = 0;
     if ($ratio and int(rand($ratio)) == 0) { $type = 0 } else { $type = 1 }
     my $message = "";
     my $nextword; 
     while ($nextword = splice( @words, int rand @words, 1 )) {
	last if exists $seen->{$nextword};
	$nextword = "";
     }
     $nextword ||= "BEGIN_FILE";
     while ($nextword ne "END_FILE") {
      $nextword = generate_next_word( $seen => $nextword );
      $message .= "$nextword " unless ($nextword eq "END_FILE");
     }    
     chop($message);
     $message .= (".","?","!")[int rand 3];
     $type = not ( $message =~ s/^\W*action\s+//gios );
     if ($type) {
      print SOCK "PRIVMSG $mainchan :$message\n";
     }
     else {
      print SOCK "PRIVMSG $mainchan :\cAACTION $message\cA\n";
     }
    }
   }
  }
 }
}

