File Coverage

blib/lib/Protocol/IRC/Message.pm
Criterion Covered Total %
statement 110 115 95.6
branch 56 62 90.3
condition 11 12 91.6
subroutine 18 19 94.7
pod 15 15 100.0
total 210 223 94.1


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2008-2015 -- leonerd@leonerd.org.uk
5              
6             package Protocol::IRC::Message;
7              
8 9     9   67340 use strict;
  9         20  
  9         224  
9 9     9   43 use warnings;
  9         16  
  9         358  
10              
11             our $VERSION = '0.11';
12              
13 9     9   42 use Carp;
  9         23  
  9         7178  
14             our @CARP_NOT = qw( Net::Async::IRC );
15              
16             =head1 NAME
17              
18             C - encapsulates a single IRC message
19              
20             =head1 SYNOPSIS
21              
22             use Protocol::IRC::Message;
23              
24             my $hello = Protocol::IRC::Message->new(
25             "PRIVMSG",
26             undef,
27             "World",
28             "Hello, world!"
29             );
30              
31             printf "The command is %s and the final argument is %s\n",
32             $hello->command, $hello->arg( -1 );
33              
34             =head1 DESCRIPTION
35              
36             An object in this class represents a single IRC message, either received from
37             or to be sent to the server. These objects are immutable once constructed, but
38             provide a variety of methods to access the contained information.
39              
40             This class also understands IRCv3 message tags.
41              
42             =cut
43              
44             =head1 CONSTRUCTOR
45              
46             =cut
47              
48             =head2 new_from_line
49              
50             $message = Protocol::IRC::Message->new_from_line( $line )
51              
52             Returns a new C object, constructed by parsing the
53             given IRC line. Most typically used to create a new object to represent a
54             message received from the server.
55              
56             =cut
57              
58             sub new_from_line
59             {
60 36     36 1 5440 my $class = shift;
61 36         66 my ( $line ) = @_;
62              
63 36         50 my %tags;
64 36 100       148 if( $line =~ s/^\@([^ ]+) +// ) {
65 1         6 foreach ( split m/;/, $1 ) {
66 3 100       12 if( m/^([^=]+)=(.*)$/ ) {
67 2         6 $tags{$1} = $2;
68             }
69             else {
70 1         3 $tags{$_} = undef;
71             }
72             }
73             }
74              
75 36         48 my $prefix;
76 36 100       249 if( $line =~ s/^:([^ ]+) +// ) {
77 29         66 $prefix = $1;
78             }
79              
80 36         159 my ( $mid, $final ) = split( m/ +:/, $line, 2 );
81 36         131 my @args = split( m/ +/, $mid );
82              
83 36 100       107 push @args, $final if defined $final;
84              
85 36         59 my $command = shift @args;
86              
87 36         124 return $class->new_with_tags( $command, \%tags, $prefix, @args );
88             }
89              
90             =head2 new
91              
92             $message = Protocol::IRC::Message->new( $command, $prefix, @args )
93              
94             Returns a new C object, intialised from the given
95             components. Most typically used to create a new object to send to the server
96             using C. The message will contain no IRCv3 tags.
97              
98             =cut
99              
100             sub new
101             {
102 12     12 1 2284 my $class = shift;
103 12         72 return $class->new_with_tags( $_[0], {}, $_[1], @_[2..$#_] );
104             }
105              
106             =head2 new_with_tags
107              
108             $mesage = Protocol::IRC::Message->new_with_tags( $command, \%tags, $prefix, @args )
109              
110             Returns a new C object, as with C but also
111             containing the given IRCv3 tags.
112              
113             =cut
114              
115             sub new_with_tags
116             {
117 51     51 1 1028 my $class = shift;
118 51         145 my ( $command, $tags, $prefix, @args ) = @_;
119              
120             # IRC is case-insensitive for commands, but we'd like them in uppercase
121             # to keep things simpler
122 51         119 $command = uc $command;
123              
124             # Less strict checking than RFC 2812 because a lot of servers lately seem
125             # to be more flexible than that.
126              
127 51 100 100     479 $command =~ m/^[A-Z]+$/ or $command =~ m/^\d\d\d$/ or
128             croak "Command must be just letters or three digits";
129              
130 50         149 foreach my $key ( keys %$tags ) {
131 6 100       143 $key =~ m{^[a-zA-Z0-9./-]+$} or
132             croak "Tag key '$key' is invalid";
133              
134 5         10 my $value = $tags->{$key};
135 5 100 100     139 defined $value and $value =~ m{[ ;]} and
136             croak "Tag value '$value' for key '$key' is invalid";
137             }
138              
139 48 100       117 if( defined $prefix ) {
140 31 100       232 $prefix =~ m/[ \t\x0d\x0a]/ and
141             croak "Prefix must not contain whitespace";
142             }
143              
144 47         139 foreach ( @args[0 .. $#args-1] ) { # Not the final
145 42 100       223 defined or croak "Argument must be defined";
146 41 100       263 m/[ \t\x0d\x0a]/ and
147             croak "Argument must not contain whitespace";
148             }
149              
150 45 100       126 if( @args ) {
151 40 100       211 defined $args[-1] or croak "Final argument must be defined";
152 39 100       274 $args[-1] =~ m/[\x0d\x0a]/ and croak "Final argument must not contain a linefeed";
153             }
154              
155 43         211 my $self = {
156             command => $command,
157             prefix => $prefix,
158             args => \@args,
159             tags => { %$tags },
160             };
161              
162 43         259 return bless $self, $class;
163             }
164              
165             =head1 METHODS
166              
167             =cut
168              
169             =head2 STRING
170              
171             $str = $message->STRING
172              
173             $str = "$message"
174              
175             Returns a string representing the message, suitable for use in a debugging
176             message or similar. I: This is not the same as the IRC wire form, to
177             send to the IRC server; for that see C.
178              
179             =cut
180              
181 9     9   12765 use overload '""' => "STRING";
  9         10066  
  9         56  
182             sub STRING
183             {
184 0     0 1 0 my $self = shift;
185 0         0 my $class = ref $self;
186             return $class . "[" .
187             ( defined $self->{prefix} ? "prefix=$self->{prefix}," : "" ) .
188             "cmd=$self->{command}," .
189 0 0       0 "args=(" . join( ",", @{ $self->{args} } ) . ")]";
  0         0  
190             }
191              
192             =head2 command
193              
194             $command = $message->command
195              
196             Returns the command name or numeric stored in the message object.
197              
198             =cut
199              
200             sub command
201             {
202 87     87 1 5005 my $self = shift;
203 87         568 return $self->{command};
204             }
205              
206             =head2 command_name
207              
208             $name = $message->command_name
209              
210             For named commands, returns the command name directly. For server numeric
211             replies, returns the name of the numeric.
212              
213             =cut
214              
215             my %NUMERIC_NAMES;
216              
217             sub command_name
218             {
219 20     20 1 34 my $self = shift;
220 20   66     50 return $NUMERIC_NAMES{ $self->command } || $self->command;
221             }
222              
223             =head2 tags
224              
225             $tags = $message->tags
226              
227             Returns a HASH reference containing IRCv3 message tags. This is a reference to
228             the hash stored directly by the object itself, so the caller should be careful
229             not to modify it.
230              
231             =cut
232              
233             sub tags
234             {
235 2     2 1 9 my $self = shift;
236             return $self->{tags}
237 2         11 }
238              
239             =head2 prefix
240              
241             $prefix = $message->prefix
242              
243             Returns the line prefix stored in the object, or the empty string if one was
244             not supplied.
245              
246             =cut
247              
248             sub prefix
249             {
250 52     52 1 79 my $self = shift;
251 52 100       300 return defined $self->{prefix} ? $self->{prefix} : "";
252             }
253              
254             =head2 prefix_split
255              
256             ( $nick, $ident, $host ) = $message->prefix_split
257              
258             Splits the prefix into its nick, ident and host components. If the prefix
259             contains only a hostname (such as the server name), the first two components
260             will be returned as C.
261              
262             =cut
263              
264             sub prefix_split
265             {
266 22     22 1 43 my $self = shift;
267              
268 22         71 my $prefix = $self->prefix;
269              
270 22 100       197 return ( $1, $2, $3 ) if $prefix =~ m/^(.*?)!(.*?)@(.*)$/;
271              
272             # $prefix doesn't split into nick!ident@host so presume host only
273 4         13 return ( undef, undef, $prefix );
274             }
275              
276             =head2 arg
277              
278             $arg = $message->arg( $index )
279              
280             Returns the argument at the given index. Uses normal perl array indexing, so
281             negative indices work as expected.
282              
283             =cut
284              
285             sub arg
286             {
287 43     43 1 54 my $self = shift;
288 43         83 my ( $index ) = @_;
289 43         111 return $self->{args}[$index];
290             }
291              
292             =head2 args
293              
294             @args = $message->args
295              
296             Returns a list containing all the message arguments.
297              
298             =cut
299              
300             sub args
301             {
302 14     14 1 22 my $self = shift;
303 14         20 return @{$self->{args}};
  14         82  
304             }
305              
306             =head2 stream_to_line
307              
308             $line = $message->stream_to_line
309              
310             Returns a string suitable for sending the message to the IRC server.
311              
312             =cut
313              
314             sub stream_to_line
315             {
316 13     13 1 23 my $self = shift;
317              
318 13         21 my $line = "";
319              
320 13 100       17 if( keys %{ $self->{tags} } ) {
  13         85  
321 1         2 my $tags = $self->{tags};
322 1 50       4 $line .= "\@" . join( ";", map { defined $tags->{$_} ? "$_=$tags->{$_}" : $_ } keys %$tags ) . " ";
  1         8  
323             }
324              
325 13 100       38 if( defined $self->{prefix} ) {
326 2         7 $line .= ":$self->{prefix} ";
327             }
328              
329 13         31 $line .= $self->{command};
330              
331 13         18 foreach ( @{$self->{args}} ) {
  13         36  
332 20 100 100     122 if( m/ / or m/^:/ ) {
333 7         22 $line .= " :$_";
334             }
335             else {
336 13         34 $line .= " $_";
337             }
338             }
339              
340 13         61 return $line;
341             }
342              
343             # Argument naming information
344              
345             # This hash holds HASH refs giving the names of the positional arguments of
346             # any message. The hash keys store the argument names, and the values store
347             # an argument index, the string "pn" meaning prefix nick, or "$n~$m" meaning
348             # an index range. Endpoint can be absent.
349              
350             my %ARG_NAMES = (
351             INVITE => { inviter_nick => "pn",
352             invited_nick => 0,
353             target_name => 1 },
354             KICK => { kicker_nick => "pn",
355             target_name => 0,
356             kicked_nick => 1,
357             text => 2 },
358             MODE => { target_name => 0,
359             modechars => 1,
360             modeargs => "2.." },
361             NICK => { old_nick => "pn",
362             new_nick => 0 },
363             NOTICE => { targets => 0,
364             text => 1 },
365             PING => { text => 0 },
366             PONG => { text => 0 },
367             QUIT => { text => 0 },
368             PART => { target_name => 0,
369             text => 1 },
370             PRIVMSG => { targets => 0,
371             text => 1 },
372             TOPIC => { target_name => 0,
373             text => 1 },
374             );
375              
376             # Misc. named commands
377             $ARG_NAMES{$_} = { target_name => 0 } for qw(
378             JOIN LIST NAMES WHO WHOIS WHOWAS
379             );
380              
381             # TODO: 472 ERR_UNKNOWNMODE: :is unknown mode char to me for
382             # How to parse this one??
383              
384             =head2 arg_names
385              
386             $names = $message->arg_names
387              
388             Returns a HASH reference giving details on how to parse named arguments for
389             the command given in this message.
390              
391             This will be a hash whose keys give the names of the arguments, and the values
392             of these keys indicate how that argument is derived from the simple positional
393             arguments.
394              
395             Normally this method is only called internally by the C method,
396             but is documented here for the benefit of completeness, and in case extension
397             modules wish to define parsing of new message types.
398              
399             Each value should be one of the following:
400              
401             =over 4
402              
403             =item * String literal C
404              
405             The value is a string, the nickname given in the message prefix
406              
407             =item * NUMBER..NUMBER
408              
409             The value is an ARRAY ref, containing a list of all the numbered arguments
410             between the (inclusive) given limits. Either or both limits may be negative;
411             they will count backwards from the end.
412              
413             =item * NUMBER
414              
415             The value is the argument at that numeric index. May be negative to count
416             backwards from the end.
417              
418             =item * NUMBER@
419              
420             The value is the argument at that numeric index as for C, except that
421             the result will be split on spaces and stored in an ARRAY ref.
422              
423             =back
424              
425             =cut
426              
427             sub arg_names
428             {
429             # Usage: Class->arg_names($command) or $self->arg_names()
430 31     31 1 60 my $command;
431              
432 31 100       73 if( ref $_[0] ) {
433 30         42 my $self = shift;
434 30         85 $command = $self->{command};
435             }
436             else {
437 1         2 my $class = shift; # ignore
438 1         2 ( $command ) = @_;
439 1 50       4 defined $command or croak 'Usage: '.__PACKAGE__.'->arg_names($command)';
440             }
441              
442 31         121 return $ARG_NAMES{$command};
443             }
444              
445             =head2 named_args
446              
447             $args = $message->named_args
448              
449             Parses arguments in the message according to the specification given by the
450             C method. Returns a hash of parsed arguments.
451              
452             TODO: More complete documentation on the exact arg names/values per message
453             type.
454              
455             =cut
456              
457             sub named_args
458             {
459 24     24 1 37 my $self = shift;
460              
461 24 100       56 my $argnames = $self->arg_names or return;
462              
463 23         35 my %named_args;
464 23         71 foreach my $name ( keys %$argnames ) {
465 46         77 my $argindex = $argnames->{$name};
466              
467 46         61 my $value;
468 46 100       267 if( $argindex eq "pn" ) {
    100          
    100          
    50          
469 2         5 ( $value, undef, undef ) = $self->prefix_split;
470             }
471             elsif( $argindex =~ m/^(-?\d+)?\.\.(-?\d+)?$/ ) {
472 3         10 my ( $start, $end ) = ( $1, $2 );
473 3         8 my @args = $self->args;
474              
475 3 50       9 defined $start or $start = 0;
476 3 100       17 defined $end or $end = $#args;
477              
478 3 100       8 $end += @args if $end < 0;
479              
480 3         13 $value = [ splice( @args, $start, $end-$start+1 ) ];
481             }
482             elsif( $argindex =~ m/^-?\d+$/ ) {
483 40         94 $value = $self->arg( $argindex );
484             }
485             elsif( $argindex =~ m/^(-?\d+)\@$/ ) {
486 1         3 $value = [ split ' ', $self->arg( $1 ) ];
487             }
488             else {
489 0         0 die "Unrecognised argument specification $argindex";
490             }
491              
492 46         129 $named_args{$name} = $value;
493             }
494              
495 23         100 return \%named_args;
496             }
497              
498             =head2 gate_disposition
499              
500             $disp = $message->gate_disposition
501              
502             Returns the "gating disposition" of the message. This defines how a reply
503             message from the server combines with other messages in response of a command
504             sent by the client. The disposition is either C, or a string consisting
505             of a type symbol and a gate name. If defined, the symbol defines what effect
506             it has on the gate name.
507              
508             =over 4
509              
510             =item -GATE
511              
512             Adds more information to the response for that gate, but doesn't yet complete
513             it.
514              
515             =item +GATE
516              
517             Completes the gate with a successful result.
518              
519             =item !GATE
520              
521             Completes the gate with a failure result.
522              
523             =back
524              
525             =cut
526              
527             my %GATE_DISPOSITIONS;
528              
529             sub gate_disposition
530             {
531 18     18 1 35 my $self = shift;
532 18         42 return $GATE_DISPOSITIONS{ $self->command };
533             }
534              
535             =head1 AUTHOR
536              
537             Paul Evans
538              
539             =cut
540              
541             local $_;
542             while( ) {
543             chomp;
544             my ( $numname, $args, $gating ) = split m/\s*\|\s*/, $_ or next;
545             my ( $num, $name ) = split m/=/, $numname;
546              
547             my $index = 0;
548             my %args = map {
549             if( m/^(.*)=(.*)$/ ) {
550             $index = $1;
551             ( $2 => $1 )
552             }
553             else {
554             ( $_ => ++$index );
555             }
556             } split m/,/, $args;
557              
558             $NUMERIC_NAMES{$num} = $name;
559             $ARG_NAMES{$num} = \%args;
560             $GATE_DISPOSITIONS{$num} = $gating if defined $gating;
561             }
562             close DATA;
563              
564             0x55AA;
565              
566             # And now the actual numeric definitions, given in columns
567             # number=NAME | argname,argname,argname
568              
569             # arg may be position=argname
570              
571             # See also
572             # http://www.alien.net.au/irc/irc2numerics.html
573              
574             __DATA__