# bleh stuff

use Irssi;
use Irssi::Irc;
use strict;
use vars qw($VERSION %IRSSI $DEBUG);

# 0.05 -- Add IPv6 support

$VERSION = q$Revision$;
%IRSSI = (authors     => 'Don Armstrong',
	  name        => 'auto_bleh',
	  description => 'Provides /ak /aq /ab /abr /abrn /arn /amb /amr /at',
	  license     => 'GPL',
	  changed     => q$Id$,
	 );

$DEBUG = 1 unless defined $DEBUG;

my ($actions, %defaults);

%defaults = (GET_OP       => 1,    # Should we try to get opped when we auto_bleh?
	     USE_CHANSERV => 1,    # Should we use chanserv to get opped?
	     EXPIRE       => 6000,   # Do not try to do anything if the action is more than 6000 seconds old.
	     DEOP         => 1,    # Automatically deop after we've done whatever we were supposed to do.
	     TIMEOUT      => 10,   # Timeout /at bans after 10 minutes
	     SMELLSLIKEFN => qr/freenode.net/,
	    );

my %command_bindings = (ak   => 'cmd_ak',
			ab   => 'cmd_ab',
			aq   => 'cmd_aq',
			ar   => 'cmd_ar',
			abr  => 'cmd_abr',
			abk  => 'cmd_abk',
			abrn => 'cmd_abrn',
			abk  => 'cmd_abkn',
			arn  => 'cmd_arn',
			amb  => 'cmd_amb',
			amr  => 'cmd_amr',
			at   => 'cmd_at',
		       );

my %bans_to_remove;


sub cmd_at {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(timeout)],$data,$server,$witem);
}

sub cmd_ak {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(kick)],$data,$server,$witem);
}

sub cmd_abk {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(kick ban)],$data,$server,$witem);
}
sub cmd_abkn {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(kick ban notice)],$data,$server,$witem);
}

sub cmd_amb{
     my ($data, $server, $witem) = @_;
     my @nicks = split /\s+/, $data;
     for (@nicks) {
	  next unless /\w/;
	  do_auto_bleh([qw(ban)],$_,$server,$witem);
     }
}

sub cmd_ab {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(ban)],$data,$server,$witem);
}

sub cmd_aq {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(quiet)],$data,$server,$witem);
}


sub cmd_amr{
     my ($data, $server, $witem) = @_;
     my @nicks = split /\s+/, $data;
     for (@nicks) {
	  next unless /\w/;
	  do_auto_bleh([qw(remove)],$_,$server,$witem);
     }
}

sub cmd_ar {
     my ($data, $server, $witem) = @_;
     return do_auto_bleh([qw(remove)],$data,$server,$witem);
}
sub cmd_abr {
     my ($data, $server, $witem) =@_;
     return do_auto_bleh([qw(remove ban)],$data,$server,$witem);
}
sub cmd_abrn {
     my ($data, $server, $witem) =@_;
     return do_auto_bleh([qw(remove ban notice)],$data,$server,$witem);
}
sub cmd_arn {
     my ($data, $server, $witem) =@_;
     return do_auto_bleh([qw(remove notice)],$data,$server,$witem);
}


sub do_auto_bleh {
     my ($cmd, $data, $server, $witem, $duration) = @_;

     if (!$server || !$server->{connected}) {
	  Irssi::print("Not connected to server");
	  return;
     }

     if ($witem->{type} ne 'CHANNEL') {
	  Irssi::print("Can't autokick on a non-channel. [$witem->{type}]");
	  return;
     }

     if (ref($cmd) eq 'HASH') {
	  # do nothing
     }
     elsif (ref($cmd) eq 'ARRAY') {
	  $cmd = {map {$_,1} @$cmd};
     }
     elsif (not ref($cmd)) {
	  $cmd = {map {$_,1} split /\s*,\s*/, $cmd};
     }
     else {
	  die "Cmd: $cmd option to do_auto_bleh is not a supported type";
     }

     # Fix up options for opn which doesn't do quiet or remove;
     # turn them into ban and kick, respectively.
     if ($server->{address} !~ $defaults{SMELLSLIKEFN}) {
	  my %fn_mapping = (remove  => 'kick',
			    quiet   => 'ban',
			    unquiet => 'unban',
			    timeout => 'btimeout',
			   );
	  for my $key (keys %fn_mapping) {
	       if ($$cmd{$key}) {
		    delete $$cmd{$key};
		    $$cmd{$fn_mapping{$key}} = 1;
	       }
	  }
     }

     # use Data::Dumper;
     # Irssi::print(Dumper($data,$server,$witem));
     # set the network that we're on, the channel and the nick to kick
     # once we've been opped

     # Heh. Looks like $data needs to be sanitized a bit.
     $data =~ /^\s*([^\s]+)\s*(\d+)?\s*(.+?|)\s*$/;
     my $nick = $1;
     my $timeout = $2;
     my $reason = $3;
     $timeout = $defaults{TIMEOUT} if not defined $timeout or $timeout eq '';
     $reason = 'see ya!' if not defined $reason or $reason eq '';

     my $nick_rec = $witem->nick_find($nick);
     if (not defined $nick_rec) {
	  Irssi::print("Unable to find nick: $nick");
     }

     my $hostname = $nick_rec->{host} if defined $nick_rec;
     Irssi::print("Unable to find hostname for $nick") if not defined $hostname or $hostname eq '';
     $hostname =~ s/.+\@//;

     Irssi::print("Nick set to '$nick' from '$data', reason set to '$reason'.") if $DEBUG;
     my $action = {type      => $cmd,
		   nick      => $nick,
		   nick_rec  => $nick_rec,
		   network   => $witem->{server}->{chatnet},
		   server    => $witem->{server},
		   completed => 0,
		   inserted  => time,
		   channel   => $witem->{name},
		   reason    => $reason,
		   hostname  => $hostname,
		   timeout   => $timeout,
		  };
     Irssi::print(i_want($action)) if $DEBUG;
     if ($witem->{chanop}) { # we seem to be opped.
	  take_action($action,$server,$witem);
     }
     else {
	       $actions->{$data.$action->{inserted}}=$action;
	       get_op($server, $action->{channel}) if $defaults{GET_OP};
     }

}

sub get_op {
     my ($server,$channel) = @_;

     Irssi::print("MSG chanserv op $channel");
     $server->command("MSG chanserv op $channel") if $defaults{USE_CHANSERV};
}

sub i_want {
     my $action = shift;

     return "I've wanted to $action->{type} $action->{nick} off $action->{channel} on $action->{network} since $action->{inserted}";
}

sub take_action {
     my ($action,$server,$channel) = @_;

     my $type = $action->{type};
     # Now support multiple actions against a single nick (to FE, kick
     # ban, or remove ban). See /abr foo
     if ($type->{timeout}) {
	  Irssi::print("Quieting $action->{nick} on $action->{channel} with hostname $action->{hostname} for $action->{timeout} minutes");
	  $server->send_raw("MODE $action->{channel} +q *!*@".$action->{hostname}) if $action->{hostname} ne ''; #quiet hostname
	  $bans_to_remove{"$action->{nick}$action->{inserted}"} = $action;
     }
     if ($type->{btimeout}) {
	  Irssi::print("Quieting $action->{nick} on $action->{channel} with hostname $action->{hostname} for $action->{timeout} minutes");
	  $server->send_raw("MODE $action->{channel} +b *!*@".$action->{hostname}) if $action->{hostname} ne ''; #quiet hostname
	  $bans_to_remove{"$action->{nick}$action->{inserted}"} = $action;
     }
     if ($type->{quiet}) {
	  Irssi::print("Quieting $action->{nick} on $action->{channel} with hostname $action->{hostname}") if $DEBUG;
	  # Find hostname
	  if ($action->{hostname}) {
	       $server->send_raw("MODE $action->{channel} +b %*!*@".$action->{hostname}) if $action->{hostname} ne ''; #quiet hostname
	  }

     }
     if ($type->{ban}) {
	  Irssi::print("Banning $action->{nick} from $action->{channel} with hostname $action->{hostname}") if $DEBUG;
	  $server->send_raw("MODE $action->{channel} +b *!*@".$action->{hostname}) if $action->{hostname} ne ''; # ban hostname
     }
     if ($type->{kick}) {
	  Irssi::print("Kicking $action->{nick} from $action->{channel}") if $DEBUG;
	  if ($action->{reason} =~ /\s/) {
	       $server->send_raw("KICK $action->{channel} $action->{nick} :$action->{reason}");
	  }
	  else {
	        $server->send_raw("KICK $action->{channel} $action->{nick} $action->{reason}");
	   }
     }
     if ($type->{remove}) {
	  Irssi::print("Removing $action->{nick} from $action->{channel}") if $DEBUG;
	  if ($action->{reason} =~ /\s/) {
	       $server->send_raw("REMOVE $action->{channel} $action->{nick} :$action->{reason}");
	  }
	  else {
	       $server->send_raw("REMOVE $action->{channel} $action->{nick} $action->{reason}");
	  }
     }
     if ($type->{notice}) {
	  Irssi::print("Noticing $action->{nick} with $action->{reason}") if $DEBUG;
	  $server->command("NOTICE $action->{nick} $action->{reason}");
     }
     # unquiet
     if ($type->{unquiet}) {
	  Irssi::print("Unquieting $action->{nick} on $action->{channel} with hostname $action->{hostname}") if $DEBUG;
	  $server->command("MODE $action->{channel} -q *!*@".$action->{hostname});
     }
     # unban
     if ($type->{unban}) {
	  Irssi::print("Unbanning $action->{nick} on $action->{channel} with hostname $action->{hostname}") if $DEBUG;
	  $server->command("MODE $action->{channel} -b *!*@".$action->{hostname});
     }

     return; #for now.
}

sub deop_us {
     my ($rec) = @_;
     my $channel = $rec->{channel};
     my $server = $rec->{server};

     #$server->command("MODE $channel->{name} -o $channel->{ownick}->{nick}");
     if ($channel->{chanop}) {
	  Irssi::print("MODE $channel->{name} -o $channel->{ownnick}->{nick}");
	  $channel->command("/deop $channel->{ownnick}->{nick}");
     }
}

sub sig_mode_change {
     my ($channel,$nick) = @_;

     #Irssi::print(Dumper($nick));
     #Irssi::print(Dumper($channel));

     # Are there any actions to process?
     # See if we got opped.
     return if scalar(keys %$actions) eq 0;

     if ($channel->{server}->{nick} eq $nick->{nick} and $nick->{op}) {
	  # Ok, we've been opped, or we are opped now, so do whatever we're supposed to do.
	  Irssi::print("We've been opped") if $DEBUG;
	  # We seem to need some sort of delay here for the chanop stuff to catch up
	  Irssi::timeout_add_once(400,'attempt_actions',undef);
     }
     else {
	  Irssi::print("Fooey. Not opped.") if $DEBUG;
     }
}

sub attempt_actions {

     my @deop_array;

     foreach (keys %$actions) {
	  #Irssi::print(Dumper($actions->{$_}));
	  # See if this action is too old
	  if (time - $actions->{$_}->{inserted} > $defaults{EXPIRE}) {
	       Irssi::print("Expiring action: \"".i_want($actions->{$_})."\" because of time");
	       delete $actions->{$_};
	       next;
	  }
	  Irssi::print(i_want($actions->{$_})) if $DEBUG;
	  # Find the server to take action on
	  my $server = Irssi::server_find_chatnet($actions->{$_}->{network});
	  Irssi::print("Unable to find server for chatnet: $actions->{$_}->{network}") and return if not defined $server;
	  Irssi::print("Found server for chatnet: $actions->{$_}->{network}") if $DEBUG;
	  # Find the channel to take action on
	  my $s_channel = $server->channel_find($actions->{$_}->{channel});
	  Irssi::print("Unable to find channel for channel: $actions->{$_}->{channel}") and return if not defined $s_channel;
	  Irssi::print("Found channel for channel: $actions->{$_}->{channel}") if $DEBUG;
	  # Are we opped on that channel?
	  if ($s_channel->{chanop}) { 	       # Yes? Take the action against the user.
	       Irssi::print("We are opped on the channel!") if $DEBUG;
	       take_action($actions->{$_},$server,$s_channel);
	       push @deop_array,{server=>$server,channel=>$s_channel} if $defaults{DEOP};
	       delete $actions->{$_}; # Do not repeat this action.
	  }
	  else {
	       Irssi::print("We are not opped on the channel.") if $DEBUG;
	  }
     }
     foreach (@deop_array) {
	  deop_us($_);
     }
}


sub try_to_remove_bans {
     return unless keys %bans_to_remove;
     for my $key (keys %bans_to_remove) {
	  if (($bans_to_remove{$key}{inserted} + $bans_to_remove{$key}{timeout}*60) < time) {
	       $bans_to_remove{$key}{type} = {unquiet => 1}; #unquiet
	       $actions->{$key} = $bans_to_remove{$key};
	       delete $bans_to_remove{$key};
	       get_op($actions->{$key}{server}, $actions->{$key}{channel}) if $defaults{GET_OP};
	  }
     }
}

# call the try to remove bans function every minute
Irssi::timeout_add(1000*60,'try_to_remove_bans',undef);
Irssi::signal_add_last('nick mode changed','sig_mode_change');
my ($command,$function);

while (($command,$function) = each %command_bindings) {
     Irssi::command_bind($command,$function);
}

1;
