File Coverage

blib/lib/Telegram/Bot/Brain.pm
Criterion Covered Total %
statement 48 159 30.1
branch 8 56 14.2
condition 0 34 0.0
subroutine 12 24 50.0
pod 8 9 88.8
total 76 282 26.9


line stmt bran cond sub pod time code
1             package Telegram::Bot::Brain;
2             $Telegram::Bot::Brain::VERSION = '0.024';
3             # ABSTRACT: A base class to make your very own Telegram bot
4              
5              
6 2     2   2812 use Mojo::Base -base;
  2         383677  
  2         16  
7              
8 2     2   510 use strict;
  2         4  
  2         43  
9 2     2   10 use warnings;
  2         4  
  2         48  
10              
11 2     2   1006 use Mojo::IOLoop;
  2         320992  
  2         13  
12 2     2   1123 use Mojo::UserAgent;
  2         186867  
  2         22  
13 2     2   103 use Mojo::JSON qw/encode_json/;
  2         4  
  2         99  
14 2     2   19 use Carp qw/croak/;
  2         5  
  2         92  
15 2     2   1102 use Log::Any;
  2         16726  
  2         13  
16 2     2   91 use Data::Dumper;
  2         4  
  2         113  
17              
18 2     2   1152 use Telegram::Bot::Object::Message;
  2         6  
  2         11  
19              
20             # base class for building telegram robots with Mojolicious
21             has longpoll_time => 60;
22             has ua => sub { Mojo::UserAgent->new->inactivity_timeout(shift->longpoll_time + 15) };
23             has token => sub { croak "you need to supply your own token"; };
24              
25             has tasks => sub { [] };
26             has listeners => sub { [] };
27              
28             has log => sub { Log::Any->get_logger };
29              
30              
31             sub add_repeating_task {
32 0     0 1 0 my $self = shift;
33 0         0 my $seconds = shift;
34 0         0 my $task = shift;
35              
36             my $repeater = sub {
37              
38             # Perform operation every $seconds seconds
39 0     0   0 my $last_check = time();
40             Mojo::IOLoop->recurring(0.1 => sub {
41 0         0 my $loop = shift;
42 0         0 my $now = time();
43 0 0       0 return unless ($now - $last_check) >= $seconds;
44 0         0 $last_check = $now;
45 0         0 $task->($self);
46 0         0 });
47 0         0 };
48              
49             # keep a copy
50 0         0 push @{ $self->tasks }, $repeater;
  0         0  
51              
52             # kick it off
53 0         0 $repeater->();
54             }
55              
56              
57             sub add_listener {
58 2     2 1 1833 my $self = shift;
59 2         3 my $coderef = shift;
60              
61 2         4 push @{ $self->listeners }, $coderef;
  2         6  
62             }
63              
64             sub init {
65 0     0 0 0 die "init was not overridden!";
66             }
67              
68              
69             sub think {
70 0     0 1 0 my $self = shift;
71 0         0 $self->init();
72              
73 0         0 $self->_add_getUpdates_handler;
74 0 0       0 Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
75             }
76              
77              
78              
79             sub getMe {
80 0     0 1 0 my $self = shift;
81 0   0     0 my $token = $self->token || croak "no token?";
82              
83 0         0 my $url = "https://api.telegram.org/bot${token}/getMe";
84 0         0 my $api_response = $self->_post_request($url);
85              
86 0         0 return Telegram::Bot::Object::User->create_from_hash($api_response, $self);
87             }
88              
89              
90             sub sendMessage {
91 0     0 1 0 my $self = shift;
92 0   0     0 my $args = shift || {};
93              
94 0         0 my $send_args = {};
95 0 0       0 croak "no chat_id supplied" unless $args->{chat_id};
96 0         0 $send_args->{chat_id} = $args->{chat_id};
97              
98 0 0       0 croak "no text supplied" unless $args->{text};
99 0         0 $send_args->{text} = $args->{text};
100              
101             # these are optional, send if they are supplied
102 0 0       0 $send_args->{parse_mode} = $args->{parse_mode} if exists $args->{parse_mode};
103 0 0       0 $send_args->{disable_web_page_preview} = $args->{disable_web_page_preview} if exists $args->{disable_web_page_preview};
104 0 0       0 $send_args->{disable_notification} = $args->{disable_notification} if exists $args->{disable_notification};
105 0 0       0 $send_args->{reply_to_message_id} = $args->{reply_to_message_id} if exists $args->{reply_to_message_id};
106              
107             # check reply_markup is the right kind
108 0 0       0 if (exists $args->{reply_markup}) {
109 0         0 my $reply_markup = $args->{reply_markup};
110 0 0 0     0 die "bad reply_markup supplied"
      0        
      0        
111             if ( ref($reply_markup) ne 'Telegram::Bot::Object::InlineKeyboardMarkup' &&
112             ref($reply_markup) ne 'Telegram::Bot::Object::ReplyKeyboardMarkup' &&
113             ref($reply_markup) ne 'Telegram::Bot::Object::ReplyKeyboardRemove' &&
114             ref($reply_markup) ne 'Telegram::Bot::Object::ForceReply' );
115 0         0 $send_args->{reply_markup} = encode_json($reply_markup->as_hashref);
116             }
117              
118 0   0     0 my $token = $self->token || croak "no token?";
119 0         0 my $url = "https://api.telegram.org/bot${token}/sendMessage";
120 0         0 my $api_response = $self->_post_request($url, $send_args);
121              
122 0         0 return Telegram::Bot::Object::Message->create_from_hash($api_response, $self);
123             }
124              
125              
126             sub forwardMessage {
127 0     0 1 0 my $self = shift;
128 0   0     0 my $args = shift || {};
129 0         0 my $send_args = {};
130 0 0       0 croak "no chat_id supplied" unless $args->{chat_id};
131 0         0 $send_args->{chat_id} = $args->{chat_id};
132              
133 0 0       0 croak "no from_chat_id supplied" unless $args->{from_chat_id};
134 0         0 $send_args->{from_chat_id} = $args->{from_chat_id};
135              
136 0 0       0 croak "no message_id supplied" unless $args->{message_id};
137 0         0 $send_args->{message_id} = $args->{message_id};
138              
139             # these are optional, send if they are supplied
140 0 0       0 $send_args->{disable_notification} = $args->{disable_notification} if exists $args->{disable_notification};
141              
142 0   0     0 my $token = $self->token || croak "no token?";
143 0         0 my $url = "https://api.telegram.org/bot${token}/forwardMessage";
144 0         0 my $api_response = $self->_post_request($url, $send_args);
145              
146 0         0 return Telegram::Bot::Object::Message->create_from_hash($api_response, $self);
147             }
148              
149              
150             sub sendPhoto {
151 0     0 1 0 my $self = shift;
152 0   0     0 my $args = shift || {};
153 0         0 my $send_args = {};
154              
155 0 0       0 croak "no chat_id supplied" unless $args->{chat_id};
156 0         0 $send_args->{chat_id} = $args->{chat_id};
157              
158             # photo can be a string (which might be either a URL for telegram servers
159             # to fetch, or a file_id string) or a file on disk to upload - we need
160             # to handle that last case here as it changes the way we create the HTTP
161             # request
162 0 0       0 croak "no photo supplied" unless $args->{photo};
163 0 0       0 if (-e $args->{photo}) {
164 0         0 $send_args->{photo} = { photo => { file => $args->{photo} } };
165             }
166             else {
167 0         0 $send_args->{photo} = $args->{photo};
168             }
169              
170 0   0     0 my $token = $self->token || croak "no token?";
171 0         0 my $url = "https://api.telegram.org/bot${token}/sendPhoto";
172 0         0 my $api_response = $self->_post_request($url, $send_args);
173              
174 0         0 return Telegram::Bot::Object::Message->create_from_hash($api_response, $self);
175             }
176              
177              
178             sub sendDocument {
179 0     0 1 0 my $self = shift;
180 0   0     0 my $args = shift || {};
181 0         0 my $send_args = {};
182              
183 0 0       0 croak "no chat_id supplied" unless $args->{chat_id};
184 0         0 $send_args->{chat_id} = $args->{chat_id};
185              
186             # document can be a string (which might be either a URL for telegram servers
187             # to fetch, or a file_id string) or a file on disk to upload - we need
188             # to handle that last case here as it changes the way we create the HTTP
189             # request
190 0 0       0 croak "no document supplied" unless $args->{document};
191 0 0       0 if (-e $args->{document}) {
192 0         0 $send_args->{document} = { document => { file => $args->{document} } };
193             }
194             else {
195 0         0 $send_args->{document} = $args->{document};
196             }
197              
198 0   0     0 my $token = $self->token || croak "no token?";
199 0         0 my $url = "https://api.telegram.org/bot${token}/sendDocument";
200 0         0 my $api_response = $self->_post_request($url, $send_args);
201              
202 0         0 return Telegram::Bot::Object::Message->create_from_hash($api_response, $self);
203             }
204              
205              
206             sub _add_getUpdates_handler {
207 0     0   0 my $self = shift;
208              
209 0         0 my $http_active = 0;
210 0         0 my $last_update_id = -1;
211 0         0 my $token = $self->token;
212              
213             Mojo::IOLoop->recurring(0.1 => sub {
214             # do nothing if our previous longpoll is still going
215 0 0   0   0 return if $http_active;
216              
217 0         0 my $offset = $last_update_id + 1;
218 0         0 my $updateURL = "https://api.telegram.org/bot${token}/getUpdates?offset=${offset}&timeout=60";
219 0         0 $http_active = 1;
220              
221             $self->ua->get_p($updateURL)->then(sub {
222 0         0 my ($tx) = @_;
223 0         0 my $res = $tx->res->json;
224 0         0 my $items = $res->{result};
225 0         0 foreach my $item (@$items) {
226 0         0 $last_update_id = $item->{update_id};
227 0         0 $self->_process_message($item);
228             }
229              
230 0         0 $http_active = 0;
231             })->catch(sub {
232 0         0 my ($error) = @_;
233 0         0 warn "Connection error: $error";
234            
235 0         0 $http_active = 0;
236 0         0 });
237 0         0 });
238             }
239              
240             # process a message which arrived via getUpdates
241             sub _process_message {
242 6     6   1947 my $self = shift;
243 6         12 my $item = shift;
244              
245 6         10 my $update_id = $item->{update_id};
246             # There can be several types of responses. But only one response.
247             # https://core.telegram.org/bots/api#update
248 6         7 my $update;
249 6 100       26 $update = Telegram::Bot::Object::Message->create_from_hash($item->{message}, $self) if $item->{message};
250 6 100       21 $update = Telegram::Bot::Object::Message->create_from_hash($item->{edited_message}, $self) if $item->{edited_message};
251 6 50       13 $update = Telegram::Bot::Object::Message->create_from_hash($item->{channel_post}, $self) if $item->{channel_post};
252 6 50       11 $update = Telegram::Bot::Object::Message->create_from_hash($item->{edited_channel_post}, $self) if $item->{edited_channel_post};
253              
254             # if we got to this point without creating a response, it must be a type we
255             # don't handle yet
256 6 100       13 if (! $update) {
257 1         10 warn "Telegram::Bot::Brain does not know how to handle this update: " . Dumper($item);
258 1         132 return;
259             }
260              
261 5         6 foreach my $listener (@{ $self->listeners }) {
  5         14  
262             # call the listener code, supplying ourself and the update
263 8         53 $listener->($self, $update);
264             }
265             }
266              
267              
268             sub _post_request {
269 0     0     my $self = shift;
270 0           my $url = shift;
271 0   0       my $form_args = shift || {};
272              
273 0           my $res = $self->ua->post($url, form => $form_args)->result;
274 0 0         if ($res->is_success) { return $res->json->{result}; }
  0 0          
275 0           elsif ($res->is_error) { die "Failed to post: " . $res->json->{description}; }
276 0           else { die "Not sure what went wrong"; }
277             }
278              
279              
280             1;
281              
282             __END__