File Coverage

blib/lib/Bot/Backbone/Message.pm
Criterion Covered Total %
statement 111 117 94.8
branch 27 32 84.3
condition 26 39 66.6
subroutine 24 26 92.3
pod 15 16 93.7
total 203 230 88.2


line stmt bran cond sub pod time code
1             package Bot::Backbone::Message;
2             $Bot::Backbone::Message::VERSION = '0.160630';
3 4     4   2585 use v5.10;
  4         10  
4 4     4   14 use Moose;
  4         3  
  4         22  
5              
6 4     4   17060 use Bot::Backbone::Identity;
  4         284  
  4         111  
7 4     4   15 use Bot::Backbone::Types qw( VolumeLevel );
  4         94  
  4         51  
8 4     4   4703 use List::MoreUtils qw( all );
  4         4  
  4         46  
9 4     4   1252 use Scalar::Util qw( blessed );
  4         5  
  4         592  
10              
11             # ABSTRACT: Describes a message or response
12              
13              
14             has chat => (
15                 is => 'ro',
16                 does => 'Bot::Backbone::Service::Role::Chat',
17                 required => 1,
18                 weak_ref => 1,
19             );
20              
21              
22             has from => (
23                 is => 'rw',
24                 isa => 'Bot::Backbone::Identity',
25                 required => 1,
26                 handles => {
27                     'is_from_me' => 'is_me',
28                 },
29             );
30              
31              
32             has to => (
33                 is => 'rw',
34                 isa => 'Maybe[Bot::Backbone::Identity]',
35                 required => 1,
36                 handles => {
37                     '_is_to_me' => 'is_me',
38                 },
39             );
40              
41              
42             has group => (
43                 is => 'rw',
44                 isa => 'Maybe[Str]',
45                 required => 1,
46             );
47              
48              
49             has volume => (
50                 is => 'ro',
51                 isa => VolumeLevel,
52                 required => 1,
53                 default => 'spoken',
54             );
55              
56              
57             has text => (
58                 is => 'rw',
59                 isa => 'Str',
60                 required => 1,
61             );
62              
63             {
64                 package Bot::Backbone::Message::Arg;
65             $Bot::Backbone::Message::Arg::VERSION = '0.160630';
66 4     4   15 use Moose;
  4         5  
  4         15  
67              
68                 has [ qw( text original ) ] => (
69                     is => 'rw',
70                     isa => 'Str',
71                     required => 1,
72                 );
73              
74                 sub clone {
75 470     470 0 390         my $self = shift;
76 470         10854         Bot::Backbone::Message::Arg->new(
77                         original => $self->original,
78                         text => $self->text,
79                     );
80                 }
81              
82                 __PACKAGE__->meta->make_immutable;
83             }
84              
85              
86             has args => (
87                 is => 'rw',
88                 isa => 'ArrayRef[Bot::Backbone::Message::Arg]',
89                 required => 1,
90                 lazy_build => 1,
91                 predicate => 'has_args',
92                 traits => [ 'Array' ],
93                 handles => {
94                     'all_args' => 'elements',
95                     'shift_args' => 'shift',
96                     'unshift_args' => 'unshift',
97                     'pop_args' => 'pop',
98                     'push_args' => 'push',
99                     'has_more_args' => 'count',
100                 },
101             );
102              
103             sub _build_args {
104 67     67   63     my $self = shift;
105              
106 67         56     my @args;
107 67         1462     my $source = $self->text;
108 67         60     my $original = '';
109 67         51     my $current = '';
110 67         45     my $quote_mark;
111 67         115     while (length $source > 0) {
112 578         553         my $next_char = substr $source, 0, 1, '';
113              
114             # Handle "... '... (... [... {...
115 578 100 100     4019         if ($original =~ /^\s*$/ and $next_char =~ /['"\(\[\{]/) {
    100 66        
    100 66        
    100 100        
    100 100        
      66        
116 1         1             $original .= $next_char;
117 1         3             $quote_mark = $next_char;
118                     }
119              
120             # Handle ..." ...' ...) ...] ...}
121                     elsif (defined $quote_mark
122                        and (($quote_mark =~ /(['"])/ and $next_char eq $1)
123                         or ($quote_mark eq '(' and $next_char eq ')')
124                         or ($quote_mark eq '[' and $next_char eq ']')
125                         or ($quote_mark eq '{' and $next_char eq '}'))) {
126              
127 5         4             $original .= $next_char;
128              
129 5         128             push @args, Bot::Backbone::Message::Arg->new(
130                             text => $current,
131                             original => $original,
132                         );
133              
134 5         7             $original = '';
135 5         4             $current = '';
136 5         8             undef $quote_mark;
137                     }
138              
139             # Handle quoted whitespace
140                     elsif (defined $quote_mark and $next_char =~ /\s/) {
141 5         3             $original .= $next_char;
142 5         8             $current .= $next_char;
143                     }
144              
145             # Handle leading or trailing whitespace
146                     elsif ($next_char =~ /\s/) {
147 58         93             $original .= $next_char;
148                     }
149              
150             # Handle word breaks: non-quote chars
151                     elsif (not defined $quote_mark and $original =~ /\S\s+/
152                                                    and $next_char =~ /\S/) {
153              
154 41         1020             push @args, Bot::Backbone::Message::Arg->new(
155                             text => $current,
156                             original => $original,
157                         );
158              
159 41         42             $original = $next_char;
160 41 100       64             if ($next_char =~ /['"\(\[\{]/) {
161 4         5                 $current = '';
162 4         6                 $quote_mark = $next_char;
163                         }
164                         else {
165 37         29                 $current = $next_char;
166 37         71                 undef $quote_mark;
167                         }
168              
169                     }
170              
171             # Handle letters belonging to the current word
172                     else {
173 468         354             $original .= $next_char;
174 468         583             $current .= $next_char;
175                     }
176                 }
177              
178             # Tack on any trailing whitespace we've missed
179 67 50 66     160     if (@args and $original =~ /^\s+$/) {
180 0         0         $args[-1]->text($args[-1] . $original);
181                 }
182              
183             # Tack on any trailing word that needs be appended
184                 else {
185 67         1627         push @args, Bot::Backbone::Message::Arg->new(
186                         text => $current,
187                         original => $original,
188                     );
189                 }
190              
191 67         1715     return \@args;
192             }
193              
194              
195             has flags => (
196                 is => 'ro',
197                 isa => 'HashRef[Bool]',
198                 required => 1,
199                 default => sub { +{} },
200             );
201              
202              
203             has bookmarks => (
204                 is => 'ro',
205                 isa => 'ArrayRef[Bot::Backbone::Message]',
206                 required => 1,
207                 default => sub { [] },
208                 traits => [ 'Array' ],
209                 handles => {
210                     _set_bookmark => 'push',
211                     _restore_bookmark => 'pop',
212                 },
213             );
214              
215              
216             has parameters => (
217                 is => 'rw',
218                 isa => 'HashRef',
219                 required => 1,
220                 default => sub { +{} },
221                 traits => [ 'Hash' ],
222                 handles => {
223                     set_parameter => 'set',
224                     get_parameter => 'get',
225                 },
226             );
227              
228              
229 0     0 1 0 sub is_group { defined shift->group }
230 2     2 1 44 sub is_direct { defined shift->to }
231              
232              
233 24     24 1 22 sub add_flag { my $self = shift; $self->flags->{$_} = 1 for @_ }
  24         558  
234 1     1 1 2 sub add_flags { my $self = shift; $self->flags->{$_} = 1 for @_ }
  1         29  
235 1     1 1 1 sub remove_flag { my $self = shift; delete $self->flags->{$_} for @_ }
  1         29  
236 0     0 1 0 sub remove_flags { my $self = shift; delete $self->flags->{$_} for @_ }
  0         0  
237 12     12 1 315 sub has_flag { my $self = shift; all { $self->flags->{$_} } @_ }
  12     12   17  
  12         61  
238 6     6 1 176 sub has_flags { my $self = shift; all { $self->flags->{$_} } @_ }
  4     4   5  
  4         19  
239              
240              
241             sub is_to_me {
242 2     2 1 8     my $self = shift;
243 2 50       4     return '' unless $self->is_direct;
244 2         43     return $self->to->is_me;
245             }
246              
247              
248             sub set_bookmark {
249 392     392 1 294     my $self = shift;
250                 my $bookmark = Bot::Backbone::Message->new(
251                     chat => $self->chat,
252                     to => $self->to,
253                     from => $self->from,
254                     group => $self->group,
255                     text => $self->text,
256 392         8870         parameters => { %{ $self->parameters } },
  392         8660  
257                 );
258 392 100       11009     $bookmark->args([ map { $_->clone } @{ $self->args } ])
  470         763  
  350         7520  
259                     if $self->has_args;
260 392         11768     $self->_set_bookmark($bookmark);
261 392         367     return;
262             }
263              
264              
265             sub restore_bookmark {
266 392     392 1 303     my $self = shift;
267 392         11738     my $bookmark = $self->_restore_bookmark;
268 392         8451     $self->to($bookmark->to);
269 392         8668     $self->from($bookmark->from);
270 392         8668     $self->group($bookmark->group);
271 392         8831     $self->text($bookmark->text);
272 392 50 33     9687     $self->args($bookmark->args)
273                     if $self->has_args or $bookmark->has_args;
274 392         364     $self->parameters({ %{ $bookmark->parameters } });
  392         8928  
275 392         8750     return;
276             }
277              
278              
279             sub set_bookmark_do {
280 392     392 1 359     my ($self, $code) = @_;
281 392         460     $self->set_bookmark;
282 392         781     my $result = $code->();
283 392         4872     $self->restore_bookmark;
284 392         986     return $result;
285             }
286              
287              
288             sub match_next {
289 190     190 1 188     my ($self, $match) = @_;
290              
291 190 100       344     $match = quotemeta $match unless ref $match;
292              
293 190 100 100     5366     if ($self->has_more_args and $self->args->[0]->text =~ /^$match$/) {
294 25         747         my $arg = $self->shift_args;
295 25         565         $self->text(substr $self->text, length $arg->original);
296 25         612         return $arg->text;
297                 }
298              
299 165         447     return;
300             }
301              
302              
303             sub match_next_original {
304 2     2 1 3     my ($self, $match) = @_;
305              
306 2         57     my $text = $self->text;
307 2 100       42     if ($text =~ s/^($match)//) {
308 1         3         my $value = $1;
309 1         25         $self->text($text);
310 1 50       27         $self->args($self->_build_args) if $self->has_args; # reinit args
311 1         5         return $value;
312                 }
313              
314 1         4     return;
315             }
316              
317              
318             sub reply {
319 5     5 1 6     my ($self, $sender, $text) = @_;
320              
321 5 100 33     58     if (defined $sender and blessed $sender
    50 66        
      33        
      33        
322                        and $sender->does('Bot::Backbone::Service::Role::Sender')) {
323              
324 2         444         $sender->send_reply($self, { text => $text });
325                 }
326                 elsif (defined $sender and blessed $sender
327                         and $sender->isa('Bot::Backbone::Bot')) {
328              
329             # No warning... hmm...
330 3         936         $self->chat->send_reply($self, { text => $text });
331                 }
332                 else {
333 0                   warn "Sender given is not a sender service or a bot: $sender\n";
334 0                   $self->chat->send_reply($self, { text => $text });
335                 }
336             }
337              
338             __PACKAGE__->meta->make_immutable;
339              
340             __END__
341            
342             =pod
343            
344             =encoding UTF-8
345            
346             =head1 NAME
347            
348             Bot::Backbone::Message - Describes a message or response
349            
350             =head1 VERSION
351            
352             version 0.160630
353            
354             =head1 SYNOPSIS
355            
356             # E.g., passed in to dispatcher predicates
357             my $message = ...;
358            
359             say $message->from->nickname, ' says, "', $message->text, '"';
360            
361             my $chatroom = $message->group;
362            
363             =head1 ATTRIBUTES
364            
365             =head2 chat
366            
367             This is the L<Bot::Backbone::Service::Role::Chat> chat engine where the message
368             originated.
369            
370             =head2 from
371            
372             This is the L<Bot::Backbone::Identity> representing the user sending the
373             message.
374            
375             =head2 to
376            
377             This is C<undef> or the L<Bot::Backbone::Identity> representing hte user the
378             message is directed toward. If sent to a room or if this is a broadcast message,
379             this will be C<undef>.
380            
381             A message to a room may also be to a specific person, this may show that as
382             well.
383            
384             =head2 group
385            
386             This is the name of the chat room.
387            
388             =head2 volume
389            
390             This is the volume of the message. It must be one of the following values:
391            
392             =over
393            
394             =item shout
395            
396             This is a message sent across multiple chats and channels, typically a system message or administrator alert.
397            
398             =item spoken
399            
400             This is a message stated to all the users within the chat. This is the normal volume level.
401            
402             =item whisper
403            
404             This is a message stated to only a few users within the chat, usually just one, the recipient.
405            
406             =back
407            
408             =head2 text
409            
410             This is the message that was sent.
411            
412             =head2 args
413            
414             This is a list of "arguments" passed into the bot. Each arg is a C<Bot::Backbone::Message:Arg> object, which is a simple Moose object with only two attributes: C<text> and C<original>. The C<text> is the value of the argument and the C<original> is the original piece of the message L</text> for that value, which contains whitespace, quotation marks, etc.
415            
416             =head2 flags
417            
418             These are flags associated with the message. These may be used by dispatcher to
419             make notes about how the message has been dispatched or identifying features of
420             the message.
421            
422             See L<add_flag>, L<add_flags>, L<remove_flag>, L<remove_flags>, L<has_flag>, and
423             L<has_flags>.
424            
425             =head2 bookmarks
426            
427             When processing a dispatcher, the predicates consume parts of the message in the
428             process. This allows us to keep a stack of pass message parts in case the
429             predicate ultimately fails.
430            
431             =head2 parameters
432            
433             These are parameters assoeciated with the message created by the dispatcher
434             predicates while processing the message.
435            
436             =head2 is_group
437            
438             Returns true if this message happened in a chat group/room/channel.
439            
440             =head2 is_direct
441            
442             Returns true if this message was sent directly to the receipient.
443            
444             =head2 add_flag
445            
446             =head2 add_flags
447            
448             $message->add_flag('foo');
449             $message->add_flags(qw( bar baz ));
450            
451             Set a flag on this message.
452            
453             =head2 remove_flag
454            
455             =head2 remove_flags
456            
457             $message->remove_flag('foo');
458             $message->remove_flags(qw( bar baz ));
459            
460             Unsets a flag on this message.
461            
462             =head2 has_flag
463            
464             =head2 has_flags
465            
466             $message->has_flag('foo');
467             $message->has_flags(qw( bar baz ));
468            
469             Returns true if all the flags passed are set. Returns false if any of the flags
470             named are not set.
471            
472             =head2 is_to_me
473            
474             Returns true of the message is to me.
475            
476             =head2 set_bookmark
477            
478             $message->set_bookmark;
479            
480             Avoid using this method. See L</set_bookmark_do>.
481            
482             Saves the current message in the bookmarks stack.
483            
484             =head2 restore_bookmark
485            
486             $mesage->restore_bookmark;
487            
488             Avoid using this method. See L</set_bookmark_do>.
489            
490             Restores the bookmark on the top of the bookmarks stack. The L</to>,
491             L</from>, L</group>, L</text>, L</parameters>, and L</args> are restored. All
492             other attribute modifications will stick.
493            
494             =head2 set_bookmark_do
495            
496             $message->set_bookmark_do(sub {
497             ...
498             });
499            
500             Saves the current message on the top of the stack using L</set_bookmark>. Then,
501             it runs the given code. Afterwards, any modifications to the message will be
502             restored to the original using L</restore_bookmark>.
503            
504             =head2 match_next
505            
506             my $value = $message->match_next('!command');
507             my $value = $message->metch_next(qr{!(?:this|that)});
508            
509             Given a regular expression or string, matches that against the next argument in
510             the L</args> and strips off the match. It returns the match if the match is
511             successful or returns C<undef>. If given a regular express, the match will not
512             succeed unless it matches the entire argument (i.e., a C<^> is added to the
513             front and C<$> is added to the end).
514            
515             =head2 match_next_original
516            
517             my $value = $message->match_next_original(qr{.+});
518            
519             Given a regular expression, this will match that against the remaining unmatched
520             text (not via L</args>, but via the unparsed L</text>). A C<^> at the front of
521             the regex will be added to match against L</text>.
522            
523             If there's a match, the matching text is returned.
524            
525             =head2 reply
526            
527             $message->reply($sender, 'blah blah blah');
528            
529             Sends a reply back to the entity sending the message or the group that sent it,
530             using the chat service that created the message.
531            
532             The first argument must be a L<Bot::Backbone::Service::Role::Sender> or
533             L<Bot::Backbone::Bot>, which should be the service or bot sending the reply. The
534             send policy set for that sender will be applied. You may pass C<undef> or
535             anything else as the sender, but a warning will be issued.
536            
537             =head1 AUTHOR
538            
539             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
540            
541             =head1 COPYRIGHT AND LICENSE
542            
543             This software is copyright (c) 2016 by Qubling Software LLC.
544            
545             This is free software; you can redistribute it and/or modify it under
546             the same terms as the Perl 5 programming language system itself.
547            
548             =cut
549