Previous Section  < Day Day Up >  Next Section

Hack 28 Automate Voice Management

figs/moderate.gif figs/hack28.gif

Large channels are inevitable targets for abuse. Help prevent problems by creating a client script that is responsible for handing out voice status to deserving users.

Let's say you're the one in charge of managing a huge channel that is dedicated to some important event that's going on. Hundreds of people are joining and leaving and, like any large channel, it will inevitably attract some abusers as well, wanting to flood, swear, and spew out vile colorful text.

Your ban list is already full and a new horde of savages has just entered the channel. What do you do? A simple step would be to voice everyone as soon as they join the channel, make the channel moderated, and just devoice the user if he becomes abusive. The simple flaw here is that the evildoer can part and rejoin the channel and get voiced.

If you are a channel operator, you can voice another user by entering /mode #channel +v User. To moderate the channel, you must enter /mode #channel +m. While a channel is moderated, only channel operators and users with voice will be able to talk. Everyone else will still be able to see what's being said, but won't be able to join in with the conversation.


This hack comes in form of an irssi script and is implemented in Perl. It does not require any other modules and needs no special configuration, so you simply have to place it into your scripts directory (~/.irssi/scripts) and perform /script load autovoice in order to get it running.

The script automatically voices every newcomer on the channel, so make sure you have operator status on the channel or it won't work. However, if anyone gets devoiced, the script will remember this and save the host mask of the offender. If the offender rejoins the channel, she will not get autovoiced again. If anyone manually voices an offender, he will be removed from the blacklist and will be autovoiced if he joins the channel at a later moment. You can inspect the blacklist contents at any time by using the /AUTOVOICE command.

4.13.1 The Code

This is a fairly simple irssi Perl hack, so the comments in the code should explain what is going on. Every irssi script should have an %IRSSI hash that contains some basic information and $VERSION that shows the current version number of the script. Other scripts can then extract and use these pieces of information automatically. A good example of this is scriptassist.pl, which helps you manage your scripts repository and interfaces the http://scripts.irssi.org central irssi scripts repository. All good irssi scripts should announce themselves when they are loaded, just as ours politely does.

use strict;

use vars qw($VERSION %IRSSI);

use Irssi;



$VERSION = "0.0.1";

%IRSSI = (

  name        => 'autovoice',

  authors     => 'Petr Baudis',

  contact     => 'pasky@ucw.cz',

  description => 'Smart voice management on a channel',

  license     => 'BSD',

);



# In this blacklist we keep all the offending hostmasks

# Keys: channels, Values: pointers to arrays of strings

my %dmasks;



# This command lists the blacklist's content

sub cmd_autovoice {

  my ($data) = @_;

  foreach my $chan (keys %dmasks) {

    next unless ($dmasks{$chan});

    my $str = "[$chan] ";

    foreach my $mask (@{$dmasks{$chan}}) {

      $str .= $mask . ", ";

    }

    $str =~ s/, $//;

    Irssi::print($str);

  }

}



# Triggered when someone joins a channel

sub event_massjoin {

  my ($channel, $nicks_list) = @_;

  my @nicks = @{$nicks_list};



  return unless ($channel->{chanop});



  # Each nick in a batch...

  foreach my $nickrec (@nicks) {

    my $in_blacklist = 0;

    # Do we keep a blacklist for this channel?

    if (defined $dmasks{$channel->{name}}) {

      foreach my $mask (@{$dmasks{$channel->{name}}}) {

        # Is this user blacklisted?

        if ($channel->{server}->mask_match_address($mask, $nickrec->{nick},

                           $nickrec->{host})) {

          $in_blacklist = 1; last;

        }

      }

    }

    $channel->command("/voice ".$nickrec->{nick}) unless $in_blacklist;

  }

}



# Triggered when someone changes channel mode (including voice/devoice)

sub event_mode {

  my ($server, $data, $nick, $addr) = @_;

  my ($channel, @mmode) = split(/ /, $data);

  my ($mode, @args) = @mmode;

  my $operation;

  my $chanptr = $server->channel_find($channel);



  return if ($nick eq $server->{nick});

  foreach my $mchar (split //, $mode) {

    if ($mchar =~ /[+-]/) { $operation = $mchar; next; }

    if ($mchar =~ /[eIbolk]/) { shift @args; }

    if ($mchar ne 'v') { next; }



    # This is a voice/devoice

    my $victim = $args[0];

    my $victptr = $chanptr->nick_find($victim);

    if ($operation eq '+') {

      if (defined $dmasks{$channel}) {

        my @masks = @{$dmasks{$channel}};

        for (my $i = 0; $i < @masks; $i++) {

          if ($server->mask_match_address($masks[$i], $victim, 

            $victptr->{host})) {

            splice(@masks, $i, 1);

            $i--;

          }

        }

        $dmasks{$channel} = \@masks;

      }

    } else {

      my $in_blacklist = 1;

      foreach my $mask (@{$dmasks{$channel}}) {

        if ($server->mask_match_address($mask, 

          $victim, $victptr->{host})) {

          $in_blacklist = 0; last;

        }

      }

      push(@{$dmasks{$channel}}, $chanptr->ban_get_mask($victim, 0))

        unless $in_blacklist;

    }

  }

}



Irssi::command_bind('autovoice', 'cmd_autovoice');

Irssi::signal_add_last('massjoin', 'event_massjoin');

Irssi::signal_add_last('event mode', 'event_mode');



Irssi::print "AutoVoice.PL $VERSION (c) Petr Baudis <pasky\@ucw.cz> loaded.";

4.13.2 Running the Hack

The script will be active as soon as it is loaded. You can load the script by typing:

/script load autovoice

You can then enjoy a sensible debate channel, without having to worry about people ruining the karma.

4.13.3 Hacking the Hack

The main problem with the preceding script is that its state is not persistent. This means that if you restart irssi, the whole blacklist will be lost and you are left starting from scratch. This is easy to fix—just use the Data::Dumper module and print Dumper(\%dmasks) to a file each time you modify the hash. If the file already exists at startup, you can load its content into %dmasks.

This hack was originally written for a single irssi instance that is connected via a fast link and acts only as a given channel's gatekeeper. If you run it on multiple channels, the IRC session will start to get much more vulnerable to lag, as voicing/devoicing on so many channels will slow it down due to the rate limiting imposed by the IRC server. If your irssi client is in multiple channels and you would like to play gatekeeper on only one of them, you will need to hack some settings to control that behavior into the previous code. Also, if you are connected to multiple networks at the same time and would like your script to work properly, you will need to add support for multiple connections (for example, you could keep the blacklist as a hash of hashes, indexed by the server tag first).

It would also be good to have some way of manually editing the blacklist, allowing you to add some more general host masks to it or remove a good host mask. The script already provides a very simple interface for displaying the blacklist contents, but improving this is one thing you can experiment with.

Petr Baudis

    Previous Section  < Day Day Up >  Next Section