File Coverage

blib/lib/Mojo/SlackRTM.pm
Criterion Covered Total %
statement 18 135 13.3
branch 0 30 0.0
condition 0 12 0.0
subroutine 6 25 24.0
pod 13 13 100.0
total 37 215 17.2


line stmt bran cond sub pod time code
1             package Mojo::SlackRTM;
2 1     1   16985 use Mojo::Base 'Mojo::EventEmitter';
  1         8696  
  1         5  
3              
4 1     1   2379 use IO::Socket::SSL;
  1         68002  
  1         7  
5 1     1   693 use Mojo::IOLoop;
  1         64810  
  1         5  
6 1     1   513 use Mojo::Log;
  1         1297  
  1         7  
7 1     1   576 use Mojo::UserAgent;
  1         98517  
  1         9  
8              
9 1     1   42 use constant DEBUG => $ENV{MOJO_SLACKRTM_DEBUG};
  1         2  
  1         1863  
10              
11             our $VERSION = '0.02';
12              
13             has ioloop => sub { Mojo::IOLoop->singleton };
14             has ua => sub { Mojo::UserAgent->new };
15             has log => sub { Mojo::Log->new };
16             has "token";
17             has "pinger";
18             has 'ws';
19             has 'auto_reconnect' => 1;
20              
21             our $SLACK_URL = "https://slack.com/api";
22              
23             sub _dump {
24 0     0     shift;
25 0           require Data::Dumper;
26 0           local $Data::Dumper::Sortkeys = 1;
27 0           local $Data::Dumper::Indent = 1;
28 0           local $Data::Dumper::Terse = 1;
29 0           my $dump = Data::Dumper::Dumper(@_);
30 0 0         if (-t STDOUT) {
31 0           warn " \e[33m$_\e[m\n" for split /\n/, $dump;
32             } else {
33 0           warn " $_\n" for split /\n/, $dump;
34             }
35             }
36              
37             my $TX_ERROR = sub {
38             my $tx = shift;
39             return if $tx->success and $tx->res->json("/ok");
40             if ($tx->success) {
41             my $error = $tx->res->json("/error") || "Unknown error";
42             return $error;
43             } else {
44             my $error = $tx->error;
45             return $error->{code} ? "$error->{code} $error->{message}" : $error->{message};
46             }
47             };
48              
49             sub metadata {
50 0     0 1   my $self = shift;
51 0 0         return $self->{_metadata} unless @_;
52 0           my $metadata = shift;
53 0           $self->{_metadata} = $metadata;
54 0 0         unless ($metadata) {
55 0           $self->{$_} = undef for qw(_users _channels);
56 0           return;
57             }
58             $self->{_users} = [
59 0           +{ map { ($_->{id}, $_->{name}) } @{$metadata->{users}} },
  0            
60 0           +{ map { ($_->{name}, $_->{id}) } @{$metadata->{users}} },
  0            
  0            
61             ];
62             $self->{_channels} = [
63 0           +{ map { ($_->{id}, $_->{name}) } @{$metadata->{channels}} },
  0            
64 0           +{ map { ($_->{name}, $_->{id}) } @{$metadata->{channels}} },
  0            
  0            
65             ];
66 0           $metadata;
67             }
68             sub next_id {
69 0     0 1   my $self = shift;
70 0   0       $self->{_id} //= 0;
71 0           ++$self->{_id};
72             }
73              
74             sub start {
75 0     0 1   my $self = shift;
76 0           $self->connect;
77 0 0         $self->ioloop->start unless $self->ioloop->is_running;
78             }
79              
80             sub connect {
81 0     0 1   my $self = shift;
82 0 0         my $token = $self->token or die "Missing token";
83 0           my $tx = $self->ua->get("$SLACK_URL/rtm.start?token=$token");
84 0 0         if (my $error = $TX_ERROR->($tx)) {
85 0           $self->log->fatal("failed to get $SLACK_URL/rtm.start?token=XXX: $error");
86 0           return;
87             }
88 0           my $metadata = $tx->res->json;
89 0           $self->metadata($metadata);
90 0           my $url = $metadata->{url};
91             $self->ua->websocket($url => sub {
92 0     0     my ($ua, $ws) = @_;
93 0 0         unless ($ws->is_websocket) {
94 0           $self->log->fatal("$url does not return websocket connection");
95 0           return;
96             }
97 0           $self->ws($ws);
98 0           $self->pinger( $self->ioloop->recurring(10 => sub { $self->ping }) );
  0            
99             $self->ws->on(json => sub {
100 0           my ($ws, $event) = @_;
101 0           $self->_handle_event($event);
102 0           });
103             $self->ws->on(finish => sub {
104 0           my ($ws) = @_;
105 0           $self->log->warn("detect 'finish' event");
106 0           $self->finish;
107 0 0         $self->connect if $self->auto_reconnect;
108 0           });
109 0           });
110             }
111              
112             sub finish {
113 0     0 1   my $self = shift;
114 0 0         $self->ws->finish if $self->ws;
115 0           $self->_clear;
116             }
117              
118             sub reconnect {
119 0     0 1   my $self = shift;
120 0           $self->finish;
121 0           $self->connect;
122             }
123              
124             sub _clear {
125 0     0     my $self = shift;
126 0 0         if (my $pinger = $self->pinger) {
127 0           $self->ioloop->remove($pinger);
128 0           $self->pinger(undef);
129             }
130 0           $self->ws(undef);
131 0           $self->metadata(undef);
132 0           $self->{_id} = 0;
133             }
134              
135             sub _handle_event {
136 0     0     my ($self, $event) = @_;
137 0 0         if (my $type = $event->{type}) {
138 0 0 0       if ($type eq "message" and defined(my $reply_to = $event->{reply_to})) {
139 0           DEBUG and $self->log->debug("===> skip 'message' event with reply_to $reply_to");
140 0           DEBUG and $self->_dump($event);
141 0           return;
142             }
143 0           DEBUG and $self->log->debug("===> emit '$type' event");
144 0           DEBUG and $self->_dump($event);
145 0           $self->emit($type, $event);
146             } else {
147 0           DEBUG and $self->log->debug("===> got event without 'type'");
148 0           DEBUG and $self->_dump($event);
149             }
150             }
151              
152             sub ping {
153 0     0 1   my $self = shift;
154 0           my $hash = {id => $self->next_id, type => "ping"};
155 0           DEBUG and $self->log->debug("===> emit 'ping' event");
156 0           DEBUG and $self->_dump($hash);
157 0           $self->ws->send({json => $hash});
158             }
159              
160             sub find_channel_id {
161 0     0 1   my ($self, $name) = @_;
162 0           $self->{_channels}[1]{$name};
163             }
164             sub find_channel_name {
165 0     0 1   my ($self, $id) = @_;
166 0           $self->{_channels}[0]{$id};
167             }
168             sub find_user_id {
169 0     0 1   my ($self, $name) = @_;
170 0           $self->{_users}[1]{$name};
171             }
172             sub find_user_name {
173 0     0 1   my ($self, $id) = @_;
174 0           $self->{_users}[0]{$id};
175             }
176              
177             sub send_message {
178 0     0 1   my ($self, $channel, $text, %option) = @_;
179 0           my $hash = {
180             id => $self->next_id,
181             type => "message",
182             channel => $channel,
183             text => $text,
184             %option,
185             };
186 0           DEBUG and $self->log->debug("===> send message");
187 0           DEBUG and $self->_dump($hash);
188 0           $self->ws->send({json => $hash});
189             }
190              
191             sub call_api {
192 0     0 1   my ($self, $method) = (shift, shift);
193 0           my ($param, $cb);
194 0 0 0       if (@_ and ref $_[-1] eq "CODE") {
195 0           $cb = pop;
196 0           $param = shift;
197             } else {
198 0           $param = shift;
199             }
200 0   0       $param ||= +{};
201             $cb ||= sub {
202 0     0     my ($slack, $tx) = @_;
203 0 0         if (my $error = $TX_ERROR->($tx)) {
204 0           $slack->log->warn("$method: $error");
205             }
206 0   0       };
207 0 0         $param->{token} = $self->token unless exists $param->{token};
208              
209 0           DEBUG and $self->log->debug("===> call api '$method'");
210 0           my $url = "$SLACK_URL/$method";
211             $self->ua->post($url => form => $param => sub {
212 0     0     (undef, my $tx) = @_;
213 0           $cb->($self, $tx);
214 0           });
215             }
216              
217             1;
218             __END__