diff options
Diffstat (limited to 'scripts/revolve.pl')
-rw-r--r-- | scripts/revolve.pl | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/scripts/revolve.pl b/scripts/revolve.pl new file mode 100644 index 0000000..1ccfaa9 --- /dev/null +++ b/scripts/revolve.pl @@ -0,0 +1,388 @@ +use strict; +use warnings; +use Irssi; +use Irssi::TextUI; +use POSIX 'strftime'; +use vars qw($VERSION %IRSSI); + +$VERSION = "0.0.6"; # 29d237f4fda2f0d +%IRSSI = ( + authors => 'Ryan Freebern', + contact => 'ryan@freebern.org', + name => 'revolve', + description => 'Summarizes multiple sequential joins/parts/quits.', + license => 'GPL v2 or later', + url => 'http://github.com/rfreebern/irssi-revolving-door', +); + +# Based on compact.pl by Wouter Coekaerts <wouter@coekaerts.be> +# http://wouter.coekaerts.be/irssi/scripts/compact.pl.html + +# Usage +# ===== +# Once loaded, the script will summarise and remove any +# JOINS/PARTS/QUITS/NICKS + +# Options +# ======= +# /set revolve_show_nickchain <ON|OFF> +# * whether more than two nick names should be shown when people +# change nicks +# +# /set revolve_modes <ON|OFF> +# * whether MODES should also be summarised +# +# /set revolve_show_rejoins <ON|OFF> +# * whether rejoins should be displayed instead of clearing them out +# from QUITS/PARTS silently +# +# /set revolve_show_time <ON|OFF> +# * whether timestamp should be displayed before and after the summary +# line +# + +# To change the look and feel, edit the script source code below: + +# -----8<------ do not change this part ----->8----- +use constant { + JOINS => +MSGLEVEL_JOINS, + PARTS => +MSGLEVEL_PARTS, + QUITS => +MSGLEVEL_QUITS, + NICKS => +MSGLEVEL_NICKS, + REJOINS => (+MSGLEVEL_JOINS|+MSGLEVEL_PARTS), + MODES => +MSGLEVEL_MODES, +}; + + +my %msg_level_text; + +# ====================== CONFIGURABLE SECTION START ====================== +# +# IMPORTANT: all texts and all separators must be distinguishable and +# one must not be a substring of another! +# +# +# here are the heading texts to be shown on the summary line: +# +@msg_level_text{(JOINS, PARTS, QUITS, NICKS, REJOINS, MODES)}=qw/Joins Parts Quits Nicks Cycles Status/; +# +# here after the => are the colour styles to be used for the above heading texts: +# +my %msg_level_style = ( + # style before heading text + JOINS() => '%C%I', + PARTS() => '%c%I', + QUITS() => '%c%I', + NICKS() => '%K%I', + REJOINS() => '%C%I', + MODES() => '%K%I', + # style after heading text separator + 0 => '%I%w', + # line colour + -1 => '%w', + ); +# +# here is the time format / style to use if time display is enabled: +# %%H:%%M are passed to strftime +# +my $time_format = '%X5N' . '%%H:%%M' . '%w'; +# +# here are the separators used on the summary line: +my $level_separator = ' ── '; +my $nick_separator = ', '; +my $type_separator = ': '; +my $new_nick_separator = ' → '; +my $time_separator = ' | '; +# +# here is the line indentation (10 spaces): +# +my $indentation = ' 'x10; +# +# ======================= CONFIGURABLE SECTION END ======================= +# +# +# + +my %summary_lines; +my %msg_level_constant = reverse %msg_level_text; +my %prefix_tbl; + +sub lrtrim { + for (@_) { + s/^\s+//; s/\s+$//; + } +} + +sub dotime { + my $time = time; + my $format = $time_format; + $format =~ y/%/\01/; + $format =~ s/\01\01/%/g; + $format = strftime($format, localtime $time); + $format =~ y/\01/%/; + $format +} + +sub summarize { + my ($window, $tag, $channel, $nick, $arg, $type) = @_; + + return unless $window; + my $view = $window->view; + my $check = $tag . ':' . $channel; + + my $tb = $view->get_bookmark('trackbar'); + $view->set_bookmark_bottom('bottom'); + my $last = $view->get_bookmark('bottom'); + if ($tb && $last->{_irssi} == $tb->{_irssi}) { + $last = $last->prev; + } + my $secondlast = $last ? $last->prev : undef; + if ($tb && $secondlast && $secondlast->{_irssi} == $tb->{_irssi}) { + $secondlast = $secondlast->prev; + } + + # Remove the last line, which should have the join/part/quit message. + return unless $last->{info}{level} & $type; + $view->remove_line($last); + + my $pt = $prefix_tbl{$tag} || []; + my $ptrim = $pt->[0] ? qr/^([\Q$pt->[0]\E]*)/ : qr/^()/; + + # If the second-to-last line is a summary line, parse it. + my %door = (JOINS() => [], PARTS() => [], QUITS() => [], NICKS() => [], REJOINS() => [], MODES() => []); + my @summarized = (); + my $old_time = dotime(); + my $new_time = $old_time; + if ($secondlast and $summary_lines{$check} and $secondlast->{_irssi} == $summary_lines{$check}) { + my $csummary = $secondlast->get_text(1); + my ($time, @x) = split /\Q$time_separator/, $csummary, 3; + my $summary = $secondlast->get_text(0); + $summary = (split /\Q$time_separator/, $summary, 3)[1] if @x; + lrtrim $summary; + $time = '' unless @x; + lrtrim $time; + @summarized = split(/\Q$level_separator/, $summary); + lrtrim @summarized; + foreach my $part (@summarized) { + my ($type, $nicks) = split(/\Q$type_separator/, $part, 2); + lrtrim $nicks; + my $ctype = $msg_level_constant{$type}; + $door{$ctype} = [ split(/\Q$nick_separator/, $nicks) ]; + if ($ctype == JOINS || $ctype == REJOINS) { + for (@{$door{$ctype}}) { + s/$ptrim//; + $_ = [ $1, $_ ] + } + } elsif ($ctype == MODES) { + for (@{$door{$ctype}}) { + s/^([\Q$pt->[0]\E:]*)//; + $_ = [ $1, $_ ]; + } + } + } + $view->remove_line($secondlast); + $old_time = $time; + } + + my $rejoins = Irssi::settings_get_bool('revolve_show_rejoins'); + my $nickchain = Irssi::settings_get_bool('revolve_show_nickchain'); + if ($type == JOINS) { # Join + if (grep { $_ eq $nick } @{$door{+PARTS}}, @{$door{+QUITS}}) { + for (PARTS, QUITS) { + @{$door{+$_}} = grep { $_ ne $nick } @{$door{+$_}}; + } + push(@{$door{+REJOINS}}, [ '', $nick ]) + if $rejoins; + } else { + push(@{$door{+$type}}, [ '', $nick ]); + } + } elsif ($type == QUITS || $type == PARTS) { # Quit / Part + for (MODES) { + @{$door{+$_}} = grep { $_->[1] ne $nick } @{$door{+$_}}; + } + if (grep { $_->[1] eq $nick } @{$door{+JOINS}}, @{$door{+REJOINS}}) { + for (JOINS, REJOINS) { + @{$door{+$_}} = grep { $_->[1] ne $nick } @{$door{+$_}}; + } + push @{$door{+$type}}, $nick + if $rejoins; + } else { + push @{$door{+$type}}, $nick; + } + } elsif ($type == NICKS) { + my $new_nick = $arg; + my $nick_found = 0; + foreach my $known_nick (@{$door{+NICKS}}) { + my @alt_nicks = split(/\Q$new_nick_separator/, $known_nick); + my $orig_nick = $alt_nicks[0]; + my $current_nick = $alt_nicks[-1]; + if ($current_nick eq $nick) { + if ($new_nick eq $orig_nick) { + if ($nickchain) { + $known_nick = $new_nick; + } else { + @{$door{+NICKS}} = grep { $_ ne $known_nick } @{$door{+NICKS}}; + } + } else { + if ($nickchain) { + @alt_nicks = grep { $_ ne $orig_nick && $_ ne $new_nick } @alt_nicks; + $known_nick = join $new_nick_separator, $orig_nick, @alt_nicks, $new_nick; + } else { + $known_nick = "$orig_nick$new_nick_separator$new_nick"; + } + } + $nick_found = 1; + last; + } + } + if (!$nick_found) { + push(@{$door{+NICKS}}, "$nick$new_nick_separator$new_nick"); + } + # Update nicks in join lists. + foreach my $part (JOINS, REJOINS, MODES) { + foreach (@{$door{$part}}) { + $_->[1] = $new_nick if $_->[1] eq $nick; + } + } + } elsif ($type == MODES) { + my $mode = $arg; + my ($spec, @args) = split ' ', $mode; + my $type = '+'; + my $i = 0; + for my $c (split //, $spec) { + if ($c eq '+' || $c eq '-') { + $type = $c; + } else { + my @ent = grep { $_->[1] eq $args[$i] } @{$door{+JOINS}}, @{$door{+REJOINS}}; + my $p = substr $pt->[0], (index $pt->[1], $c), 1; + if (@ent) { + if ($type eq '+') { + $ent[0][0] = $p . $ent[0][0]; + } else { + $ent[0][0] =~ s/\Q$p\E//; + } + } elsif (my ($e) = grep { $_->[1] eq $args[$i]} @{$door{+MODES}}) { + my $pos = $e->[0]; + my $neg = ''; + if ($pos =~ s/^(.*)://) { + $neg = $1; + } + if ($type eq '+') { + $pos = $p . $pos; + $neg =~ s/\Q$p\E//; + } else { + $neg = $p . $neg; + $pos =~ s/\Q$p\E//; + } + $e->[0] = length $neg ? "$neg:$pos" : $pos; + } else { + push @{$door{+MODES}}, [ $type eq '+' ? $p : "$p:", $args[$i] ]; + } + } + } + } + + foreach my $part (JOINS, REJOINS, MODES) { + foreach (@{$door{$part}}) { + $_ = $_->[0].$_->[1]; + } + } + + @summarized = (); + my $level = MSGLEVEL_NEVER; + foreach my $part (JOINS, PARTS, QUITS, REJOINS, NICKS, MODES) { + if (@{$door{$part}}) { + push @summarized, $msg_level_style{$part} . $msg_level_text{$part} . $type_separator . $msg_level_style{0} + . join($nick_separator, @{$door{$part}}); + $level |= $part; + } + } + + my $summary = join($msg_level_style{-1}.$level_separator, @summarized); + if (Irssi::settings_get_bool('revolve_show_time')) { + $summary = $old_time.$msg_level_style{-1}.$time_separator.$summary; + $summary .= $msg_level_style{-1}.$time_separator.$new_time + if $old_time ne $new_time; + } + if (@summarized) { + $window->print($indentation. '%|'. $msg_level_style{-1}.$summary, $level); + # Get the line we just printed so we can log its ID. + $view->set_bookmark_bottom('bottom'); + $last = $view->get_bookmark('bottom'); + $summary_lines{$check} = $last->{_irssi}; + } else { + delete $summary_lines{$check}; + } + + $view->redraw(); +} + +sub delete_and_summarize { + return unless our @summary; + my ($tag, $channel, $nick, $arg, $type) = @summary; + # "delete_and_summarize: $type"; + my ($dest) = @_; + return unless $dest->{server} && $dest->{server}{tag} eq $tag; + return if defined $channel && $dest->{target} ne $channel; + &Irssi::signal_continue; + summarize($dest->{window}, $tag, $dest->{target}, $nick, $arg, $type); +} + +sub summarize_join { + my ($server, $channel, $nick, $address, $reason) = @_; + local our @summary = ($server->{tag}, $channel, $nick, undef, JOINS); + &Irssi::signal_continue; +} + +sub summarize_quit { + my ($server, $nick, $address, $reason) = @_; + local our @summary = ($server->{tag}, undef, $nick, undef, QUITS); + &Irssi::signal_continue; +} + +sub summarize_part { + my ($server, $channel, $nick, $address, $reason) = @_; + local our @summary = ($server->{tag}, $channel, $nick, undef, PARTS); + &Irssi::signal_continue; +} + +sub summarize_nick { + my ($server, $new_nick, $old_nick, $address) = @_; + local our @summary = ($server->{tag}, undef, $old_nick, $new_nick, NICKS); + &Irssi::signal_continue; +} + +sub update_prefixes { + my ($server) = @_; + my $prefix = $server->can('isupport') && $server->isupport('prefix') || '(ohv)@%+'; + $prefix =~ s/^\((.*?)\)//; + my $modes = $1; + $prefix_tbl{$server->{tag}} = [ $prefix, $modes ]; +} + +sub summarize_irc_mode { + my ($server, $channel, $nick, $address, $mode) = @_; + return unless Irssi::settings_get_bool('revolve_modes'); + my ($spec, @args) = split ' ', $mode; + return unless @args; + update_prefixes($server) unless $prefix_tbl{$server->{tag}}; + my $modes = $prefix_tbl{$server->{tag}}[1]; + return unless $spec =~ /^([-+][\Q$modes\E]+)+$/; + local our @summary = ($server->{tag}, $channel, $nick, $mode, MODES); + &Irssi::signal_continue; + my $dest = $server->format_create_dest($channel, MSGLEVEL_MODES, $server->window_find_closest($channel, MSGLEVEL_MODES)); + Irssi::signal_emit('print starting', $dest); +} + +Irssi::signal_register({'print starting'=>[qw[Irssi::UI::TextDest]]}); +Irssi::settings_add_bool('revolve', 'revolve_show_nickchain', 0); +Irssi::settings_add_bool('revolve', 'revolve_modes', 0); +Irssi::settings_add_bool('revolve', 'revolve_show_rejoins', 0); +Irssi::settings_add_bool('revolve', 'revolve_show_time', 0); +Irssi::signal_add('message join', 'summarize_join'); +Irssi::signal_add('message part', 'summarize_part'); +Irssi::signal_add('message quit', 'summarize_quit'); +Irssi::signal_add('message nick', 'summarize_nick'); +Irssi::signal_add('message irc mode', 'summarize_irc_mode'); +Irssi::signal_add('print text', 'delete_and_summarize'); +Irssi::signal_add_last('event 376', 'update_prefixes'); |