###############################################################################
# badword.pl
# Copyright (C) 2002  Jan 'fissie' Sembera <fis@ji.cz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
###############################################################################
# This is configurable badword script. It may be configured to ban immediately
# when first badword is detected, or it may count badwords and if number of 
# badwords of given nick exceeds limit, ban him. Badword count may also be
# expired if no badword is seen for specified period of time. Optional 
# verbosity (let's call it logging) may be enabled as well
#
# Runtime variables:
#
# badword_channels = list of channels where script is active, separated by space
# badword_words = list of 'bad words' that trigger this, separated by space
# badword_reason = reason used in kick when count exceeds permitted limit
# badword_limit = if number of detected badwords reaches this number, ban'em. 
#                 Set 1 to immediately kickban.
# badword_clear_delay = if no badword is detected from user for time specified
#                       here (in seconds), clear his counter. Set 0 to disable.
# badword_verbose = turns on/off logging features
# badword_ban_delay = ban after number of kicks specified here. 0 - disables
#                     banning, 1 - ban immediately, ... 
# badword_exemptions = list of hostmasks that is excluded: nick!user@host
# badword_warning_1    = a text that is shown (in the channel, prefixed with nick) at first badword
# badword_warning_2    = a text that is shown (in the channel, prefixed with nick) at second badword
# badword_warning_3    = a text that is shown (in the channel, prefixed with nick) at third badword
###############################################################################
#
# Changelog:
#
# Feb 17 2007 [dmp] (dennis@moellegaard.dk)
#    - new logic to auto-op/deop, 
#    - badword_exemptions (it's global!)
#    - added a badword_warning_1/2/3 text that is shown first/second/third time typing a badword
# Jun 4 2002
#    - added ban delaying feature
#
###############################################################################
use Irssi;
use Irssi::Irc;
use Data::Dumper;
use strict;
use vars qw($VERSION %IRSSI);

$VERSION = "0.0.3";

%IRSSI = (
    authors     =>  "Jan 'fissie' Sembera",
    contact     =>  "fis\@ji.cz",
    name        =>  "badword",
    description =>  "Configurable badword kickbanning script",
    license     =>  "GPL v2 and any later",
    url         =>  "http://fis.bofh.cz/devel/irssi/",
);

my %nick_dbase;
my %action_queue; 

sub sig_public {
  my ($server, $msg, $nick, $address, $target) = @_;
  my $watch_channels = Irssi::settings_get_str('badword_channels');
  my $watch_words    = Irssi::settings_get_str('badword_words');
  my $exemptions     = Irssi::settings_get_str('badword_exemptions');

  my @chanz = split (/ /, $watch_channels); 
  my @wordz = split (/ /, $watch_words); 
  my @exemp = split (/ /, $exemptions);
  my @wordz_instant = split (/ /, Irssi::settings_get_str('badword_words_instantban'));
  
  my $nickrec = $server->channel_find($target)->nick_find($nick);
  my $nickmode = $nickrec->{op} ? "@" : $nickrec->{voice} ? "+" : "";

  my $aux = 0;

  my $verbose = Irssi::settings_get_bool('badword_verbose') == 1;

  if (! ($nickmode eq "")) {
    return;
  }

  foreach my $e (@exemp) {
	return if( mask_match("$nick!$address",$e) );
  }


  foreach my $ch (@chanz) {
    if ($ch eq $target) {
      $aux = 1;
    }
  }

  if ($aux == 0) { 
    return;
  } 

  $aux = 0;

  foreach my $bw_instant (@wordz_instant) {
    if ($msg =~ /($bw_instant)/i) {
      my $gotop = $server->channel_find($target)->nick_find($server->{nick})->{op};
      my @host = split(/\@/, $address);
      if (($host[0] =~ /\~/) || ($host[0] =~ /\-/) || ($host[0] =~ /\=/) || ($host[0] =~ /\^/)) { $host[0] = "*"; }
      my $mask = '*!'.$host[0].'@'.$host[1];
      if( $gotop ) {
        $server->command('mode '.$target.' +b '.$mask);
      } else {
        push(@{$action_queue{$target}},'mode '.$target.' +b '.$mask);
          Irssi::print('BW: Actions pending, trying to op myself in ' . $target) if($verbose);
          $server->command('quote privmsg chanserv :op ' . $target);
      }
      Irssi::print('BW: Nick '.$nick.' with mask '.$mask.' punished for badwording on channel '.$target.' - banned') if($verbose);
      return;
    }
  }

  my $badword;
  foreach my $bw (@wordz) {
#   if (($msg =~ /\ $bw/) || ($msg =~ /^$bw/)) {
    if ($msg =~ /($bw)/i) {
      $aux = 1;
      $badword = $1;
    } 
  }

  if ($aux == 0) {
    return;
  }

  # Ok, here comes badword, check record

  my $luser = $nick_dbase{$nick}{$target};
  
  if (!$luser) {
    $nick_dbase{$nick}{$target}{'count'} = 1;
    $nick_dbase{$nick}{$target}{'kcount'} = 0;
    $nick_dbase{$nick}{$target}{'stamp'} = time();
  }
  else {
    if ((Irssi::settings_get_int('badword_clear_delay') != 0) && (($nick_dbase{$nick}{$target}{'stamp'})+(Irssi::settings_get_int('badword_clear_delay'))) < time()) {
      $nick_dbase{$nick}{$target}{'count'} = 1;
      Irssi::print('BW: Expired for '.$nick.' with hostmask '.$address.' on channel '.$target) if($verbose);
    } else {
      $nick_dbase{$nick}{$target}{'count'} = ($nick_dbase{$nick}{$target}{'count'})+1; 
    }
    $nick_dbase{$nick}{$target}{'stamp'} = time();
  }

  Irssi::print('BW: Detected badword "'.$badword.'" from nick '.$nick.' with hostmask '.$address.' on channel '.$target.' - '.$nick_dbase{$nick}{$target}{'count'}.' times') if($verbose);

  perform_action($server,$nick,$target,$address)
}

sub perform_action {
  my ($server, $nick, $target, $address) = @_;

  my $gotop = $server->channel_find($target)->nick_find($server->{nick})->{op};
  my $verbose = Irssi::settings_get_bool('badword_verbose') == 1;
  my $luser = $nick_dbase{$nick}{$target}{'count'};

  if ($luser == Irssi::settings_get_int('badword_limit')) {
    $nick_dbase{$nick}{$target}{'count'} = 0;
    # Ban'em!
    my @host = split(/\@/, $address);
    if (($host[0] =~ /\~/) || ($host[0] =~ /\-/) || ($host[0] =~ /\=/) || ($host[0] =~ /\^/)) { $host[0] = "*"; }
    my $mask = '*!'.$host[0].'@'.$host[1];
    $nick_dbase{$nick}{$target}{'kcount'} = ($nick_dbase{$nick}{$target}{'kcount'})+1; 
    if ((Irssi::settings_get_int('badword_ban_delay') > 0) && (Irssi::settings_get_int('badword_ban_delay') == $nick_dbase{$nick}{$target}{'kcount'})) {
      if( $gotop ) {
      	$server->command('mode '.$target.' +b '.$mask);
      }
	  else {
	  	push(@{$action_queue{$target}},'mode '.$target.' +b '.$mask);
	  }
      $nick_dbase{$nick}{$target}{'kcount'} = 0;
      Irssi::print('BW: Nick '.$nick.' with mask '.$mask.' punished for badwording on channel '.$target.' - banned') if($verbose);
    } else {
	  Irssi::print('BW: Nick '.$nick.' with mask '.$mask.' punished for badwording on channel '.$target.' - kicked') if($verbose);
    }

	if( $gotop ) {
      $server->command('quote kick '.$target.' '.$nick.' :'.Irssi::settings_get_str('badword_reason'));
	}
	else {
      push(@{$action_queue{$target}},'quote kick '.$target.' '.$nick.' :'.Irssi::settings_get_str('badword_reason'));
	}
  }
  elsif( $luser == 1 ) {
  	if( Irssi::settings_get_str('badword_warning_1') ne '' ) {
		$server->command("msg $nick " . Irssi::settings_get_str('badword_warning_1') );
	}
  }
  elsif( $luser == 2 ) {
  	if( Irssi::settings_get_str('badword_warning_2') ne '' ) {
		$server->command("msg $nick " . Irssi::settings_get_str('badword_warning_2') );
	}
  }
  elsif( $luser == 3 ) {
  	if( Irssi::settings_get_str('badword_warning_3') ne '' ) {
		$server->command("msg $nick " . Irssi::settings_get_str('badword_warning_3') );
	}
  }
  if(!$gotop and defined $action_queue{$target}) {
    Irssi::print('BW: Actions pending, trying to op myself in ' . $target) if($verbose);
    $server->command('quote privmsg chanserv :op ' . $target) unless($gotop);
  }
}

sub sig_nick {
  my ($server, $newnick, $nick, $address) = @_;

  $newnick = substr ($newnick, 1) if ($newnick =~ /^:/);
  my $count = $nick_dbase{$nick}; 
  if ($count) {
    $nick_dbase{$nick} = undef;
    $nick_dbase{$newnick} = $count; 
    if (Irssi::settings_get_bool('badword_verbose') == 1) { Irssi::print('BW: Tranferred badwords from '.$nick.' to '.$newnick); }
  }
}

sub sig_mode_change {
  my ($channel,$nick) = @_;
  my $verbose = Irssi::settings_get_bool('badword_verbose') == 1;
  return unless($channel->{ownnick}->{nick} eq $nick->{nick});
  return unless($nick->{op});
  return unless(defined $action_queue{$channel->{name}} );

  Irssi::print('BW: Performing actions pending for ' . $channel->{name}) if($verbose);

  foreach my $action (@{$action_queue{$channel->{name}}}) {
    $channel->command($action);
  }

  $action_queue{$channel->{name}} = undef;
  $channel->command("deop $nick->{nick}");
}

sub mask_match ($$) {
	my ($what, $match) = @_;

	# stolen from shasta's friend.pl
	$match =~ s/\\/\\\\/g;
	$match =~ s/\./\\\./g;
	$match =~ s/\*/\.\*/g;
	$match =~ s/\!/\\\!/g;
	$match =~ s/\?/\./g;
	$match =~ s/\+/\\\+/g;
	$match =~ s/\^/\\\^/g;
	$match =~ s/\[/\\\[/g;

	return ($what =~ /^$match$/i);
}

Irssi::settings_add_str("misc", "badword_channels", "");
Irssi::settings_add_str("misc", "badword_words", "");
Irssi::settings_add_str("misc", "badword_words_instantban", "");
Irssi::settings_add_str("misc", "badword_reason", "BW: badword limit exceeded"); 
Irssi::settings_add_int("misc", "badword_limit", 3);
Irssi::settings_add_int("misc", "badword_clear_delay", 3600);
Irssi::settings_add_int("misc", "badword_ban_delay", 1);
Irssi::settings_add_bool("misc", "badword_verbose", 0); 
Irssi::settings_add_str("misc", "badword_exemptions", "ChanServ!ChanServ\@services.");
Irssi::settings_add_str("misc", "badword_warning_1", "");
Irssi::settings_add_str("misc", "badword_warning_2", "");
Irssi::settings_add_str("misc", "badword_warning_3", "");

Irssi::signal_add_last('message public', 'sig_public');
Irssi::signal_add_last('event nick', 'sig_nick');
Irssi::signal_add_last('nick mode changed','sig_mode_change');
