irssi-win-builds/scripts/automode.pl

322 lines
8.2 KiB
Perl

# automode.pl
#
# Passively learn and actively maintain the ops/voices/halfops in channels.
# This is a no-maintenance auto-op/auto-voice script for irssi.
#
# INSTALL:
# 1) /script load automode.pl
# 2) Be a channel operator
#
# HOW IT WORKS:
# When someone joins a channel and is given ops/voice/halfop, the script
# will record that user's mask, as a combination of their nickname and part
# of their hostname or IP address. When that person leaves and rejoins, the
# script will check against its database and regrant the user the modes
# he or she had before leaving.
#
# If a user is kicked from a channel, all modes for that person are removed.
# They must therefore be regiven by another operator manually. Note this.
#
# Also, this script relies on the "chatnet" attribute being set for a
# particular connection. Use /network (or /ircnet) to set up your networks
# and such. This script will spit out (lots of) warnings if the chatnet is
# not set.
#
# IGNORING CHANNELS:
# If you do not wish to maintain modes on a channel, add it to the setting
# "automode_ignore" in the form <tag>:<channel>, separated by spaces.
#
# For example: /set automode_ignore FreeNode:#perl EFnet:#irssi
# (should) not maintain modes in #perl on FreeNode or #irssi
# on EFnet, provided FreeNode and EFnet are the tags for those
# connections.
#
# NOTES:
# The Perl module Data::Serializer is needed for this script.
# The database file is not written instantaneously; it is on a timer and is
# written to every five minutes or so. If the script is reloaded before it has
# had a chance to save will result in forgotten modes.
#
use strict;
use Irssi;
use Data::Serializer;
use Data::Dumper;
use vars qw($VERSION %IRSSI);
$VERSION = '1.3';
%IRSSI = (
authors => 'Matt "f0rked" Sparks',
contact => 'ms+irssi@quadpoint.org',
name => 'automode',
description => 'Mode maintainer',
license => 'BSD',
url => 'http://quadpoint.org',
changed => '2008-06-14',
);
# show debug lines
my $debug = 0;
my $s = new Data::Serializer;
my $file = Irssi::get_irssi_dir."/automode_list";
if (!-e $file) {
print "[automode] creating $file";
system("touch $file");
}
my $listref = $s->retrieve($file);
my %list = $listref ? %{$listref} : ();
#print Dumper %list;
my $save_tag;
my %buffer_tags;
my %buffer;
sub save_list
{
$s->store(\%list,$file);
}
sub clear_list
{
%list = ();
}
sub make_mask
{
my($address) = @_;
return if !$address;
my($ident, $host) = split /@/, $address;
my @split = split /\./, $host;
if (@split <= 2) {
# host is something like "foo.com". We cannot make the mask *.com.
} else {
if ($split[$#split] =~ /^\d+$/) {
# Looks like an IP address.
pop @split;
$host = join(".", @split) . ".\d{1,3}";
} else {
# Mask the first segment.
shift @split;
$host = ".+?." . join(".", @split);
}
}
return ".+?!.*${ident}@" . "${host}";
}
sub show
{
my($net, $channel) = @_;
print Dumper %{$list{$net}->{$channel}};
}
sub show_all
{
my $list;
print Dumper %list;
}
sub clear_channel
{
my($net, $channel) = @_;
delete $list{$net}->{$channel};
}
sub set_modes
{
my($net, $channel) = @{$_[0]};
return if !$buffer{$net}->{$channel};
my($nicks, $modes) = values(%{$buffer{$net}->{$channel}});
print "[automode] modes: $modes, nicks: $nicks" if $debug;
my $c = Irssi::server_find_chatnet($net)->channel_find($channel);
# iterate through the modes and see which ones we don't have to set
my($final_modes, $final_nicks);
my $i = 0;
for (split //,$modes) {
my $m = $_;
my $n = (split / /, $nicks)[$i];
$i++;
next if (!$c->nick_find($n));
next if ($m eq "o" && $c->nick_find($n)->{"op"});
next if ($m eq "v" && $c->nick_find($n)->{"voice"});
next if ($m eq "h" && $c->nick_find($n)->{"halfop"});
# if we made it this far, add this to the final modes
$final_modes .= $m;
$final_nicks .= "$n ";
}
print "[automode] final modes: +$final_modes $final_nicks" if $debug;
$c->command("MODE $channel +$final_modes $final_nicks")
if ($final_modes && $final_nicks);
delete $buffer{$net}->{$channel};
}
sub mode2letter
{
my($mode) = @_;
if ($mode eq "@") {
return "o";
} elsif ($mode eq "+") {
return "v";
} elsif ($mode eq "%") {
return "h";
}
return -1;
}
sub remove_mode
{
my($net, $channel, $mask, $mode) = @_;
my $letter = mode2letter($mode);
$list{$net}->{$channel}->{$mask} =~ s/$letter//
if user_modes($net, $channel, $mask);
delete $list{$net}->{$channel}->{$mask}
if exists $list{$net}->{$channel}->{$mask}
and !$list{$net}->{$channel}->{$mask};
}
sub remove_all
{
my($net, $channel, $mask) = @_;
delete $list{$net}->{$channel}->{$mask}
if exists $list{$net}->{$channel}->{$mask};
}
sub user_modes
{
my($net, $channel, $mask) = @_;
return $list{$net}->{$channel}->{$mask};
}
sub add_mode
{
my($net, $channel, $mask, $mode) = @_;
return if !$mask or !$net or !$channel or !$mode;
my $letter = mode2letter($mode);
$list{$net}->{$channel}->{$mask} .= $letter
if $list{$net}->{$channel}->{$mask} !~ /$letter/;
Irssi::timeout_remove($save_tag);
$save_tag = Irssi::timeout_add_once(300, "save_list", []);
}
sub event_mode
{
my($channel, $nick, $setby, $mode, $type) = @_;
return if check_ignore($channel->{server}, $channel->{name});
my $w = Irssi::active_win;
return if $mode != '@' and $mode != '%' and $mode != '+';
my $chatnet = $channel->{server}->{chatnet};
my $tag = $channel->{server}->{tag};
print ("[automode] The 'chatnet' attribute is missing for the tag '$tag'. " .
"Use /network (or /ircnet) to properly manage this.") if !$chatnet;
return if !$chatnet;
my $mask = make_mask($nick->{host});
print "[automode] failed to make mask ($mask)" if (!$mask && $debug);
return if !$mask;
if ($type eq "+") {
print ("[automode] adding mode '$mode' for $mask in $channel->{name} on " .
$chatnet) if $debug;
add_mode($chatnet, $channel->{name}, $mask, $mode);
} else {
# don't remove op if they deop themselves.
return if $setby eq $nick->{nick};
print ("[automode] removing mode '$mode' for $mask in $channel->{name} " .
" on $chatnet") if $debug;
remove_mode($chatnet, $channel->{name}, $mask, $mode);
}
#show($chatnet, $channel->{name});
}
sub event_join
{
my($server, $channel, $nick, $address) = @_;
return if check_ignore($server, $channel);
my $mask = make_mask($address);
return if !user_modes($server->{chatnet}, $channel, $mask);
my $c = $server->channel_find($channel);
return if not $c->{chanop};
if (my $modes = user_modes($server->{chatnet}, $channel, $mask)) {
print "[automode] Matched mask ($mask) with modes: $modes" if $debug;
my $nick_list = "$nick " x length($modes);
my %buf = ($buffer{$server->{chatnet}}->{$channel} ?
%{$buffer{$server->{chatnet}}->{$channel}} : ());
$buf{modes} .= $modes;
$buf{nicks} .= $nick_list;
$buffer{$server->{chatnet}}->{$channel} = \%buf;
my $tag = $server->{chatnet} . "_$channel";
Irssi::timeout_remove($buffer_tags{$tag});
$buffer_tags{$tag} = Irssi::timeout_add_once(1000 + int(rand(250) * 3),
"set_modes",
[$server->{chatnet},
$channel]);
#print Dumper %buffer;
}
}
sub event_kick
{
my($server, $channel, $nick, $kicker, $address, $reason) = @_;
my $n = $server->channel_find($channel)->nick_find($nick);
#print Dumper $n;
my $mask = make_mask($n->{host});
remove_all($server->{chatnet}, $channel, $mask) if $mask;
}
sub check_ignore
{
my($server, $channel) = @_;
my $chatnet = $server->{chatnet};
my $ignore = Irssi::settings_get_str("automode_ignore") . " ";
return ($ignore =~ /$chatnet:$channel /i) ? 1 : 0;
}
# I don't think this does what I want it to do.
sub event_exit
{
save_list;
}
Irssi::signal_add("gui exit", "event_exit");
Irssi::signal_add("message kick", "event_kick");
Irssi::signal_add("message join", "event_join");
Irssi::signal_add("nick mode changed", "event_mode");
Irssi::settings_add_str("automode", "automode_ignore", "IM:&bitlbee");