Last active
August 29, 2015 14:13
-
-
Save mniip/66a16b6da65d6deda9bc to your computer and use it in GitHub Desktop.
A freenode-specific irssi script that checks whether someone is banned on some channel. For hexchat version see https://gist.github.com/mniip/441aacc9f7f571b8ea25
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use Irssi; | |
use strict; | |
use bignum; | |
use Socket; | |
use vars qw($VERSION %IRSSI); | |
%IRSSI = | |
( | |
name => "isbanned", | |
description => "freenode-specific module that checks whether someone is banned on some channel", | |
commands => "isbanned, ismuted, islisted, isreset", | |
authors => "mniip" | |
); | |
$VERSION = "0.6-pl"; | |
# Commands: | |
# /isbanned <channel> <user> | |
# Check whether <user> is banned on <channel> | |
# /ismuted <channel> <user> | |
# Check whether <user> is muted on <channel> | |
# /islisted <channel> <mode> <user> | |
# Check whether <user> is listed in <channel>'s | |
# <mode> list (can be b, q, e, or I) | |
# /isreset | |
# If something screws up, this resets the state to inactive | |
# | |
# <user> can either be a nickname, or a hostmask in the form | |
# nick!ident@host#gecos$account | |
# where some parts can be omitted. Strictly speaking the form is | |
# [nick] ['!' ident] ['@' host] ['#' gecos] ['$' account] | |
# If any part is omitted, it is assumed to be empty string, except for | |
# account: if the account part is omitted the user is assumed to be | |
# unidentified (as opposed to identified as empty string) | |
my $active = 0; | |
my $user; | |
my $channel; | |
my $orig_list; | |
my $modes; | |
my @whois; | |
my $lists_left; | |
my @bans; | |
sub parse_ipv6_word | |
{ | |
my ($w) = @_; | |
return hex $w if $w =~ /^0*[0-9a-fA-F]{1,4}$/; | |
die "Invalid IPv6 word"; | |
} | |
sub parse_ip | |
{ | |
my ($ip, $strict) = @_; | |
if($ip =~ /:/) | |
{ | |
if($ip =~ /::/) | |
{ | |
my $edge = ($ip =~ /::$/) || ($ip =~ /^::/); | |
$ip = $ip . "0" if $ip =~ /::$/; | |
$ip = "0" . $ip if $ip =~ /^::/; | |
my ($head, $tail) = split /::/, $ip, 2; | |
my @headwords = split /:/, $head, 8; | |
my @tailwords = split /:/, $tail, 8; | |
if(@headwords + @tailwords <= ($edge ? 8 : 7)) | |
{ | |
my $result; | |
eval | |
{ | |
@headwords = map { parse_ipv6_word($_) } @headwords; | |
@tailwords = map { parse_ipv6_word($_) } @tailwords; | |
my @words = (@headwords, (0) x (8 - @headwords - @tailwords), @tailwords); | |
$result = 0; | |
for(my $i = 0; $i < 8; $i++) | |
{ | |
$result |= $words[$i] << ((7 - $i) * 16); | |
} | |
}; | |
return $result if defined $result; | |
} | |
} | |
else | |
{ | |
my @words = split /:/, $ip, 8; | |
if(scalar @words == 8) | |
{ | |
my $result; | |
eval | |
{ | |
@words = map { parse_ipv6_word($_) } @words; | |
$result = 0; | |
for(my $i = 0; $i < 8; $i++) | |
{ | |
$result |= $words[$i] << ((7 - $i) * 16); | |
} | |
}; | |
return $result if defined $result; | |
} | |
} | |
die "Invalid IPv6" if $strict; | |
return 0; | |
} | |
else | |
{ | |
if($ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) | |
{ | |
my ($o1, $o2, $o3, $o4) = (0 + $1, 0 + $2, 0 + $3, 0 + $4); | |
return $o1 << 24 | $o2 << 16 | $o3 << 8 | $o4 | |
if $o1 < 256 && $o2 < 256 && $o3 < 256 && $o4 < 256; | |
} | |
die "Invalid IPv4" if $strict; | |
return 0; | |
} | |
} | |
my %char_classes = | |
( | |
"[" => "[\[{]", | |
"{" => "[{\[]", | |
"|" => "[|\\]", | |
"\\" => "[\\|]", | |
"]" => "[\]}]", | |
"}" => "[}\]]", | |
"~" => "[~^]", | |
"?" => "." | |
); | |
$char_classes{$_} = $_ foreach split //, '-_`^0123456789'; | |
for(my $c = 0; $c < 25; $c++) | |
{ | |
my $lc = chr($c + ord "a"); | |
my $uc = chr($c + ord "A"); | |
$char_classes{$lc} = "[$lc$uc]"; | |
$char_classes{$uc} = "[$uc$lc]"; | |
} | |
sub match_pattern | |
{ | |
my ($string, $pattern) = @_; | |
$pattern =~ s|[?*]+|"?" x ($& =~ tr/?/?/) . ($& =~ /\*/ ? "*" : "")|ge; | |
my $last_pos = 0; | |
my @pieces = split /\*/, $pattern; | |
push @pieces, "" if $pattern =~ /\*$/; | |
push @pieces, "" if $pattern eq "*"; | |
for(my $i = 0; $i < scalar @pieces; $i++) | |
{ | |
my $regex = ""; | |
$regex .= "^" if !$i; | |
$regex .= defined $char_classes{$_} ? $char_classes{$_} : "\\$_" for split //, $pieces[$i]; | |
$regex .= "\$" if $i == $#pieces; | |
if((substr $string, $last_pos) =~ qr/$regex/) | |
{ | |
$last_pos += $+[0]; | |
} | |
else | |
{ | |
return 0; | |
} | |
} | |
return 1; | |
} | |
my @found_modes; | |
sub add_ban | |
{ | |
push @found_modes, "\x0302$_[1] $_[0]\x0F in \x0306$_[2]\x0F set by \x0310$_[3]\x0F on \x0308" . (scalar localtime $_[4]) . "\x0F"; | |
} | |
sub analyze | |
{ | |
$active = 0; | |
my ($nick, $ident, $host, $gecos, $account, $ssl) = @whois; | |
@found_modes = (); | |
my @hostile_modes = (); | |
for my $c(split //, $modes) | |
{ | |
push @hostile_modes, $c if $orig_list eq "b" && ($c eq "i" || ($c eq "r" && !$account) || ($c eq "S" && !$ssl)); | |
push @hostile_modes, $c if $orig_list eq "q" && ($c eq "m" || ($c eq "r" && !$account)); | |
} | |
push @found_modes, ("\x0302+" . join("", @hostile_modes) . "\x0F in \x0306$channel\x0F") if(@hostile_modes); | |
for my $b(@bans) | |
{ | |
my ($ban) = split /(?<!^)\$/, $b->[0], 2; | |
if($ban =~ /^\$/) | |
{ | |
add_ban(@$b) if $ban eq '$a' && $account; | |
if($ban =~ /^\$a:(.*)$/) | |
{ | |
add_ban(@$b) if nick_eq($account, $1); | |
} | |
add_ban(@$b) if $ban eq '$~a' && !$account; | |
if($ban =~ /^\$~a:(.*)$/) | |
{ | |
add_ban(@$b) if !nick_eq($account, $1); | |
} | |
add_ban(@$b) if $ban =~ /^\$~j:/; | |
if($ban =~ /^\$r:(.*)$/) | |
{ | |
add_ban(@$b) if match_pattern($gecos, $1); | |
} | |
if($ban =~ /^\$~r:(.*)$/) | |
{ | |
add_ban(@$b) if !match_pattern($gecos, $1); | |
} | |
if($ban =~ /^\$x:(.*)$/) | |
{ | |
for my $h(@$host) | |
{ | |
if(match_pattern("$nick!$ident\@$h#$gecos", $1)) | |
{ | |
add_ban(@$b); | |
last; | |
} | |
} | |
} | |
if($ban =~ /^\$~x:(.*)$/) | |
{ | |
my $found = 0; | |
for my $h(@$host) | |
{ | |
if(match_pattern("$nick!$ident\@$h#$gecos", $1)) | |
{ | |
$found = 1; | |
last; | |
} | |
} | |
add_ban(@$b) if !$found; | |
} | |
add_ban(@$b) if $ban eq '$z' && $ssl; | |
add_ban(@$b) if $ban eq '$~z' && !$ssl; | |
} | |
else | |
{ | |
my ($v, $bhost) = split /@/, $ban, 2; | |
my ($bnick, $bident) = split /!/, $v, 2; | |
if(match_pattern($nick, $bnick) && match_pattern($ident, $bident)) | |
{ | |
my $found = 0; | |
for my $h(@$host) | |
{ | |
if(match_pattern($h, $bhost)) | |
{ | |
$found = 1; | |
add_ban(@$b); | |
last; | |
} | |
} | |
if(!$found) | |
{ | |
my ($ip, $width) = split /\//, $bhost, 2; | |
if($width =~ /^[0-9]+$/) | |
{ | |
my $is_v4 = !($ip =~ /:/); | |
$width = ($is_v4 ? 32 : 128) - $width; | |
$width = 0 if $width < 0; | |
$ip = parse_ip($ip); | |
for my $h(@$host) | |
{ | |
if(!($h =~ /:/) == $is_v4) | |
{ | |
eval | |
{ | |
my $h = parse_ip($h, 1); | |
if(($ip >> $width) == ($h >> $width)) | |
{ | |
add_ban(@$b); | |
last; | |
} | |
} | |
} | |
} | |
} | |
} | |
} # omg so many } | |
} | |
} | |
if(@found_modes) | |
{ | |
if($orig_list eq "b") | |
{ | |
Irssi::print("The following are preventing \x0310$user\x0F from joining \x0306$channel\x0F:"); | |
} | |
elsif($orig_list eq "q") | |
{ | |
Irssi::print("The following are preventing \x0310$user\x0F from speaking in \x0306$channel\x0F:"); | |
} | |
else | |
{ | |
Irssi::print("The following \x0302+$orig_list\x0F modes affect \x0310$user\x0F in \x0306$channel\x0F:"); | |
} | |
Irssi::print($_) for(@found_modes); | |
} | |
else | |
{ | |
if($orig_list eq "b") | |
{ | |
Irssi::print("Nothing is preventing \x0310$user\x0F from joining \x0306$channel\x0F"); | |
} | |
elsif($orig_list eq "q") | |
{ | |
Irssi::print("Nothing is preventing \x0310$user\x0F from speaking in \x0306$channel\x0F"); | |
} | |
else | |
{ | |
Irssi::print("No \x0302+$orig_list\x0F modes affect \x0310$user\x0F in \x0306$channel\x0F"); | |
} | |
} | |
} | |
sub reset | |
{ | |
$active = 0; | |
} | |
sub lookup_host | |
{ | |
my ($host) = @_; | |
Irssi::print("\x0302Resolving <$host>"); | |
my @addresses = gethostbyname($host); | |
if(@addresses) | |
{ | |
@addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses]; | |
my %seen; | |
@addresses = grep { !$seen{$_}++ } (@addresses, $host); | |
Irssi::print("\x0302IPs: <@addresses>"); | |
return @addresses; | |
} | |
else | |
{ | |
Irssi::print("\x0302Found nothing, will use <$host>"); | |
return $host; | |
} | |
} | |
sub query_list | |
{ | |
$lists_left++; | |
my ($server, $channel, $mode) = @_; | |
$server->command("quote MODE $channel"); | |
$server->command("quote MODE $channel $mode"); | |
} | |
sub query_whois | |
{ | |
my ($server, $nick) = @_; | |
$server->command("quote WHOIS $nick"); | |
} | |
sub ignored | |
{ | |
Irssi::signal_stop() if $active; | |
} | |
sub nick_eq | |
{ | |
my ($n1, $n2) = @_; | |
$n1 = lc $n1; | |
$n1 =~ tr/[]\\/{}|/; | |
$n2 = lc $n2; | |
$n2 =~ tr/[]\\/{}|/; | |
return $n1 eq $n2; | |
} | |
sub modes | |
{ | |
if($active) | |
{ | |
my ($server, $data, $nick, $address) = @_; | |
my @w = split / /, $data; | |
Irssi::print("\x0302Channel $w[1] is +s, report may be incomplete") if $w[2] =~ /s/; | |
if(nick_eq($w[1], $channel)) | |
{ | |
$modes = $w[2]; | |
analyze() if !$lists_left && @whois; | |
} | |
Irssi::signal_stop(); | |
} | |
} | |
sub list_entry_b { list_entry("+b", @_); } | |
sub list_entry_q { list_entry("+q", @_); } | |
sub list_entry_I { list_entry("+I", @_); } | |
sub list_entry_e { list_entry("+e", @_); } | |
sub list_entry | |
{ | |
if($active) | |
{ | |
my ($u, $server, $data, $nick, $address) = @_; | |
my @w = split / /, $data; | |
splice @w, 2, 1 if $u eq "+q"; | |
if(nick_eq($w[1], $channel) && $w[2] =~ /^\$j:(.*)$/) | |
{ | |
query_list($server, $1 =~ s/\$.*$//r, "b") | |
} | |
else | |
{ | |
push @bans, [$w[2], $u, $w[1], $w[3], $w[4]]; | |
} | |
Irssi::signal_stop(); | |
} | |
} | |
sub list_end | |
{ | |
if($active) | |
{ | |
$lists_left--; | |
analyze() if !$lists_left && @whois && $modes; | |
Irssi::signal_stop(); | |
} | |
} | |
sub no_modes | |
{ | |
if($active) | |
{ | |
Irssi::print("\x0304Attempted to get modes for a nickname, did you put the arguments in the wrong order?"); | |
if(!$modes) | |
{ | |
$modes = "+"; | |
} | |
else | |
{ | |
$lists_left--; | |
analyze() if !$lists_left && @whois; | |
} | |
Irssi::signal_stop(); | |
} | |
} | |
sub mode_error | |
{ | |
if($active) | |
{ | |
Irssi::print("\x0304Something went wrong with the modes, report may be incomplete"); | |
if(!$modes) | |
{ | |
$modes = "+"; | |
analyze() if !$lists_left && @whois; | |
} | |
else | |
{ | |
$lists_left--; | |
analyze() if !$lists_left && @whois; | |
} | |
Irssi::signal_stop(); | |
} | |
} | |
sub no_list | |
{ | |
if($active) | |
{ | |
my ($server, $data, $nick, $address) = @_; | |
my @w = split / /, $data; | |
Irssi::print("\x0304Could not obtain modes for $w[1], report may be incomplete"); | |
if(nick_eq($w[1], $channel) && !$modes) | |
{ | |
$modes = "+"; | |
analyze() if !$lists_left && @whois; | |
} | |
else | |
{ | |
$lists_left--; | |
analyze() if !$lists_left && @whois && $modes; | |
} | |
Irssi::signal_stop(); | |
} | |
} | |
my @wh; | |
my $ac; | |
my $ssl; | |
sub whois_start | |
{ | |
if($active) | |
{ | |
my ($server, $data, $nick, $address) = @_; | |
my @w = split / /, $data; | |
@wh = ($w[1], $w[2], [lookup_host($w[3])], substr((join " ", @w[5 .. $#w]), 1)); | |
undef $ac; | |
undef $ssl; | |
Irssi::signal_stop(); | |
} | |
} | |
sub whois_ssl | |
{ | |
if($active) | |
{ | |
$ssl = 1; | |
Irssi::signal_stop(); | |
} | |
} | |
sub whois_account | |
{ | |
if($active) | |
{ | |
my ($server, $data, $nick, $address) = @_; | |
my @w = split / /, $data; | |
$ac = $w[2]; | |
Irssi::signal_stop(); | |
} | |
} | |
sub whois_end | |
{ | |
if($active) | |
{ | |
my ($server, $data, $nick, $address) = @_; | |
if(@wh) | |
{ | |
@whois = (@wh, $ac, $ssl); | |
@wh = (); | |
analyze() if !$lists_left && $modes; | |
} | |
else | |
{ | |
Irssi::print("\x0304Whois failed, aborting!"); | |
$active = 0; | |
} | |
Irssi::signal_stop(); | |
} | |
} | |
sub start_search | |
{ | |
my ($server, $ch, $u, $mode) = @_; | |
@whois = (); | |
$orig_list = $mode; | |
$channel = $ch; | |
$user = $u; | |
if($user =~ /[!@#\$]/) | |
{ | |
my ($v, $account) = split /\$/, $user, 2; | |
my ($v, $rname) = split /#/, $v, 2; | |
my ($v, $host) = split /@/, $v, 2; | |
my ($nick, $ident) = split /!/, $v, 2; | |
@whois = ($nick, $ident, [lookup_host($host)], $rname, $account, 0); | |
} | |
undef $modes; | |
$lists_left = 0; | |
@bans = (); | |
$active = 1; | |
query_list($server, $channel, $mode); | |
query_whois($server, $user) if !@whois; | |
} | |
sub isbanned | |
{ | |
my ($arg, $server, $witem) = @_; | |
my ($chan, $user) = split / /, $arg, 2; | |
start_search($server, $chan, $user, "b"); | |
} | |
sub ismuted | |
{ | |
my ($arg, $server, $witem) = @_; | |
my ($chan, $user) = split / /, $arg, 2; | |
start_search($server, $chan, $user, "q"); | |
query_list($server, $chan, "b"); | |
} | |
sub islisted | |
{ | |
my ($arg, $server, $witem) = @_; | |
my ($chan, $mode, $user) = split / /, $arg, 3; | |
start_search($server, $chan, $user, $mode =~ s/^+//r); | |
} | |
Irssi::signal_add("event 329", \&ignored); | |
Irssi::signal_add("event 276", \&ignored); | |
Irssi::signal_add("event 317", \&ignored); | |
Irssi::signal_add("event 378", \&ignored); | |
Irssi::signal_add("event 319", \&ignored); | |
Irssi::signal_add("event 312", \&ignored); | |
Irssi::signal_add("event 324", \&modes); | |
Irssi::signal_add("event 367", \&list_entry_b); | |
Irssi::signal_add("event 728", \&list_entry_q); | |
Irssi::signal_add("event 346", \&list_entry_I); | |
Irssi::signal_add("event 348", \&list_entry_e); | |
Irssi::signal_add("event 368", \&list_end); | |
Irssi::signal_add("event 729", \&list_end); | |
Irssi::signal_add("event 347", \&list_end); | |
Irssi::signal_add("event 349", \&list_end); | |
Irssi::signal_add("event 502", \&no_modes); | |
Irssi::signal_add("event 221", \&no_modes); | |
Irssi::signal_add("event 472", \&mode_error); | |
Irssi::signal_add("event 501", \&mode_error); | |
Irssi::signal_add("event 403", \&no_list); | |
Irssi::signal_add("event 482", \&no_list); | |
Irssi::signal_add("event 311", \&whois_start); | |
Irssi::signal_add("event 671", \&whois_ssl); | |
Irssi::signal_add("event 330", \&whois_account); | |
Irssi::signal_add("event 318", \&whois_end); | |
Irssi::command_bind("isbanned", \&isbanned); | |
Irssi::command_bind("ismuted", \&ismuted); | |
Irssi::command_bind("islisted", \&islisted); | |
Irssi::command_bind("isreset", \&reset); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment