| App-Bondage documentation | Contained in the App-Bondage distribution. |
App::Bondage::Recall - A PoCo-IRC plugin which can greet proxy clients with the messages they missed while they were away.
use App::Bondage::Recall;
$irc->plugin_add('Recall', App::Bondage::Recall->new( Mode => 'missed' ));
This plugin requires the IRC component to be POE::Component::IRC::State or a subclass thereof. It also requires a POE::Component::IRC::Plugin::BotTraffic to be in the plugin pipeline. It will be added automatically if it is not present.
newOne optional argument:
'Mode', which public messages you want it to recall. 'missed', the default, makes it only recall public messages that were received while no proxy client was attached. 'all' will recall public messages from all channels since they were joined. 'none' will recall none. The plugin will always recall missed private messages, regardless of this option.
Returns a plugin object suitable for feeding to
POE::Component::IRC's plugin_add() method.
Hinrik Örn Sigurðsson, hinrik.sig@gmail.com
| App-Bondage documentation | Contained in the App-Bondage distribution. |
package App::Bondage::Recall; BEGIN { $App::Bondage::Recall::AUTHORITY = 'cpan:HINRIK'; } use strict; use warnings FATAL => 'all'; use File::Temp qw(tempfile); use POE::Component::IRC::Common qw( parse_user ); use POE::Component::IRC::Plugin qw( :ALL ); use POE::Component::IRC::Plugin::BotTraffic; use POE::Filter::IRCD; use Tie::File; our $VERSION = '1.5'; sub new { my ($package, %self) = @_; if (!$self{Mode} || $self{Mode} !~ /missed|all|none/) { $self{Mode} = 'missed'; } return bless \%self, $package; } sub PCI_register { my ($self, $irc) = @_; if (!$irc->isa('POE::Component::IRC::State')) { die __PACKAGE__ . " requires PoCo::IRC::State or a subclass thereof\n"; } if (!grep { $_->isa('POE::Component::IRC::Plugin::BotTraffic') } values %{ $irc->plugin_list() }) { $irc->plugin_add('BotTraffic', POE::Component::IRC::Plugin::BotTraffic->new()); } ($self->{state}) = grep { $_->isa('App::Bondage::State') } values %{ $irc->plugin_list() }; $self->{irc} = $irc; $self->{filter} = POE::Filter::IRCD->new(); $self->{recall} = [ ]; $self->{clients} = 0; $self->{last_detach} = 0; tie @{ $self->{recall} }, 'Tie::File', scalar tempfile() if $self->{Mode} =~ /all|missed/; $irc->raw_events(1); $irc->plugin_register($self, 'SERVER', qw(cap bot_ctcp_action bot_public connected ctcp_action msg public part proxy_authed proxy_close raw)); return 1; } sub PCI_unregister { my ($self, $irc) = @_; delete $self->{irc}; return 1; } sub S_cap { my ($self, $irc) = splice @_, 0, 2; my $cmd = ${ $_[0] }; if ($cmd eq 'ACK') { my $list = ${ $_[1] } eq '*' ? ${ $_[2] } : ${ $_[1] }; my @enabled = split / /, $list; if (grep { $_ =~ /^=?identify-msg$/ } @enabled) { $self->{idmsg} = 1; } if (grep { $_ =~ /^-identify-msg$/ } @enabled) { $self->{idmsg} = 0; } } return PCI_EAT_NONE; } sub S_bot_ctcp_action { my ($self, $irc) = splice @_, 0, 2; my $recipients = join (',', @{ ${ $_[0] } }); my $msg = ${ $_[1] }; if ($self->{Mode} =~ /all|missed/) { my $line = ':' . $irc->nick_long_form($irc->nick_name()) . " PRIVMSG $recipients :\x01ACTION $msg\x01"; push @{ $self->{recall} }, $line; } return PCI_EAT_NONE; } sub S_bot_public { my ($self, $irc) = splice @_, 0, 2; my $recipients = join (',', @{ ${ $_[0] } }); my $msg = ${ $_[1] }; if ($self->{Mode} =~ /all|missed/) { my $line = ':' . $irc->nick_long_form($irc->nick_name()) . " PRIVMSG $recipients :$msg"; push @{ $self->{recall} }, $line; } return PCI_EAT_NONE; } sub S_connected { my ($self, $irc) = splice @_, 0, 2; $self->{stash} = [ ]; $self->{stashing} = 1; $self->{idmsg} = 0; return PCI_EAT_NONE; } sub S_ctcp_action { my ($self, $irc) = splice @_, 0, 2; my $sender = ${ $_[0] }; my $recipients = ${ $_[1] }; my $msg = ${ $_[2] }; return PCI_EAT_NONE if $self->{clients}; for my $recipient (@{ $recipients }) { if ($recipient eq $irc->nick_name()) { my $line = ":$sender PRIVMSG $irc->nick_name :\x01ACTION$msg\x01"; push @{ $self->{recall} }, $line; } } return PCI_EAT_NONE; } sub S_msg { my ($self, $irc) = splice @_, 0, 2; my $sender = ${ $_[0] }; my $msg = ${ $_[2] }; return PCI_EAT_NONE if $self->{clients}; my $line = ":$sender PRIVMSG $irc->nick_name :$msg"; push @{ $self->{recall} }, $line; return PCI_EAT_NONE; } sub S_part { my ($self, $irc) = splice @_, 0, 2; my $chan = ${ $_[1] }; if (my $cycle = grep { $_->isa('POE::Component::IRC::Plugin::CycleEmpty') } values %{ $irc->plugin_list() } ) { return PCI_EAT_NONE if $cycle->cycling($chan); } # too CPU-heavy # if ($self->{Mode} eq 'all') { # # remove all messages related to this channel # my $input = $self->{filter}->get( $self->{recall} ); # for my $line (0..$#{ $self->{recall} }) { # if (lc $input->[$line]{params}[0] eq lc $chan) { # delete $self->{recall}[$line]; # } # elsif ($input->[$line]{command} =~ /332|333|366/ && lc $input->[$line]{params}[1] eq lc $chan) { # delete $self->{recall}[$line]; # } # elsif ($input->[$line]{command} eq '353' && lc $input->[$line]{params}->[2] eq lc $chan) { # delete $self->{recall}[$line]; # } # } # } return PCI_EAT_NONE; } sub S_public { my ($self, $irc) = splice @_, 0, 2; my $sender = ${ $_[0] }; my $chan = ${ $_[1] }->[0]; my $msg = ${ $_[2] }; # do this here instead rather than in S_raw so that IDENTIFY-MSG # will by handled by POE::Filter::IRC::Compat if ($self->{Mode} =~ /all|missed/) { push @{ $self->{recall} }, ":$sender PRIVMSG $chan :$msg"; } return PCI_EAT_NONE; } sub S_proxy_authed { my ($self, $irc) = splice @_, 0, 2; $self->{clients}++; return PCI_EAT_NONE; } sub S_proxy_close { my ($self, $irc) = splice @_, 0, 2; $self->{clients}--; return if $self->{clients}; if ($self->{Mode} eq 'missed') { $self->{recall} = [ ]; push @{ $self->{recall} }, $self->_chan_info(); } elsif ($self->{Mode} eq 'all') { $self->{last_detach} = $#{ $self->{recall} }; } return PCI_EAT_NONE; } sub S_raw { my ($self, $irc) = splice @_, 0, 2; my $raw_line = ${ $_[0] }; my $input = $self->{filter}->get( [ $raw_line ] )->[0]; if ($self->{stashing}) { # capture all numeric commands until we've got the MOTD if ($input->{command} =~ /\d{3}/) { push @{ $self->{stash} }, $raw_line; } # RPL_ENDOFMOTD / ERR_NOMOTD if ($input->{command} =~ /376|422/) { $self->{stashing} = 0; } } if ($self->{Mode} =~ /all|missed/) { if ($input->{command} eq 'MODE' && $input->{params}[0] =~ /^[#&+!]/) { # channel mode changes push @{ $self->{recall} }, $raw_line; } elsif ($input->{command} =~ /JOIN|KICK|PART|QUIT|NICK|TOPIC/) { # other channel-related things push @{ $self->{recall} }, $raw_line; } elsif ($input->{command} =~ /332|333|353|366/) { # only log these when we've just joined the channel push @{ $self->{recall} }, $raw_line if $self->{state}->is_syncing($input->{params}[0]); } } return PCI_EAT_NONE; } # returns everything that an IRC server would send us upon joining # the channels we're on sub _chan_info { my ($self) = @_; my $irc = $self->{irc}; my $state = $self->{state}; my $me = $irc->nick_name(); my @info; for my $chan (keys %{ $irc->channels() }) { push @info, ':' . $irc->nick_long_form($me) . " JOIN :$chan"; push @info, $state->topic_reply($chan) if keys %{ $irc->channel_topic($chan) }; push @info, $state->names_reply($chan); } return @info; } sub recall { my ($self) = @_; my $irc = $self->{irc}; my $me = $irc->nick_name(); my $server = $irc->server_name(); my @lines; for my $line (@{ $self->{stash} }) { $line =~ s/^(\S+ +\S+) +\S+ +(.*)/$1 $me $2/; push @lines, $line; } push @lines, ":$server MODE $me :" . $irc->umode() if $irc->umode(); push @lines, @{ $self->{recall} }; push @lines, ":$server CAP * ACK :identify-msg" if $self->{idmsg}; if ($self->{Mode} eq 'all' && $#{ $self->{recall} } > $self->{last_detach}) { # remove all PMs received since we last detached for my $line ($self->{last_detach} .. $#{ $self->{recall} }) { my $in = shift @{ $self->{filter}->get( $self->{recall} ) }; if ($in->{command} eq 'PRIVMSG' && $in->{params}[0] !~ /^[#&+!]/) { delete $self->{recall}[$line]; } } } elsif ($self->{Mode} eq 'missed') { $self->{recall} = [ ]; push @{ $self->{recall} }, $self->_chan_info(); } elsif ($self->{Mode} eq 'none') { $self->{recall} = [ ]; push @lines, $self->_chan_info(); } return @lines; } 1;