File Coverage

blib/lib/Slack/RTM/Bot/Client.pm
Criterion Covered Total %
statement 85 186 45.7
branch 16 48 33.3
condition 13 30 43.3
subroutine 17 32 53.1
pod 0 8 0.0
total 131 304 43.0


line stmt bran cond sub pod time code
1             package Slack::RTM::Bot::Client;
2            
3 8     8   58 use strict;
  8         17  
  8         241  
4 8     8   44 use warnings;
  8         15  
  8         254  
5            
6 8     8   43 use JSON;
  8         15  
  8         42  
7 8     8   4541 use Encode;
  8         119102  
  8         641  
8 8     8   4743 use Data::Dumper;
  8         49655  
  8         516  
9 8     8   4049 use HTTP::Request::Common qw(POST GET);
  8         186412  
  8         604  
10 8     8   5477 use LWP::UserAgent;
  8         232344  
  8         334  
11 8     8   3840 use LWP::Protocol::https;
  8         707770  
  8         369  
12            
13 8     8   3613 use Protocol::WebSocket::Client;
  8         173695  
  8         308  
14 8     8   65 use IO::Socket::SSL qw/SSL_VERIFY_NONE/;
  8         18  
  8         82  
15            
16 8     8   4276 use Slack::RTM::Bot::Information;
  8         24  
  8         364  
17 8     8   3575 use Slack::RTM::Bot::Response;
  8         22  
  8         16023  
18            
19             my $ua = LWP::UserAgent->new(
20             ssl_opts => {
21             verify_hostname => 0,
22             SSL_verify_mode => SSL_VERIFY_NONE
23             }
24             );
25             $ua->agent('Slack::RTM::Bot');
26            
27             sub new {
28 3     3 0 24 my $pkg = shift;
29 3         15 my $self = {
30             @_
31             };
32 3 50       15 die "token is required." unless $self->{token};
33 3         10 return bless $self, $pkg;
34             }
35            
36             sub connect {
37 1     1 0 2 my $self = shift;
38 1         3 my ($token) = @_;
39            
40 1         17 my $res = $ua->request(POST 'https://slack.com/api/rtm.connect', [ token => $token ]);
41 1         219834 my $content;
42 1         3 eval {
43 1         9 $content = JSON::from_json($res->content);
44             };
45 1 50       76 if ($@) {
46 0         0 die 'connect response fail:'.Dumper $res->content;
47             }
48 1 50       46 die 'connect response fail: '.$res->content unless ($content->{ok});
49            
50 0         0 $self->{info} = Slack::RTM::Bot::Information->new(%{$content});
  0         0  
51 0         0 $self->_connect;
52             }
53            
54             sub _connect {
55 0     0   0 my $self = shift;
56 0         0 my ($host) = $self->{info}->{url} =~ m{wss://(.+)/websocket};
57 0         0 my $socket = IO::Socket::SSL->new(
58             SSL_verify_mode => SSL_VERIFY_NONE,
59             PeerHost => $host,
60             PeerPort => 443
61             );
62 0         0 $socket->blocking(0);
63 0         0 $socket->connect;
64            
65 0         0 my $ws_client = Protocol::WebSocket::Client->new(url => $self->{info}->{url});
66 0 0       0 $ws_client->{hs}->req->{max_message_size} = $self->{options}->{max_message_size} if $self->{options}->{max_message_size};
67 0 0       0 $ws_client->{hs}->res->{max_message_size} = $self->{options}->{max_message_size} if $self->{options}->{max_message_size};
68             $ws_client->on(read => sub {
69 0     0   0 my ($cli, $buffer) = @_;
70 0         0 $self->_listen($buffer);
71 0         0 });
72             $ws_client->on(write => sub {
73 0     0   0 my ($cli, $buffer) = @_;
74 0         0 syswrite $socket, $buffer;
75 0         0 });
76             $ws_client->on(connect => sub {
77 0 0   0   0 print "RTM (re)connected.\n" if ($self->{options}->{debug});
78 0         0 });
79             $ws_client->on(error => sub {
80 0     0   0 my ($cli, $error) = @_;
81 0         0 print STDERR 'error: '. $error;
82 0         0 });
83 0         0 $ws_client->connect;
84            
85 0         0 $self->{ws_client} = $ws_client;
86 0         0 $self->{socket} = $socket;
87             }
88            
89             sub disconnect {
90 0     0 0 0 my $self = shift;
91 0         0 $self->{ws_client}->disconnect;
92 0         0 undef $self;
93             }
94            
95             sub read {
96 0     0 0 0 my $self = shift;
97 0         0 my $data = '';
98 0         0 while (my $line = readline $self->{socket}) {
99 0         0 $data .= $line;
100             }
101 0 0       0 if ($data) {
102 0         0 $self->{ws_client}->read($data);
103 0         0 return $data =~ /.*hello.*/;
104             }
105             }
106            
107             sub write {
108 0     0 0 0 my $self = shift;
109 0         0 $self->{ws_client}->write(JSON::encode_json({@_}));
110             }
111            
112             sub find_conversation_id {
113 0     0 0 0 my $self = shift;
114 0         0 my ($name) = @_;
115 0         0 my $id = $self->{info}->_find_conversation_id($name);
116 0 0 0     0 $id ||= $self->_refetch_conversation_id($name) or die "There are no conversations of such name: $name";
117 0         0 return $id;
118             }
119            
120             sub _refetch_conversation_id {
121 0     0   0 my $self = shift;
122 0         0 my ($name) = @_;
123 0         0 $self->_refetch_conversations;
124 0         0 return $self->{info}->_find_conversation_id($name);
125             }
126            
127             sub find_conversation_name {
128 1     1 0 2 my $self = shift;
129 1         3 my ($id) = @_;
130 1         5 my $name = $self->{info}->_find_conversation_name($id);
131 1 50 33     4 $name ||= $self->_refetch_conversation_name($id) or warn "There are no conversations of such id: $id";
132 1   33     3 $name ||= $id;
133 1         2 return $name;
134             }
135            
136             sub _refetch_conversation_name {
137 0     0   0 my $self = shift;
138 0         0 my ($id) = @_;
139 0         0 $self->_refetch_conversations;
140 0         0 return $self->{info}->_find_conversation_name($id);
141             }
142            
143             sub _refetch_conversations {
144 0     0   0 my $self = shift;
145 0         0 my $cursor = "";
146 0         0 do {
147 0         0 my $res = $ua->request(POST 'https://slack.com/api/conversations.list', [ token => $self->{token}, types => "public_channel,private_channel,im", cursor => $cursor ]);
148 0         0 my $content;
149 0         0 eval {
150 0         0 $content = JSON::decode_json($res->content);
151             };
152 0 0       0 if ($@) {
153 0         0 die 'connect response fail:' . Dumper $res->content;
154             }
155 0 0       0 die 'connect response fail: ' . $res->content unless ($content->{ok});
156            
157 0         0 for my $channel (@{$content->{channels}}) {
  0         0  
158 0 0       0 if ($channel->{is_im}) {
159 0         0 my $user_id = $channel->{user};
160 0         0 my $name = $self->{info}->_find_user_name($user_id);
161 0 0 0     0 $name ||= $self->_refetch_user_name($user_id) or warn "There are no users of such id: $user_id";
162 0         0 $self->{info}->{channels}->{$channel->{id}} = { %$channel, name => '@'.$name };
163 0         0 next;
164             }
165 0         0 $self->{info}->{channels}->{$channel->{id}} = $channel;
166             }
167            
168 0         0 $cursor = $content->{response_metadata}->{next_cursor};
169             } until ($cursor eq "");
170             }
171            
172             sub find_user_name {
173 1     1 0 3 my $self = shift;
174 1         2 my ($id) = @_;
175 1         4 my $name = $self->{info}->_find_user_name($id);
176 1 50 33     5 $name ||= $self->_refetch_user_name($id) or warn "There are no users of such id: $id";
177 1   33     2 $name ||= $id;
178 1         2 return $name;
179             }
180            
181             sub _refetch_user_id {
182 0     0   0 my $self = shift;
183 0         0 my ($name) = @_;
184 0         0 $self->_refetch_users;
185 0         0 return $self->{info}->_find_user_id($name);
186             }
187            
188             sub _refetch_user_name {
189 0     0   0 my $self = shift;
190 0         0 my ($id) = @_;
191 0         0 $self->_refetch_users;
192 0         0 return $self->{info}->_find_user_name($id);
193             }
194            
195             sub _refetch_users {
196 0     0   0 my $self = shift;
197 0         0 my $res;
198 0         0 eval {
199 0         0 my $users = {};
200 0         0 my $cursor = "";
201 0         0 do {
202 0         0 $res = $ua->request(GET "https://slack.com/api/users.list?token=$self->{token}&cursor=$cursor");
203 0         0 my $args = JSON::from_json($res->content);
204 0         0 for my $user (@{$args->{members}}) {
  0         0  
205 0         0 $users->{$user->{id}} = $user;
206             }
207 0 0       0 if (defined($args->{response_metadata}->{next_cursor})) {
208 0         0 $cursor = $args->{response_metadata}->{next_cursor};
209             }
210             } until ($cursor eq "");
211 0         0 $self->{info}->{users} = $users;
212             };
213 0 0       0 if ($@) {
214 0         0 die '_refetch_users response fail:'.Dumper $res->content;
215             }
216             }
217            
218             sub _listen {
219 5     5   2014 my $self = shift;
220 5         12 my ($buffer) = @_;
221 5         9 my $buffer_obj;
222 5         10 eval {
223 5         15 $buffer_obj = JSON::from_json($buffer);
224             };
225 5 50       99 if ($@) {
226 0         0 die "response is not json string. : $buffer";
227             }
228 5 50 33     46 if ($buffer_obj->{type} && $buffer_obj->{type} eq 'reconnect_url') {
229 0         0 $self->{info}->{url} = $buffer_obj->{url};
230             }
231            
232 5         12 my ($user, $channel);
233 5 100 66     20 if ($buffer_obj->{user} && !ref($buffer_obj->{user})) {
234 1         4 $user = $self->find_user_name($buffer_obj->{user});
235 1 50       4 warn "There are no users of such id: $buffer_obj->{user}" unless $user;
236             }
237 5 100 100     19 if ($buffer_obj->{channel} && !ref($buffer_obj->{channel})) {
238 1         4 $channel = $self->find_conversation_name($buffer_obj->{channel});
239 1 50       3 warn "There are no conversations of such id: $buffer_obj->{channel}" unless $channel;
240            
241             }
242 5         25 my $response = Slack::RTM::Bot::Response->new(
243             buffer => $buffer_obj,
244             user => $user,
245             channel => $channel
246             );
247 5         24 ACTION: for my $action(@{$self->{actions}}){
  5         17  
248 10         18 for my $key(keys %{$action->{events}}){
  10         27  
249 10         16 my $regex = $action->{events}->{$key};
250 10 100 100     66 if(!defined $response->{$key} || $response->{$key} !~ $regex){
251 5         24 next ACTION;
252             }
253             }
254 5         12 eval {
255 5         13 $action->{routine}->($response);
256             };
257 5 50       1447 if ($@) {
258 0           warn $@;
259 0           kill 9, @{$self->{pids}};
  0            
260 0           exit(1);
261             }
262             }
263             };
264            
265             1;