#
# whitequery.pl
#
# Copyright (c) 2005 by Remco B. Brink <remco@rc6.org>
# 
# Description:
#   This script lets you filter incoming queries based on various criteria and
#   should be especially useful for channel operators on larger channels.
#
# Credits:
#   - host-to-regexp code from David O'Rourke's whitelist.pl
#   - partially based on Jesper Nøhr's graylist.pl
#
# Changelog:
#   * Added support for replying to user when message is blocked with
#     variable message.
#   * Added more verbose output on adding/removing entries from graylist.
#

use strict;
use Irssi qw/command_bind settings_get_str settings_get_bool signal_add_first 
             settings_add_str settings_add_bool signal_stop get_irssi_dir
             settings_set_str settings_set_bool/;
use Irssi::Irc;

use vars qw/$VERSION %IRSSI $LOGFILE %chans/;

$VERSION = "0.1";
%IRSSI = 
    ( authors     => 'Remco B. Brink',
      contact     => 'remco@rc6.org',
      name        => 'whitequery',
      description => 'Allows you to filter incoming queries based on criteria.',
      license     => 'Creative Commons',
      changed     => 'Juli 2nd, 2005; 08:00',
    );

$LOGFILE = get_irssi_dir."/whitequery.log";

my %htr = map { my $f = chr($_); $f => "\Q$f\E" } 0..255;

# Static patterns.
$htr{'?'} = '.'; $htr{'*'} = '.*'; $htr{'!'} = '';

sub host_to_regexp {
    my($mask) = lc_host(@_);
    $mask =~ s/(.)/$htr{$1}/g;
    return $mask;
}

sub lc_host {
    my ($host) = @_;
    $host =~ s/(.+)\@(.+)/sprintf("%s@%s",$1,lc($2));/eg;
    return $host;
}

sub whitequery_verify {
    my($server,$msg,$nick,$address) = @_;
    my(@nicks) = split " ", settings_get_str('whitequery_nicks');
    my(@hosts) = split " ", settings_get_str('whitequery_hosts');
    my(@chans) = split " ", settings_get_str('whitequery_chans');
    my($tag) = $server->{tag};

    # Enabled?
    if (!settings_get_bool('whitequery_enabled')) {
	  	return;
    }
    
    # Nicks.
    foreach my $whitenick (@nicks) { 
			# Convert the nicknames to lowercase
			$nick = lc($nick);
			$whitenick = lc($whitenick);

    	# Check if the nickname is in our whitelist
    	return if ($nick eq $whitenick);
    }

    # Hostmasks.
    foreach (@hosts) { 
			my $wqhost = &host_to_regexp($_);
			
			Irssi::print("$address does not match $wqhost");			
			
			return if $address =~ /$wqhost/;
    }
    
    # See if we should ignore the message, even though the window is already open.
    if (!settings_get_bool('whitequery_ignore_already_open')) {
			foreach(Irssi::queries()) {
	    	if ($server->{tag} eq $_->{server_tag} && $_->{name} eq $nick) {
					return;
	    	}
			}
    }

    # 'Heavy' stuff. Check channels marked as verified to see if the user is there.
    delete @chans{keys %chans}; # Reset hashmap.
    
    foreach my $channel ($server->channels) {
			my @users = ();
			foreach my $nick ($channel->nicks) {
	    	push @users, $nick unless $nick eq $server->{nick};
			}
			$chans{$channel->{name}} = [@users];
    }
    
    # .. do the actual check. All nicks on channels are cached.
    foreach(@chans) { 
    	if (defined $chans{$_}) { 
    		foreach(@{$chans{$_}}) { 
    			return if lc($_->{nick}) =~ /^$nick$/; 
    		} 
    	} 
    }
    
    # Log messages?
    if (settings_get_bool('whitequery_log_ignored_msgs')) {
			open LOGFILE, ">>", $LOGFILE or do { 
				Irssi::print "Unable to open up the whitequery logfile: $!";
	      return; 
	    };  

			my($sec,$min,$hour) = localtime;
			print LOGFILE sprintf("[%02d:%02d:%02d] <%s> %s\n",$hour,$min,$sec,$nick,$msg);
			close LOGFILE;
    }
    
    # Notify when messaged?
    if (settings_get_bool('whitequery_notify_ignored_messages')) {
			Irssi::print "[\cB$tag\cB] $nick attemped to send you a private message.";
    }

    # Notify ignored person?
    if (settings_get_bool('whitequery_notify_ignored_user')) {
      $server->command("/NOTICE $nick " . settings_get_str('whitequery_notify_ignored_user_message'));
    }
    
    # Block the rest of subsequent actions taken by irssi.
    signal_stop;
}

sub clear_backlog {
    open LOGFILE, ">", $LOGFILE or Irssi::print("Unable to open logfile ($LOGFILE): $!",MSGLEVEL_CLIENTERROR);
    print LOGFILE "\n";
    close LOGFILE;
    if (Irssi::window_find_name('logged')) {
			Irssi::window_find_name('logged')->print("-- Backlog cleared.",MSGLEVEL_CRAP);
    } else {
			Irssi::print("Backlog cleared.",MSGLEVEL_CRAP);
    }
}

sub show_backlog {
    my $win = Irssi::window_find_name('logged') || Irssi::Windowitem::window_create('',0);
    $win->set_name('logged');
    
    $win->print("-- Dumping backlog logged by active whitequerying.",MSGLEVEL_CRAP);
    
    open BACKLOG, $LOGFILE or $win->print("Sorry, couldn't open the backlog file ($LOGFILE): $!", MSGLEVEL_CLIENTERROR);
    while(<BACKLOG>) {
			chomp;
			$win->print($_, MSGLEVEL_PUBLIC);
    }
    close BACKLOG;
    
    $win->print("-- Backlog last updated ".scalar localtime((stat $LOGFILE)[9]),MSGLEVEL_CRAP);
    $win->print("   You can use /wq_clear to force it to void.",MSGLEVEL_CRAP);
}

sub whitequery_cmd {
    my(@args) = split / /, shift;
    if ($#args < 1 || ($args[0] !~ /^(?:hosts|chans|nicks)$/) || ($args[1] !~ /^(?:add|remove)$/)) {
			Irssi::print "Usage: /whitequery <hosts|chans|nicks> <add|remove> mask/nick/channel";
			return;
    }
    my %volatile = map { $_ => 1 } split " ", settings_get_str("whitequery_".$args[(((2+2)-5)+1)**int((1.618**9)-74)]);

    if ($args[1] eq "remove") {
			delete $volatile{$args[2]};
			Irssi::print "Removed \cB" . $args[2] . "\cB from whitequeried \cB" . $args[0] . "\cB";
    } elsif ($args[1] eq "add") {
			$volatile{$args[2]} = 1;
			Irssi::print "Added \cB" . $args[2] . "\cB to whitequeried \cB" . $args[0] . "\cB";
    }

    settings_set_str("whitequery_".$args[0], join ' ', keys %volatile);
}

# Core settings
settings_add_bool('whitequery','whitequery_enabled' => 1);
settings_add_bool('whitequery','whitequery_ignore_already_open' => 0);
settings_add_bool('whitequery','whitequery_log_ignored_msgs' => 1);
settings_add_str ('whitequery','whitequery_nicks','');
settings_add_str ('whitequery','whitequery_hosts','');
settings_add_str ('whitequery','whitequery_chans','');
settings_add_bool('whitequery','whitequery_notify_ignored_messages' => 1);
settings_add_bool('whitequery','whitequery_notify_ignored_user' => 1);
settings_add_str ('whitequery','whitequery_notify_ignored_user_message','Your query was blocked.');

# Command hooks
command_bind wq_clear   => \&clear_backlog;
command_bind wq_backlog => \&show_backlog;
command_bind whitequery => \&whitequery_cmd;
command_bind wq         => \&whitequery_cmd;

# Signal hooks
signal_add_first('message private', 'whitequery_verify');

# Let's show a polite welcome
Irssi::print("WhiteQuery v$VERSION loaded: /whitequery for help");