File Coverage

blib/lib/App/Bondage/Client.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1             package App::Bondage::Client;
2             BEGIN {
3 1     1   2665 $App::Bondage::Client::AUTHORITY = 'cpan:HINRIK';
4             }
5              
6 1     1   7 use strict;
  1         2  
  1         43  
7 1     1   6 use warnings FATAL => 'all';
  1         1  
  1         38  
8 1     1   4 use Carp;
  1         1  
  1         83  
9 1     1   473 use POE qw(Filter::Line Filter::Stackable);
  0            
  0            
10             use POE::Component::IRC::Common qw( u_irc );
11             use POE::Component::IRC::Plugin qw( :ALL );
12             use POE::Filter::IRCD;
13              
14             our $VERSION = '1.3';
15              
16             sub new {
17             my ($package, %self) = @_;
18             if (!$self{Socket}) {
19             croak "$package requires a Socket";
20             }
21             return bless \%self, $package;
22             }
23              
24             sub PCI_register {
25             my ($self, $irc) = @_;
26            
27             if (!$irc->isa('POE::Component::IRC::State')) {
28             die __PACKAGE__ . " requires PoCo::IRC::State or a subclass thereof\n";
29             }
30            
31             if (!grep { $_->isa('App::Bondage::Recall') } values %{ $irc->plugin_list() } ) {
32             die __PACKAGE__ . " requires App::Bondage::Recall\n";
33             }
34            
35             $self->{filter} = POE::Filter::IRCD->new();
36             $self->{stacked} = POE::Filter::Stackable->new(
37             Filters => [
38             POE::Filter::Line->new(),
39             POE::Filter::IRCD->new(),
40             ]
41             );
42            
43             ($self->{state}) = grep { $_->isa('App::Bondage::State') } values %{ $irc->plugin_list() };
44             $self->{irc} = $irc;
45             $irc->raw_events(1);
46             $irc->plugin_register($self, 'SERVER', qw(raw));
47            
48             POE::Session->create(
49             object_states => [
50             $self => [ qw(_start _client_error _client_input) ],
51             ],
52             );
53              
54             return 1;
55             }
56              
57             sub PCI_unregister {
58             my ($self, $irc) = @_;
59              
60             $poe_kernel->call("$self", '_client_error');
61             return 1;
62             }
63              
64             sub _start {
65             my ($kernel, $self) = @_[KERNEL, OBJECT];
66              
67             $kernel->alias_set("$self");
68              
69             $self->{wheel} = POE::Wheel::ReadWrite->new(
70             Handle => $self->{Socket},
71             InputFilter => $self->{stacked},
72             OutputFilter => POE::Filter::Line->new(),
73             InputEvent => '_client_input',
74             ErrorEvent => '_client_error',
75             );
76             delete $self->{Socket};
77             $self->{wheel_id} = $self->{wheel}->ID();
78              
79             my ($recall_plug) = grep { $_->isa('App::Bondage::Recall') } values %{ $self->{irc}->plugin_list() };
80             $self->{wheel}->put($recall_plug->recall());
81            
82             return;
83             }
84              
85             sub _client_error {
86             my ($kernel, $self) = @_[KERNEL, OBJECT];
87             my $irc = $self->{irc};
88            
89             if ($self->{wheel}) {
90             # causes deep recursion somehow
91             #$self->{wheel}->put('ERROR :Closing link (Caught interrupt)');
92             #$self->{wheel}->flush();
93             delete $self->{wheel};
94             $irc->send_event(irc_proxy_close => $self->{wheel_id});
95             $kernel->alias_remove("$self");
96             $irc->plugin_del($self) if grep { $_ == $self } values %{ $irc->plugin_list() };
97             }
98             return;
99             }
100              
101             sub _client_input {
102             my ($self, $input) = @_[OBJECT, ARG0];
103             my $irc = $self->{irc};
104             my $state = $self->{state};
105            
106             if ($input->{command} eq 'QUIT') {
107             $irc->plugin_del($self);
108             return;
109             }
110             elsif ($input->{command} eq 'PING') {
111             $self->{wheel}->put('PONG ' . $input->{params}[0] || '');
112             return;
113             }
114             elsif ($input->{command} eq 'PRIVMSG') {
115             my ($recipient, $msg) = @{ $input->{params} }[0..1];
116             if ($recipient =~ /^[#&+!]/) {
117             # recreate channel messages from this client for
118             # other clients to see
119             my $line = ':' . $irc->nick_long_form($irc->nick_name()) . " PRIVMSG $recipient :$msg";
120            
121             for my $client (grep { $_->isa('App::Bondage::Client') } values %{ $irc->plugin_list() } ) {
122             $client->put($line) if $client != $self;
123             }
124             }
125             }
126             elsif ($input->{command} eq 'WHO') {
127             if ($input->{params}[0] && $input->{params}[0] !~ tr/*//) {
128             if (!defined $input->{params}[1]) {
129             if ($input->{params}[0] !~ /^[#&+!]/ || $irc->channel_list($input->{params}[0])) {
130             $state->enqueue(sub { $self->put($_[0]) }, 'who_reply', $input->{params}[0]);
131             return;
132             }
133             }
134             }
135             }
136             elsif ($input->{command} eq 'MODE') {
137             if ($input->{params}[0]) {
138             my $mapping = $irc->isupport('CASEMAPPING');
139             if (u_irc($input->{params}[0], $mapping) eq u_irc($irc->nick_name(), $mapping)) {
140             if (!defined $input->{params}[1]) {
141             $self->put($state->mode_reply($input->{params}[0]));
142             return;
143             }
144             }
145             elsif ($input->{params}[0] =~ /^[#&+!]/ && $irc->channel_list($input->{params}[0])) {
146             if (!defined $input->{params}[1] || $input->{params}[1] =~ /^[eIb]$/) {
147             $state->enqueue(sub { $self->put($_[0]) }, 'mode_reply', @{ $input->{params} }[0,1]);
148             return;
149             }
150             }
151             }
152             }
153             elsif ($input->{command} eq 'NAMES') {
154             if ($irc->channel_list($input->{params}[0]) && !defined $input->{params}[1]) {
155             $state->enqueue(sub { $self->put($_[0]) }, 'names_reply', $input->{params}[0]);
156             return;
157             }
158             }
159             elsif ($input->{command} eq 'TOPIC') {
160             if ($irc->channel_list($input->{params}[0]) && !defined $input->{params}[1]) {
161             $state->enqueue(sub { $self->put($_[0]) }, 'topic_reply', $input->{params}[0]);
162             return;
163             }
164             }
165            
166             $irc->yield(quote => $input->{raw_line});
167            
168             return;
169             }
170              
171             sub S_raw {
172             my ($self, $irc) = splice @_, 0, 2;
173             my $raw_line = ${ $_[0] };
174             return PCI_EAT_NONE if !defined $self->{wheel};
175              
176             my $input = $self->{filter}->get( [ $raw_line ] )->[0];
177             $self->{wheel}->put($raw_line) if $input->{command} !~ /^(?:PING|PONG)/;
178             return PCI_EAT_NONE;
179             }
180              
181             sub put {
182             my ($self, $raw_line) = @_;
183             $self->{wheel}->put($raw_line) if defined $self->{wheel};
184             return;
185             }
186              
187             1;
188              
189             =encoding utf8
190              
191             =head1 NAME
192              
193             App::Bondage::Client - A PoCo-IRC plugin which handles a proxy client.
194              
195             =head1 SYNOPSIS
196              
197             use App::Bondage::Client;
198              
199             $irc->plugin_add('Client_1', App::Bondage::Client->new(Socket => $socket));
200              
201             =head1 DESCRIPTION
202              
203             App::Bondage::Client is a L plugin.
204             It handles a input/output and disconnects from a proxy client.
205              
206             This plugin requires the IRC component to be
207             L or a subclass thereof.
208              
209             =head1 METHODS
210              
211             =head2 C
212              
213             One argument:
214              
215             B<'Socket'>, the socket of the proxy client.
216              
217             Returns a plugin object suitable for feeding to
218             L's C method.
219              
220             =head2 C
221              
222             One argument:
223              
224             An IRC protocol line
225              
226             Sends an IRC protocol line to the client
227              
228             =head1 AUTHOR
229              
230             Hinrik Ern SigurEsson, hinrik.sig@gmail.com
231              
232             =cut