File Coverage

blib/lib/Protocol/IRC/Message.pm
Criterion Covered Total %
statement 125 127 98.4
branch 61 68 89.7
condition 11 12 91.6
subroutine 20 20 100.0
pod 16 16 100.0
total 233 243 95.8


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