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.161950';
3 4     4   3496 use v5.10;
  4         12  
4 4     4   17 use Moose;
  4         6  
  4         27  
5              
6 4     4   20592 use Bot::Backbone::Identity;
  4         297  
  4         166  
7 4     4   19 use Bot::Backbone::Types qw( VolumeLevel );
  4         6  
  4         65  
8 4     4   5523 use List::MoreUtils qw( all );
  4         4  
  4         59  
9 4     4   1468 use Scalar::Util qw( blessed );
  4         7  
  4         717  
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.161950';
66 4     4   21 use Moose;
  4         8  
  4         18  
67              
68             has [ qw( text original ) ] => (
69             is => 'rw',
70             isa => 'Str',
71             required => 1,
72             );
73              
74             sub clone {
75 470     470 0 380 my $self = shift;
76 470         11155 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   65 my $self = shift;
105              
106 67         80 my @args;
107 67         1622 my $source = $self->text;
108 67         62 my $original = '';
109 67         60 my $current = '';
110 67         49 my $quote_mark;
111 67         113 while (length $source > 0) {
112 578         547 my $next_char = substr $source, 0, 1, '';
113              
114             # Handle "... '... (... [... {...
115 578 100 100     4132 if ($original =~ /^\s*$/ and $next_char =~ /['"\(\[\{]/) {
    100 66        
    100 66        
    100 100        
    100 100        
      66        
116 1         2 $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         125 push @args, Bot::Backbone::Message::Arg->new(
130             text => $current,
131             original => $original,
132             );
133              
134 5         6 $original = '';
135 5         2 $current = '';
136 5         10 undef $quote_mark;
137             }
138              
139             # Handle quoted whitespace
140             elsif (defined $quote_mark and $next_char =~ /\s/) {
141 5         5 $original .= $next_char;
142 5         7 $current .= $next_char;
143             }
144              
145             # Handle leading or trailing whitespace
146             elsif ($next_char =~ /\s/) {
147 58         85 $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         994 push @args, Bot::Backbone::Message::Arg->new(
155             text => $current,
156             original => $original,
157             );
158              
159 41         37 $original = $next_char;
160 41 100       73 if ($next_char =~ /['"\(\[\{]/) {
161 4         6 $current = '';
162 4         6 $quote_mark = $next_char;
163             }
164             else {
165 37         31 $current = $next_char;
166 37         60 undef $quote_mark;
167             }
168              
169             }
170              
171             # Handle letters belonging to the current word
172             else {
173 468         358 $original .= $next_char;
174 468         599 $current .= $next_char;
175             }
176             }
177              
178             # Tack on any trailing whitespace we've missed
179 67 50 66     165 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         1704 push @args, Bot::Backbone::Message::Arg->new(
186             text => $current,
187             original => $original,
188             );
189             }
190              
191 67         1705 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 50 sub is_direct { defined shift->to }
231              
232              
233 24     24 1 24 sub add_flag { my $self = shift; $self->flags->{$_} = 1 for @_ }
  24         615  
234 1     1 1 2 sub add_flags { my $self = shift; $self->flags->{$_} = 1 for @_ }
  1         31  
235 1     1 1 2 sub remove_flag { my $self = shift; delete $self->flags->{$_} for @_ }
  1         30  
236 0     0 1 0 sub remove_flags { my $self = shift; delete $self->flags->{$_} for @_ }
  0         0  
237 12     12 1 339 sub has_flag { my $self = shift; all { $self->flags->{$_} } @_ }
  12     12   19  
  12         71  
238 6     6 1 155 sub has_flags { my $self = shift; all { $self->flags->{$_} } @_ }
  4     4   5  
  4         18  
239              
240              
241             sub is_to_me {
242 2     2 1 4 my $self = shift;
243 2 50       6 return '' unless $self->is_direct;
244 2         47 return $self->to->is_me;
245             }
246              
247              
248             sub set_bookmark {
249 392     392 1 319 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         9054 parameters => { %{ $self->parameters } },
  392         8675  
257             );
258 392 100       11093 $bookmark->args([ map { $_->clone } @{ $self->args } ])
  470         761  
  350         7673  
259             if $self->has_args;
260 392         12269 $self->_set_bookmark($bookmark);
261 392         396 return;
262             }
263              
264              
265             sub restore_bookmark {
266 392     392 1 345 my $self = shift;
267 392         11813 my $bookmark = $self->_restore_bookmark;
268 392         8674 $self->to($bookmark->to);
269 392         8871 $self->from($bookmark->from);
270 392         8648 $self->group($bookmark->group);
271 392         8619 $self->text($bookmark->text);
272 392 50 33     9936 $self->args($bookmark->args)
273             if $self->has_args or $bookmark->has_args;
274 392         368 $self->parameters({ %{ $bookmark->parameters } });
  392         8810  
275 392         9072 return;
276             }
277              
278              
279             sub set_bookmark_do {
280 392     392 1 387 my ($self, $code) = @_;
281 392         486 $self->set_bookmark;
282 392         787 my $result = $code->();
283 392         5433 $self->restore_bookmark;
284 392         1081 return $result;
285             }
286              
287              
288             sub match_next {
289 190     190 1 212 my ($self, $match) = @_;
290              
291 190 100       387 $match = quotemeta $match unless ref $match;
292              
293 190 100 100     5450 if ($self->has_more_args and $self->args->[0]->text =~ /^$match$/) {
294 25         802 my $arg = $self->shift_args;
295 25         581 $self->text(substr $self->text, length $arg->original);
296 25         606 return $arg->text;
297             }
298              
299 165         464 return;
300             }
301              
302              
303             sub match_next_original {
304 2     2 1 3 my ($self, $match) = @_;
305              
306 2         56 my $text = $self->text;
307 2 100       35 if ($text =~ s/^($match)//) {
308 1         2 my $value = $1;
309 1         24 $self->text($text);
310 1 50       29 $self->args($self->_build_args) if $self->has_args; # reinit args
311 1         5 return $value;
312             }
313              
314 1         3 return;
315             }
316              
317              
318             sub reply {
319 5     5 1 8 my ($self, $sender, $text) = @_;
320              
321 5 100 33     66 if (defined $sender and blessed $sender
    50 66        
      33        
      33        
322             and $sender->does('Bot::Backbone::Service::Role::Sender')) {
323              
324 2         578 $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         1011 $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.161950
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