use strict; use Irssi; use POSIX; use Socket; use vars qw($VERSION %IRSSI); $VERSION = "3.7"; %IRSSI = ( authors => 'Toni Salomäki', name => 'autoopper', contact => 'Toni@IRCNet', description => 'Auto-op script with dynamic address support and random delay', license => 'GNU GPLv2 or later', url => 'http://vinku.dyndns.org/irssi_scripts/' ); # This is a script to auto-op people on a certain channel (all or, represented with *). # Users are auto-opped on join with random delay. # There is a possibility to use dns aliases (for example dyndns.org) for getting the correct address. # The auto-op list is stored into ~/.irssi/autoop # # To get the dynamic addresses to be refreshed automatically, set value to autoop_dynamic_refresh (in hours) # The value will be used next time the script is loaded (at startup or manual load) # # NOTICE: the datafile is in completely different format than in 1.0 and this version cannot read it. Sorry. # # COMMANDS: # # autoop_show - Displays list of auto-opped hostmasks & channels # The current address of dynamic host is displayed in parenthesis # # autoop_add - Add new auto-op. Parameters hostmask, channel (or *) and dynamic flag # # Dynamic flag has 3 different values: # 0: treat host as a static ip # 1: treat host as an alias for dynamic ip # 2: treat host as an alias for dynamic ip, but do not resolve the ip (not normally needed) # # autoop_del - Remove auto-op # # autoop_save - Save auto-ops to file (done normally automatically) # # autoop_load - Load auto-ops from file (use this if you have edited the autoop -file manually) # # autoop_check - Check all channels and op people needed # # autoop_dynamic - Refresh dynamic addresses (automatically if parameter set) # # Data is stored in ~/.irssi/autoop # format: host channels flag # channels separated with comma # one host per line my (%oplist); my (@opitems); srand(); #resolve dynamic host sub resolve_host { my ($host, $dyntype) = @_; if (my $iaddr = inet_aton($host)) { if ($dyntype ne "2") { if (my $newhost = gethostbyaddr($iaddr, AF_INET)) { return $newhost; } else { return inet_ntoa($iaddr); } } else { return inet_ntoa($iaddr); } } return "error"; } # return list of dynamic hosts with real addresses sub fetch_dynamic_hosts { my %hostcache; my $resultext; foreach my $item (@opitems) { next if ($item->{dynamic} ne "1" && $item->{dynamic} ne "2"); my (undef, $host) = split(/\@/, $item->{mask}, 2); # fetch the host's real address (if not cached) unless ($hostcache{$host}) { $hostcache{$host} = resolve_host($host, $item->{dynamic}); $resultext .= $host . "\t" . $hostcache{$host} . "\n"; } } chomp $resultext; return $resultext; } # fetch real addresses for dynamic hosts sub cmd_change_dynamic_hosts { pipe READ, WRITE; my $pid = fork(); unless (defined($pid)) { Irssi::print("Can't fork - aborting"); return; } if ($pid > 0) { # the original process, just add a listener for pipe close (WRITE); Irssi::pidwait_add($pid); my $target = {fh => \*READ, tag => undef}; $target->{tag} = Irssi::input_add(fileno(READ), INPUT_READ, \&read_dynamic_hosts, $target); } else { # the new process, fetch addresses and write to the pipe print WRITE fetch_dynamic_hosts; close (READ); close (WRITE); POSIX::_exit(1); } } # get dynamic hosts from pipe and change them to users sub read_dynamic_hosts { my $target = shift; my $rh = $target->{fh}; my %hostcache; while (<$rh>) { chomp; my ($dynhost, $realhost, undef) = split (/\t/, $_, 3); $hostcache{$dynhost} = $realhost; } close($target->{fh}); Irssi::input_remove($target->{tag}); my $mask; my $count = 0; undef %oplist if (%oplist); foreach my $item (@opitems) { if ($item->{dynamic} eq "1" || $item->{dynamic} eq "2") { my ($user, $host) = split(/\@/, $item->{mask}, 2); $count++ if ($item->{dynmask} ne $hostcache{$host}); $item->{dynmask} = $hostcache{$host}; $mask = $user . "\@" . $hostcache{$host}; } else { $mask = $item->{mask}; } foreach my $channel (split (/,/,$item->{chan})) { $oplist{$channel} .= "$mask "; } } chop %oplist; Irssi::print("$count dynamic hosts changed") if ($count > 0); } # Save data to file sub cmd_save_autoop { my $file = Irssi::get_irssi_dir."/autoop"; open FILE, ">", "$file" or return; foreach my $item (@opitems) { printf FILE ("%s\t%s\t%s\n", $item->{mask}, $item->{chan}, $item->{dynamic}); } close FILE; Irssi::print("Auto-op list saved to $file"); } # Load data from file sub cmd_load_autoop { my $file = Irssi::get_irssi_dir."/autoop"; open FILE, "<","$file" or return; undef @opitems if (@opitems); while () { chomp; my ($mask, $chan, $dynamic, undef) = split (/\t/, $_, 4); my $item = {mask=>$mask, chan=>$chan, dynamic=>$dynamic, dynmask=>undef}; push (@opitems, $item); } close FILE; Irssi::print("Auto-op list reloaded from $file"); cmd_change_dynamic_hosts; } # Show who's being auto-opped sub cmd_show_autoop { my %list; foreach my $item (@opitems) { foreach my $channel (split (/,/,$item->{chan})) { $list{$channel} .= "\n" . $item->{mask}; $list{$channel} .= " (" . $item->{dynmask} . ")" if ($item->{dynmask}); } } Irssi::print("All channels:" . $list{"*"}) if (exists $list{"*"}); delete $list{"*"}; #this is already printed, so remove it foreach my $channel (sort (keys %list)) { Irssi::print("$channel:" . $list{$channel}); } } # Add new auto-op sub cmd_add_autoop { my ($data) = @_; my ($mask, $chan, $dynamic, undef) = split(" ", $data, 4); my $found = 0; if ($chan eq "" || $mask eq "" || !($mask =~ /.+!.+@.+/)) { Irssi::print("Invalid hostmask. It must contain both ! and @.") if (!($mask =~ /.+!.+@.+/)); Irssi::print("Usage: /autoop_add <*|#channel> [dynflag]"); Irssi::print("Dynflag: 0 normal, 1 dynamic, 2 dynamic without resolving"); return; } foreach my $item (@opitems) { next unless ($item->{mask} eq $mask); $found = 1; $item->{chan} .= ",$chan"; last; } if ($found == 0) { $dynamic = "0" unless ($dynamic eq "1" || $dynamic eq "2"); my $item = {mask=>$mask, chan=>$chan, dynamic=>$dynamic, dynmask=>undef}; push (@opitems, $item); } $oplist{$chan} .= " $mask"; Irssi::print("Added auto-op: $chan: $mask"); } # Remove autoop sub cmd_del_autoop { my ($data) = @_; my ($mask, $channel, undef) = split(" ", $data, 3); if ($channel eq "" || $mask eq "") { Irssi::print("Usage: /autoop_del <*|#channel>"); return; } my $i=0; foreach my $item (@opitems) { if ($item->{mask} eq $mask) { if ($channel eq "*" || $item->{chan} eq $channel) { splice @opitems, $i, 1; Irssi::print("Removed: $mask"); } else { my $newchan; foreach my $currchan (split (/,/,$item->{chan})) { if ($channel eq $currchan) { Irssi::print("Removed: $channel from $mask"); } else { $newchan .= $currchan . ","; } } chop $newchan; Irssi::print("Couldn't remove $channel from $mask") if ($item->{chan} eq $newchan); $item->{chan} = $newchan; } last; } $i++; } } # Do the actual opping sub do_autoop { my $target = shift; Irssi::timeout_remove($target->{tag}); # nick has to be fetched again, because $target->{nick}->{op} is not updated my $nick = $target->{chan}->nick_find($target->{nick}->{nick}); # if nick is changed during delay, it will probably be lost here... if ($nick->{nick} ne "") { if ($nick->{host} eq $target->{nick}->{host}) { $target->{chan}->command("op " . $nick->{nick}) unless ($nick->{op}); } else { Irssi::print("Host changed for nick during delay: " . $nick->{nick}); } } undef $target; } # Someone joined, might be multiple person. Check if opping is needed sub event_massjoin { my ($channel, $nicklist) = @_; my @nicks = @{$nicklist}; return if (!$channel->{chanop}); my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}}; foreach my $nick (@nicks) { my $host = $nick->{host}; $host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident) next unless ($channel->{server}->masks_match($masks, $nick->{nick}, $host)); my $min_delay = Irssi::settings_get_int("autoop_min_delay"); my $max_delay = Irssi::settings_get_int("autoop_max_delay") - $min_delay; my $delay = int(rand($max_delay)) + $min_delay; my $target = {nick => $nick, chan => $channel, tag => undef}; $target->{tag} = Irssi::timeout_add($delay, 'do_autoop', $target); } } # Check channel op status sub do_channel_check { my $target = shift; Irssi::timeout_remove($target->{tag}); my $channel = $target->{chan}; my $server = $channel->{server}; my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}}; my $nicks = ""; foreach my $nick ($channel->nicks()) { next if ($nick->{op}); my $host = $nick->{host}; $host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident) if ($server->masks_match($masks, $nick->{nick}, $host)) { $nicks = $nicks . " " . $nick->{nick}; } } $channel->command("op" . $nicks) unless ($nicks eq ""); undef $target; } #check people needing opping after getting ops sub event_nickmodechange { my ($channel, $nick, $setby, $mode, $type) = @_; return unless (($mode eq '@') && ($type eq '+')); my $server = $channel->{server}; return unless ($server->{nick} eq $nick->{nick}); my $min_delay = Irssi::settings_get_int("autoop_min_delay"); my $max_delay = Irssi::settings_get_int("autoop_max_delay") - $min_delay; my $delay = int(rand($max_delay)) + $min_delay; my $target = {chan => $channel, tag => undef}; $target->{tag} = Irssi::timeout_add($delay, 'do_channel_check', $target); } #Check all channels / all users if someone needs to be opped sub cmd_autoop_check { my ($data, $server, $witem) = @_; foreach my $channel ($server->channels()) { Irssi::print("Checking: " . $channel->{name}); next if (!$channel->{chanop}); my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}}; foreach my $nick ($channel->nicks()) { next if ($nick->{op}); my $host = $nick->{host}; $host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident) if ($server->masks_match($masks, $nick->{nick}, $host)) { $channel->command("op " . $nick->{nick}) if (!$nick->{op}); } } } } #Set dynamic refresh period. sub set_dynamic_refresh { my $refresh = Irssi::settings_get_int("autoop_dynamic_refresh"); return if ($refresh == 0); Irssi::print("Dynamic host refresh set for $refresh hours"); Irssi::timeout_add($refresh*3600000, 'cmd_change_dynamic_hosts', undef); } Irssi::command_bind('autoop_show', 'cmd_show_autoop'); Irssi::command_bind('autoop_add', 'cmd_add_autoop'); Irssi::command_bind('autoop_del', 'cmd_del_autoop'); Irssi::command_bind('autoop_save', 'cmd_save_autoop'); Irssi::command_bind('autoop_load', 'cmd_load_autoop'); Irssi::command_bind('autoop_check', 'cmd_autoop_check'); Irssi::command_bind('autoop_dynamic', 'cmd_change_dynamic_hosts'); Irssi::signal_add_last('massjoin', 'event_massjoin'); Irssi::signal_add_last('setup saved', 'cmd_save_autoop'); Irssi::signal_add_last('setup reread', 'cmd_load_autoop'); Irssi::signal_add_last("nick mode changed", "event_nickmodechange"); Irssi::settings_add_int('autoop', 'autoop_max_delay', 15000); Irssi::settings_add_int('autoop', 'autoop_min_delay', 1000); Irssi::settings_add_int('autoop', 'autoop_dynamic_refresh', 0); cmd_load_autoop; set_dynamic_refresh;