msparks / irssiscripts
- Source
- Commits
- Network (1)
- Issues (0)
- Downloads (0)
- Wiki (1)
- Graphs
-
Branch:
master
irssiscripts / automode.pl
| 1e6d50bf » | msparks | 2009-01-23 | 1 | # automode.pl | |
| 2 | # | ||||
| 3 | # Passively learn and actively maintain the ops/voices/halfops in channels. | ||||
| 4 | # This is a no-maintenance auto-op/auto-voice script for irssi. | ||||
| 5 | # | ||||
| 6 | # INSTALL: | ||||
| 7 | # 1) /script load automode.pl | ||||
| 8 | # 2) Be a channel operator | ||||
| 9 | # | ||||
| 10 | # HOW IT WORKS: | ||||
| 11 | # When someone joins a channel and is given ops/voice/halfop, the script | ||||
| 12 | # will record that user's mask, as a combination of their nickname and part | ||||
| 13 | # of their hostname or IP address. When that person leaves and rejoins, the | ||||
| 14 | # script will check against its database and regrant the user the modes | ||||
| 15 | # he or she had before leaving. | ||||
| 16 | # | ||||
| 17 | # If a user is kicked from a channel, all modes for that person are removed. | ||||
| 18 | # They must therefore be regiven by another operator manually. Note this. | ||||
| 19 | # | ||||
| 20 | # Also, this script relies on the "chatnet" attribute being set for a | ||||
| 21 | # particular connection. Use /network (or /ircnet) to set up your networks | ||||
| 22 | # and such. This script will spit out (lots of) warnings if the chatnet is | ||||
| 23 | # not set. | ||||
| 24 | # | ||||
| 25 | # IGNORING CHANNELS: | ||||
| 26 | # If you do not wish to maintain modes on a channel, add it to the setting | ||||
| 27 | # "automode_ignore" in the form <tag>:<channel>, separated by spaces. | ||||
| 28 | # | ||||
| 29 | # For example: /set automode_ignore FreeNode:#perl EFnet:#irssi | ||||
| 30 | # (should) not maintain modes in #perl on FreeNode or #irssi | ||||
| 31 | # on EFnet, provided FreeNode and EFnet are the tags for those | ||||
| 32 | # connections. | ||||
| 33 | # | ||||
| 34 | # NOTES: | ||||
| 35 | # The Perl module Data::Serializer is needed for this script. | ||||
| 36 | # The database file is not written instantaneously; it is on a timer and is | ||||
| 37 | # written to every five minutes or so. If the script is reloaded before it has | ||||
| 38 | # had a chance to save will result in forgotten modes. | ||||
| 39 | # | ||||
| 40 | use strict; | ||||
| 41 | use Irssi; | ||||
| 42 | use Data::Serializer; | ||||
| 43 | use Data::Dumper; | ||||
| 44 | |||||
| 45 | use vars qw($VERSION %IRSSI); | ||||
| 46 | |||||
| 47 | $VERSION = '1.3'; | ||||
| 48 | %IRSSI = ( | ||||
| 49 | authors => 'Matt "f0rked" Sparks', | ||||
| 50 | contact => 'ms+irssi@quadpoint.org', | ||||
| 51 | name => 'automode', | ||||
| 52 | description => 'Mode maintainer', | ||||
| 53 | license => 'BSD', | ||||
| 54 | url => 'http://quadpoint.org', | ||||
| 55 | changed => '2008-06-14', | ||||
| 56 | ); | ||||
| 57 | |||||
| 58 | # show debug lines | ||||
| 59 | my $debug = 0; | ||||
| 60 | |||||
| 61 | my $s = new Data::Serializer; | ||||
| 62 | my $file = Irssi::get_irssi_dir."/automode_list"; | ||||
| 63 | |||||
| 64 | if (!-e $file) { | ||||
| 65 | print "[automode] creating $file"; | ||||
| 66 | system("touch $file"); | ||||
| 67 | } | ||||
| 68 | |||||
| 69 | my $listref = $s->retrieve($file); | ||||
| 70 | my %list = $listref ? %{$listref} : (); | ||||
| 71 | |||||
| 72 | #print Dumper %list; | ||||
| 73 | |||||
| 74 | my $save_tag; | ||||
| 75 | my %buffer_tags; | ||||
| 76 | my %buffer; | ||||
| 77 | |||||
| 78 | |||||
| 79 | sub save_list | ||||
| 80 | { | ||||
| 81 | $s->store(\%list,$file); | ||||
| 82 | } | ||||
| 83 | |||||
| 84 | |||||
| 85 | sub clear_list | ||||
| 86 | { | ||||
| 87 | %list = (); | ||||
| 88 | } | ||||
| 89 | |||||
| 90 | |||||
| 91 | sub make_mask | ||||
| 92 | { | ||||
| 93 | my($address) = @_; | ||||
| 94 | return if !$address; | ||||
| 95 | my($ident, $host) = split /@/, $address; | ||||
| 96 | my @split = split /\./, $host; | ||||
| 97 | |||||
| 98 | if (@split <= 2) { | ||||
| 99 | # host is something like "foo.com". We cannot make the mask *.com. | ||||
| 100 | } else { | ||||
| 101 | if ($split[$#split] =~ /^\d+$/) { | ||||
| 102 | # Looks like an IP address. | ||||
| 103 | pop @split; | ||||
| 104 | $host = join(".", @split) . ".\d{1,3}"; | ||||
| 105 | } else { | ||||
| 106 | # Mask the first segment. | ||||
| 107 | shift @split; | ||||
| 108 | $host = ".+?." . join(".", @split); | ||||
| 109 | } | ||||
| 110 | } | ||||
| 111 | |||||
| 112 | return ".+?!.*${ident}@" . "${host}"; | ||||
| 113 | } | ||||
| 114 | |||||
| 115 | |||||
| 116 | sub show | ||||
| 117 | { | ||||
| 118 | my($net, $channel) = @_; | ||||
| 119 | print Dumper %{$list{$net}->{$channel}}; | ||||
| 120 | } | ||||
| 121 | |||||
| 122 | |||||
| 123 | sub show_all | ||||
| 124 | { | ||||
| 125 | my $list; | ||||
| 126 | print Dumper %list; | ||||
| 127 | } | ||||
| 128 | |||||
| 129 | |||||
| 130 | sub clear_channel | ||||
| 131 | { | ||||
| 132 | my($net, $channel) = @_; | ||||
| 133 | delete $list{$net}->{$channel}; | ||||
| 134 | } | ||||
| 135 | |||||
| 136 | |||||
| 137 | sub set_modes | ||||
| 138 | { | ||||
| 139 | my($net, $channel) = @{$_[0]}; | ||||
| 140 | return if !$buffer{$net}->{$channel}; | ||||
| 141 | |||||
| 142 | my($nicks, $modes) = values(%{$buffer{$net}->{$channel}}); | ||||
| 143 | print "[automode] modes: $modes, nicks: $nicks" if $debug; | ||||
| 144 | my $c = Irssi::server_find_chatnet($net)->channel_find($channel); | ||||
| 145 | |||||
| 146 | # iterate through the modes and see which ones we don't have to set | ||||
| 147 | my($final_modes, $final_nicks); | ||||
| 148 | |||||
| 149 | my $i = 0; | ||||
| 150 | for (split //,$modes) { | ||||
| 151 | my $m = $_; | ||||
| 152 | my $n = (split / /, $nicks)[$i]; | ||||
| 153 | $i++; | ||||
| 154 | |||||
| 155 | next if (!$c->nick_find($n)); | ||||
| 156 | next if ($m eq "o" && $c->nick_find($n)->{"op"}); | ||||
| 157 | next if ($m eq "v" && $c->nick_find($n)->{"voice"}); | ||||
| 158 | next if ($m eq "h" && $c->nick_find($n)->{"halfop"}); | ||||
| 159 | |||||
| 160 | # if we made it this far, add this to the final modes | ||||
| 161 | $final_modes .= $m; | ||||
| 162 | $final_nicks .= "$n "; | ||||
| 163 | } | ||||
| 164 | |||||
| 165 | print "[automode] final modes: +$final_modes $final_nicks" if $debug; | ||||
| 166 | |||||
| 167 | $c->command("MODE $channel +$final_modes $final_nicks") | ||||
| 168 | if ($final_modes && $final_nicks); | ||||
| 169 | delete $buffer{$net}->{$channel}; | ||||
| 170 | } | ||||
| 171 | |||||
| 172 | |||||
| 173 | sub mode2letter | ||||
| 174 | { | ||||
| 175 | my($mode) = @_; | ||||
| 176 | if ($mode eq "@") { | ||||
| 177 | return "o"; | ||||
| 178 | } elsif ($mode eq "+") { | ||||
| 179 | return "v"; | ||||
| 180 | } elsif ($mode eq "%") { | ||||
| 181 | return "h"; | ||||
| 182 | } | ||||
| 183 | return -1; | ||||
| 184 | } | ||||
| 185 | |||||
| 186 | |||||
| 187 | sub remove_mode | ||||
| 188 | { | ||||
| 189 | my($net, $channel, $mask, $mode) = @_; | ||||
| 190 | my $letter = mode2letter($mode); | ||||
| 191 | $list{$net}->{$channel}->{$mask} =~ s/$letter// | ||||
| 192 | if user_modes($net, $channel, $mask); | ||||
| 193 | delete $list{$net}->{$channel}->{$mask} | ||||
| 194 | if exists $list{$net}->{$channel}->{$mask} | ||||
| 195 | and !$list{$net}->{$channel}->{$mask}; | ||||
| 196 | } | ||||
| 197 | |||||
| 198 | |||||
| 199 | sub remove_all | ||||
| 200 | { | ||||
| 201 | my($net, $channel, $mask) = @_; | ||||
| 202 | delete $list{$net}->{$channel}->{$mask} | ||||
| 203 | if exists $list{$net}->{$channel}->{$mask}; | ||||
| 204 | } | ||||
| 205 | |||||
| 206 | |||||
| 207 | sub user_modes | ||||
| 208 | { | ||||
| 209 | my($net, $channel, $mask) = @_; | ||||
| 210 | return $list{$net}->{$channel}->{$mask}; | ||||
| 211 | } | ||||
| 212 | |||||
| 213 | |||||
| 214 | sub add_mode | ||||
| 215 | { | ||||
| 216 | my($net, $channel, $mask, $mode) = @_; | ||||
| 217 | return if !$mask or !$net or !$channel or !$mode; | ||||
| 218 | |||||
| 219 | my $letter = mode2letter($mode); | ||||
| 220 | $list{$net}->{$channel}->{$mask} .= $letter | ||||
| 221 | if $list{$net}->{$channel}->{$mask} !~ /$letter/; | ||||
| 222 | |||||
| 223 | Irssi::timeout_remove($save_tag); | ||||
| 224 | $save_tag = Irssi::timeout_add_once(300, "save_list", []); | ||||
| 225 | } | ||||
| 226 | |||||
| 227 | |||||
| 228 | sub event_mode | ||||
| 229 | { | ||||
| 230 | my($channel, $nick, $setby, $mode, $type) = @_; | ||||
| 231 | return if check_ignore($channel->{server}, $channel->{name}); | ||||
| 232 | my $w = Irssi::active_win; | ||||
| 233 | return if $mode != '@' and $mode != '%' and $mode != '+'; | ||||
| 234 | |||||
| 235 | my $chatnet = $channel->{server}->{chatnet}; | ||||
| 236 | my $tag = $channel->{server}->{tag}; | ||||
| 237 | print ("[automode] The 'chatnet' attribute is missing for the tag '$tag'. " . | ||||
| 238 | "Use /network (or /ircnet) to properly manage this.") if !$chatnet; | ||||
| 239 | return if !$chatnet; | ||||
| 240 | |||||
| 241 | my $mask = make_mask($nick->{host}); | ||||
| 242 | print "[automode] failed to make mask ($mask)" if (!$mask && $debug); | ||||
| 243 | return if !$mask; | ||||
| 244 | |||||
| 245 | if ($type eq "+") { | ||||
| 246 | print ("[automode] adding mode '$mode' for $mask in $channel->{name} on " . | ||||
| 247 | $chatnet) if $debug; | ||||
| 248 | add_mode($chatnet, $channel->{name}, $mask, $mode); | ||||
| 249 | } else { | ||||
| 250 | # don't remove op if they deop themselves. | ||||
| 251 | return if $setby eq $nick->{nick}; | ||||
| 252 | print ("[automode] removing mode '$mode' for $mask in $channel->{name} " . | ||||
| 253 | " on $chatnet") if $debug; | ||||
| 254 | remove_mode($chatnet, $channel->{name}, $mask, $mode); | ||||
| 255 | } | ||||
| 256 | |||||
| 257 | #show($chatnet, $channel->{name}); | ||||
| 258 | } | ||||
| 259 | |||||
| 260 | |||||
| 261 | sub event_join | ||||
| 262 | { | ||||
| 263 | my($server, $channel, $nick, $address) = @_; | ||||
| 264 | return if check_ignore($server, $channel); | ||||
| 265 | my $mask = make_mask($address); | ||||
| 266 | return if !user_modes($server->{chatnet}, $channel, $mask); | ||||
| 267 | my $c = $server->channel_find($channel); | ||||
| 268 | return if not $c->{chanop}; | ||||
| 269 | |||||
| 270 | if (my $modes = user_modes($server->{chatnet}, $channel, $mask)) { | ||||
| 271 | print "[automode] Matched mask ($mask) with modes: $modes" if $debug; | ||||
| 272 | my $nick_list = "$nick " x length($modes); | ||||
| 273 | my %buf = ($buffer{$server->{chatnet}}->{$channel} ? | ||||
| 274 | %{$buffer{$server->{chatnet}}->{$channel}} : ()); | ||||
| 275 | $buf{modes} .= $modes; | ||||
| 276 | $buf{nicks} .= $nick_list; | ||||
| 277 | $buffer{$server->{chatnet}}->{$channel} = \%buf; | ||||
| 278 | my $tag = $server->{chatnet} . "_$channel"; | ||||
| 279 | Irssi::timeout_remove($buffer_tags{$tag}); | ||||
| 280 | $buffer_tags{$tag} = Irssi::timeout_add_once(1000 + int(rand(250) * 3), | ||||
| 281 | "set_modes", | ||||
| 282 | [$server->{chatnet}, | ||||
| 283 | $channel]); | ||||
| 284 | #print Dumper %buffer; | ||||
| 285 | } | ||||
| 286 | } | ||||
| 287 | |||||
| 288 | |||||
| 289 | sub event_kick | ||||
| 290 | { | ||||
| 291 | my($server, $channel, $nick, $kicker, $address, $reason) = @_; | ||||
| 292 | my $n = $server->channel_find($channel)->nick_find($nick); | ||||
| 293 | #print Dumper $n; | ||||
| 294 | my $mask = make_mask($n->{host}); | ||||
| 295 | remove_all($server->{chatnet}, $channel, $mask) if $mask; | ||||
| 296 | } | ||||
| 297 | |||||
| 298 | |||||
| 299 | sub check_ignore | ||||
| 300 | { | ||||
| 301 | my($server, $channel) = @_; | ||||
| 302 | my $chatnet = $server->{chatnet}; | ||||
| 303 | my $ignore = Irssi::settings_get_str("automode_ignore") . " "; | ||||
| 304 | return ($ignore =~ /$chatnet:$channel /i) ? 1 : 0; | ||||
| 305 | } | ||||
| 306 | |||||
| 307 | |||||
| 308 | # I don't think this does what I want it to do. | ||||
| 309 | sub event_exit | ||||
| 310 | { | ||||
| 311 | save_list; | ||||
| 312 | } | ||||
| 313 | |||||
| 314 | |||||
| 315 | Irssi::signal_add("gui exit", "event_exit"); | ||||
| 316 | |||||
| 317 | Irssi::signal_add("message kick", "event_kick"); | ||||
| 318 | Irssi::signal_add("message join", "event_join"); | ||||
| 319 | Irssi::signal_add("nick mode changed", "event_mode"); | ||||
| 320 | |||||
| 321 | Irssi::settings_add_str("automode", "automode_ignore", "IM:&bitlbee"); | ||||
