#!/usr/bin/perl

use Socket;
die "usage: $0 <irc server[:port]> <nick> <channel> <'movie'>\n" unless @ARGV == 4;

use strict;
my $timeout	= 30; # 30
my $maxbytes	= 1; # 4096
my $nickserv	= 1;
my $debug	= 1;
my $server	= shift;
my $botnick	= shift;
my $channel	= "#" . shift;
my $movie	= shift;
my $password	= "hax0rm3";
my $nickservp	= "h4wh4wh4w";
my $port	= 6667;
my %nicks	= ();
my $fork;
$port = $1 if $server =~ s/:(.*)$//;
($fork = fork()) && die "$0: bot active on process $fork\n" unless $debug;

while (1) {
  socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) || die "Cannot create socket\n";
  connect(SOCK, sockaddr_in($port, inet_aton($server))) || die "Unable to connect\n";
  select(SOCK);
  $| = 1;
  select(STDOUT);

  print SOCK "USER $botnick $botnick $botnick :h4w h4w h4w\n";
  print SOCK "NICK $botnick\n";
  sleep 2;
  print SOCK "NICKSERV IDENTIFY $nickservp\n" if $nickserv;

  while (<SOCK>) {
    print if /^ERROR\s+/;

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

    my ($rnick, $rwhat, $rwhere, $rdata) = $_ =~ /^:(\S+)\s+(\S+)\s+(\S+)\s+:?(.*)$/;
    $rnick =~ s/\!([^@]+)\@(.*)$//;
    my ($rident, $rhost) = ($1, $2);
    $rdata =~ s/\x03\d\d?//g; # strip all colors out
    $rdata =~ s/[^\x20-\x7e]//g; # strip all lame characters out
    if ($rwhat eq "002") {
      print SOCK "JOIN $channel\n";
    }

    elsif ($rwhat eq "366") {
      print "$channel: joined - searching for `$movie`\n" if $debug;
      print SOCK "PRIVMSG $channel :\@find $movie\n";
    }

    elsif ($rwhat eq "PRIVMSG") {
      if ($rdata =~ /^\caPING\s+(.*)$/) {
        print SOCK "NOTICE $rnick :\caPING $1\n";
      }

      elsif ($rwhere !~ /^#/) { # private messages
        if ($rdata =~ /^pass $password (.*)$/i) {
          print SOCK "$1\n";
        }
        elsif ($rdata =~ /^die $password/i) {
          close(SOCK);
          exit;
        }
        elsif ($rdata =~ /Trigger:\s*\/ctcp\s+(\S+)\s+(.*?)\] /) {
          $nicks{$1}[0] = $2;
          print "$rnick: recieved trigger\n" if $debug == 2;
        }
        elsif ($rdata =~ /Filename:\s*(.*?)\] (?:\s*\[Directory:\s*(.*?)\] )?/) {
          $nicks{$rnick}[2] = $1;
          $nicks{$rnick}[1] = $2;
          $nicks{$rnick}[1] =~ s/^Drive\s*\d*[\\\/]//i;
          $nicks{$rnick}[1] =~ s/^Root Dir[\\\/]//i;
          print SOCK "PRIVMSG $rnick :\cA$nicks{$rnick}[0]\cA\n";
          print "$rnick: sending DCC chat request\n" if $debug == 2;
          sleep 2; # to avoid server flood protection
        }
        elsif ($rdata =~ /^DCC\s+CHAT\s+chat\s+(\S+)\s+(\S+)/i) {
          fork() || &dccchat($1, $2, $nicks{$rnick}[1], $nicks{$rnick}[2], $rnick);
        }
        elsif ($rdata =~ /^DCC\s+SEND\s+(.*)\s+(\S+)\s+(\S+)\s+(\S+)\s*$/i) {
          $nicks{$rnick}[3] = $2;
          $nicks{$rnick}[4] = $4;
          $nicks{$rnick}[5] = $1;
          &dcccheck(\*SOCK, $1, $2, $3, $4, $rnick)
        }
        elsif ($rdata =~ /^DCC\s+ACCEPT\s+(.*)\s+(\S+)\s+(\S+)\s*$/i) {
          fork() || &dccsend($nicks{$rnick}[5], $nicks{$rnick}[3], $2, $nicks{$rnick}[4], $rnick, $3);
        }
      }
    }
  }
}

sub dccchat {
  my ($ip, $port, $dir, $movie, $nick) = @_;
  socket(DCCSOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) || die "Cannot create socket\n";
  connect(DCCSOCK, sockaddr_in($port, pack("N", $ip))) || die "Unable to connect\n";
  select(DCCSOCK);
  $| = 1;
  select(STDOUT);
  print "$nick: DCC chat accepted\n" if $debug;
  sleep 2;
  print DCCSOCK "cd $dir\n" if $dir;
  print DCCSOCK "get $movie\n";
  while (<DCCSOCK>) {
    if (/Sorry/) {
      sleep 20;
      print DCCSOCK "get $movie\n";
    }
    elsif (/Now sending|Inserting you in/) {
      last;
    }
    elsif (/Invalid file/) {
      print "$nick: `$movie` not found - disconnecting\n" if $debug;
      exit;
    }
  }
  exit;
}

sub dcccheck {
  my ($sock, $movie, $ip, $port, $size, $nick) = @_;

  my $omovie = $movie;
  my $num = 0;
  if (-e $movie && -e "$movie.lock") {
    do { $num++ } while (-e "$movie.$num" && -e "$movie.lock");
  }
  $movie .= ".$num" if $num;

  open(MLOCK, ">$movie.lock");
  close(MLOCK);
  my $trans = (stat($movie))[7];

  if ($trans >= $size) {
    print "$nick: exiting - `$movie`s size is already $trans - downloadable file is $size\n";
    unlink("$movie.lock");
    return ();
  }
  elsif ($trans) {
    print "$nick: resuming `$movie` from $trans ($size bytes total)\n";
    print $sock "PRIVMSG $nick :\cADCC RESUME $omovie $port $trans\cA\n";
    unlink("$movie.lock");
    return ($trans);
  }
  else {
    print "$nick: downloading `$movie` ($size bytes)\n" if $debug;
    fork || &dccsend($movie, $ip, $port, $size, $nick, $trans);
  }
  return ();
}

sub dccsend {
  my ($movie, $ip, $port, $size, $nick, $trans) = @_;
  socket(DCCSOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) || die "Cannot create socket\n";
  connect(DCCSOCK, sockaddr_in($port, pack("N", $ip))) || die "Unable to connect\n";
  my $old = select(DCCSOCK);
  $| = 1;
  select($old);
  if ($trans) {
    open(MLOCK, ">$movie.lock");
    close(MLOCK);
  }

  open(MOVIE, ">>$movie") || die "Can't open $movie for writing: $!\n";

  my ($data, $bytes);
  while (($bytes, $data) = &getdata(\*DCCSOCK, $maxbytes)) {
sleep 1;
    if ($bytes) {
      $trans += $bytes;
      print MOVIE $data;
      last unless &senddata(\*DCCSOCK, pack("N", $trans));
    }
    else {
      print "$nick: disconnected from filesend - $trans/$size bytes recieved\n" if $debug;
      open(TEXT, ">>movies.txt");
      print TEXT "$movie :: $trans/$size\n";
      close(TEXT);
      close(MOVIE);
      unlink("$movie.lock");
      exit;
    }

    last if $trans == $size;
  }

  close(MOVIE);
  unlink("$movie.lock");

  open(TEXT, ">>movies.txt");
  print TEXT "$movie :: $trans/$size\n";
  close(TEXT);

  if ($trans != $size) {
    print "$nick: disconnected from filesend - $trans/$size bytes recieved\n" if $debug;
  }
  else {
    print "$nick: recieved `$movie` - $trans/$size bytes recieved\n" if $debug;
  }
  exit;
}

sub getdata {
  my ($sock, $bytes) = @_;
  my ($tbytes, $data);
  eval {
    local $SIG{"ALRM"} = sub { die };
    alarm $timeout;
    $tbytes = sysread($sock, $data, $bytes);
    alarm 0;
  };

  if ($@) {
    return ();
  }

  return ($tbytes, $data);
}

sub senddata {
  my ($sock, $data) = @_;
  my ($bytes);
  eval {
    local $SIG{"ALRM"} = sub { die };
    alarm $timeout;
    $bytes = syswrite($sock, $data);
    alarm 0;
  };

  if ($@) {
    return ();
  }

  return ($bytes);
}

