File Coverage

blib/lib/Bot/ChatBots/Telegram/LongPoll.pm
Criterion Covered Total %
statement 67 82 81.7
branch 9 24 37.5
condition 3 9 33.3
subroutine 17 18 94.4
pod 5 5 100.0
total 101 138 73.1


line stmt bran cond sub pod time code
1             package Bot::ChatBots::Telegram::LongPoll;
2 2     2   2190 use strict;
  2         6  
  2         62  
3 2     2   11 use warnings;
  2         13  
  2         101  
4             { our $VERSION = '0.013'; }
5              
6 2     2   13 use Ouch;
  2         4  
  2         18  
7 2     2   152 use Try::Tiny;
  2         5  
  2         113  
8 2     2   491 use Log::Any qw< $log >;
  2         8879  
  2         15  
9 2     2   2473 use Mojo::IOLoop ();
  2         6  
  2         28  
10 2     2   10 use IO::Socket::SSL (); # just to be sure to complain loudly in case
  2         6  
  2         47  
11 2     2   11 use List::Util qw< max >;
  2         5  
  2         145  
12 2     2   16 use Data::Dumper;
  2         6  
  2         126  
13              
14 2     2   13 use Moo;
  2         6  
  2         11  
15 2     2   800 use namespace::clean;
  2         12  
  2         16  
16              
17             with 'Bot::ChatBots::Telegram::Role::Source'; # normalize_record, token
18             with 'Bot::ChatBots::Role::Source'; # processor, typename
19              
20             has connect_timeout => (
21             is => 'ro',
22             default => sub { return 20 },
23             );
24              
25             has interval => (
26             is => 'ro',
27             default => sub { return 0.1 },
28             );
29              
30             has max_redirects => (
31             is => 'ro',
32             default => sub { return 5 },
33             );
34              
35             has _start => (
36             is => 'ro',
37             default => sub { return 1 },
38             init_arg => 'start',
39             );
40              
41             has update_timeout => (
42             is => 'ro',
43             default => sub { return 300 },
44             );
45              
46             sub BUILD {
47 1     1 1 11 my $self = shift;
48 1 50       12 $self->start if $self->_start;
49             }
50              
51             sub class_custom_pairs {
52 1     1 1 28 my $self = shift;
53 1         26 return (token => $self->token);
54             }
55              
56             sub parse_response {
57 1     1 1 126 my ($self, $res, $threshold_id) = @_;
58 1   50     8 my $data = $res->json // {};
59 1   50     6 return grep { $_->{update_id} >= $threshold_id } @{$data->{result}//[]}
  1         5  
60 1 50       109 if $data->{ok}; # boolean flag from Telegram API
61              
62 0   0     0 my $error = $data->{description} // 'unknown error';
63 0         0 $log->error('getUpdates error: ' . $error);
64 0 0       0 if ($log->is_trace) {
65 0         0 local $Data::Dumper::Indent = 1;
66 0         0 for ([json => $data], [res => $res]) {
67 0         0 (my $d = Dumper $_->[1]) =~ s{\A.*?=}{$_->[0] =>}mxs;
68 0         0 $log->trace($d);
69             }
70             }
71 0         0 return;
72             }
73              
74             sub poller {
75 1     1 1 757 my $self = shift;
76 1 50 33     8 my $args = (@_ && ref($_[0])) ? $_[0] : {@_};
77              
78 1         6 my $update_timeout = $self->update_timeout;
79 1         6 my %query = (
80             offset => 0,
81             telegram_method => 'getUpdates',
82             timeout => $update_timeout,
83             );
84              
85 1         26 my $sender = $self->sender;
86 1         28 $sender->telegram->agent->connect_timeout($self->connect_timeout)
87             ->inactivity_timeout($update_timeout + 5)
88             ->max_redirects($self->max_redirects);
89              
90             # this flag tells us whether we're in a call already, avoiding
91             # duplicates. It is set before sending a request, and reset when the
92             # response is managed
93 1         524 my $is_busy;
94              
95             my $on_data = sub {
96 1     1   3155 my ($ua, $tx) = @_;
97              
98 1         2 my @updates;
99             try {
100 1         127 @updates = $self->parse_response($tx->res, $query{offset});
101             }
102             catch {
103 0         0 $log->error(bleep $_);
104 0 0       0 die $_ if $self->should_rethrow($args);
105 1         10 };
106              
107 1         32 my @retval = $self->process_updates(
108             refs => {
109             sender => $sender,
110             tx => $tx,
111             ua => $ua,
112             },
113             source_pairs => {
114             query => \%query,
115             },
116             updates => \@updates,
117             %$args, # may override it all!
118             );
119              
120 1         41 for my $item (@retval) {
121 1 50       4 next unless defined $item;
122 1 50       4 defined(my $record = $item->{record}) or next;
123 1 50       6 defined(my $outcome = $item->{outcome}) or next;
124 1 50       6 defined(my $message = $outcome->{send_response}) or next;
125 0         0 $sender->send_message($message, record => $record);
126             }
127              
128             # if we get here, somehow me managed to get past this call... Get
129             # ready for the next one. Just to be on the safe side, we will
130             # advance $query{offset} anyway
131 1 50       20 $query{offset} = 1 + max map { $_->{update_id} } @updates
  1         6  
132             if @updates;
133 1         7 $is_busy = 0;
134 1         6 };
135              
136             return sub {
137 1 50   1   1017 return if $is_busy;
138 1         3 $is_busy = 1; # $on_data below will reset $is_busy when ready
139 1         7 $sender->send_message(\%query, callback => $on_data);
140 1         7 };
141             } ## end sub callback
142              
143             around process => sub {
144             my ($orig, $self, $record) = @_;
145             my $outcome = $orig->($self, $record);
146             $record->{source}{query}{offset} = $record->{update}{update_id} + 1;
147             return $outcome;
148             };
149              
150             sub start {
151 0     0 1   my $self = shift;
152 0           Mojo::IOLoop->recurring($self->interval, $self->poller(@_));
153 0 0         Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
154 0           return $self;
155             }
156              
157             1;