File Coverage

blib/lib/Telegram/Bot/Brain.pm
Criterion Covered Total %
statement 43 139 30.9
branch 5 50 10.0
condition 0 29 0.0
subroutine 11 22 50.0
pod 7 8 87.5
total 66 248 26.6


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