File Coverage

blib/lib/Telegram/Bot/Brain.pm
Criterion Covered Total %
statement 48 143 33.5
branch 8 50 16.0
condition 0 29 0.0
subroutine 12 23 52.1
pod 7 8 87.5
total 75 253 29.6


line stmt bran cond sub pod time code
1             package Telegram::Bot::Brain;
2             $Telegram::Bot::Brain::VERSION = '0.023';
3             # ABSTRACT: A base class to make your very own Telegram bot
4              
5              
6 2     2   3074 use Mojo::Base -base;
  2         400649  
  2         16  
7              
8 2     2   610 use strict;
  2         13  
  2         43  
9 2     2   11 use warnings;
  2         4  
  2         54  
10              
11 2     2   1249 use Mojo::IOLoop;
  2         343518  
  2         15  
12 2     2   1283 use Mojo::UserAgent;
  2         196392  
  2         21  
13 2     2   113 use Mojo::JSON qw/encode_json/;
  2         5  
  2         112  
14 2     2   26 use Carp qw/croak/;
  2         6  
  2         90  
15 2     2   1082 use Log::Any;
  2         17498  
  2         12  
16 2     2   105 use Data::Dumper;
  2         7  
  2         111  
17              
18 2     2   1299 use Telegram::Bot::Object::Message;
  2         7  
  2         13  
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 1338 my $self = shift;
59 2         5 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 _add_getUpdates_handler {
179 0     0   0 my $self = shift;
180              
181 0         0 my $http_active = 0;
182 0         0 my $last_update_id = -1;
183 0         0 my $token = $self->token;
184              
185             Mojo::IOLoop->recurring(0.1 => sub {
186             # do nothing if our previous longpoll is still going
187 0 0   0   0 return if $http_active;
188              
189 0         0 my $offset = $last_update_id + 1;
190 0         0 my $updateURL = "https://api.telegram.org/bot${token}/getUpdates?offset=${offset}&timeout=60";
191 0         0 $http_active = 1;
192              
193             $self->ua->get($updateURL => sub {
194 0         0 my ($ua, $tx) = @_;
195 0         0 my $res = $tx->res->json;
196 0         0 my $items = $res->{result};
197 0         0 foreach my $item (@$items) {
198 0         0 $last_update_id = $item->{update_id};
199 0         0 $self->_process_message($item);
200             }
201              
202 0         0 $http_active = 0;
203 0         0 });
204 0         0 });
205             }
206              
207             # process a message which arrived via getUpdates
208             sub _process_message {
209 6     6   2101 my $self = shift;
210 6         10 my $item = shift;
211              
212 6         11 my $update_id = $item->{update_id};
213             # There can be several types of responses. But only one response.
214             # https://core.telegram.org/bots/api#update
215 6         8 my $update;
216 6 100       27 $update = Telegram::Bot::Object::Message->create_from_hash($item->{message}, $self) if $item->{message};
217 6 100       23 $update = Telegram::Bot::Object::Message->create_from_hash($item->{edited_message}, $self) if $item->{edited_message};
218 6 50       13 $update = Telegram::Bot::Object::Message->create_from_hash($item->{channel_post}, $self) if $item->{channel_post};
219 6 50       14 $update = Telegram::Bot::Object::Message->create_from_hash($item->{edited_channel_post}, $self) if $item->{edited_channel_post};
220              
221             # if we got to this point without creating a response, it must be a type we
222             # don't handle yet
223 6 100       11 if (! $update) {
224 1         5 warn "Telegram::Bot::Brain does not know how to handle this update: " . Dumper($item);
225 1         190 return;
226             }
227              
228 5         7 foreach my $listener (@{ $self->listeners }) {
  5         13  
229             # call the listener code, supplying ourself and the update
230 8         54 $listener->($self, $update);
231             }
232             }
233              
234              
235             sub _post_request {
236 0     0     my $self = shift;
237 0           my $url = shift;
238 0   0       my $form_args = shift || {};
239              
240 0           my $res = $self->ua->post($url, form => $form_args)->result;
241 0 0         if ($res->is_success) { return $res->json->{result}; }
  0 0          
242 0           elsif ($res->is_error) { die "Failed to post: " . $res->json->{description}; }
243 0           else { die "Not sure what went wrong"; }
244             }
245              
246              
247             1;
248              
249             __END__