AnyEvent-SlackBot
view release on metacpan or search on metacpan
lib/AnyEvent/SlackBot.pm view on Meta::CPAN
required=>1,
default=>1,
);
has connection=>(
is=>'rw',
isa=>Object,
required=>0,
);
has bot_id=>(
is=>'rw',
isa=>Str,
required=>0,
);
has keep_alive_timeout =>(
is=>'ro',
isa=>Int,
requried=>1,
default=>15,
);
# This method runs after the new constructor
sub BUILD {
my ($self)=@_;
$self->{backlog}=[];
$self->{ignore}={};
$self->stats->{service_started_on}=time;
$self->stats->{running_posts}=0;
}
# this method runs before the new constructor, and can be used to change the arguments passed to the module
around BUILDARGS => sub {
my ($org,$class,@args)=@_;
return $class->$org(@args);
};
=head1 OO Methods
=over 4
=item * $self->connect_and_run
COnnects and starts running
=cut
sub connect_and_run {
my ($self)=@_;
my $request=POST $self->rtm_start_url,[token=>$self->token];
my $ua=LWP::UserAgent->new;
$ua->ssl_opts(
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
SSL_hostname => '',
verify_hostname => 0
);
my $response=$ua->request($request);
$self->{timer}=undef;
if($response->code==200) {
my $data=eval { from_json($response->decoded_content) };
if($@) {
return $self->new_false("Failed to decode response, error was: $@");
}
unless(exists $data->{url} and $data->{self}) {
my $msg=exists $data->{error} ? $data->{error} : 'unknown slack error';
return $self->new_false("Failed to get valid connection info, error was: $msg");
}
$self->build_connection($data);
} else {
return $self->new_false("Failed to get conenction info from slack, error was: ".$response->status_line);
}
}
=item * my $id=$self->next_id
Provides an id for the next message.
=cut
sub next_id {
my ($self)=@_;
return ++$self->{next_id}
}
=item * if($self->is_connected) { ... }
Denotes if we are currently connected to slack
=cut
sub is_connected {
return defined($_[0]->connection)
}
=item * $self->send($ref)
Converts $ref to json and sends it on the session.
=cut
sub send {
my ($self,$ref)=@_;
my $json=to_json($ref);
if($self->connection) {
$self->connection->send($json);
++$self->stats->{total_messages_sent};
} else {
push @{$self->{backlog}},$json;
}
}
=item * $self->send_typing($json)
Given $json sends a currently typing reply
=cut
lib/AnyEvent/SlackBot.pm view on Meta::CPAN
my (undef,$ref,$data)=@{$args};
$self->log_info("processing backlog event");
next if $self->we_sent_msg($ref);
$self->on_event->($self,$ref,$data);
}
}
});
$self->agent->run_next;
}
=item * if($self->we_sent_msg($json,$connection_data)) { ... }
When true, $json is a duplicate from something we sent
=cut
sub we_sent_msg {
my ($self,$ref,$data)=@_;
if(exists $ref->{msg}) {
my $sent=delete $self->{ignore}->{$ref->{msg}};
if(defined($sent)) {
$self->info("This is a message we sent");
$self->on_reply->($self,$ref,$data);
return 1;;
}
} elsif(exists $ref->{reply_to}) {
$self->info("This is a message we sent");
$self->on_reply->($self,$ref,$data);
return 1;
} else {
$self->debug(Dumper($ref));
}
return 0;
}
=item * $self->build_connection($connection_details)
Internal Method used for buiding connections.
=cut
sub build_connection {
my ($self,$data)=@_;
my $url=$data->{url};
$self->bot_id($data->{self}->{id});
my $client=AnyEvent::WebSocket::Client->new;
$client->connect($url)->cb(sub {
my $connection=eval { shift->recv };
$self->connection($connection);
if($@) {
$self->log_error("Failed to cnnect to our web socket, error was: $@");
return $self->handle_reconnect;
}
$self->stats->{last_connected_on}=time;
$self->stats->{total_connections}++;
$self->stats->{last_msg_on}=time;
$self->{timer}=AnyEvent->timer(
interval=>$self->keep_alive_timeout,
after=>$self->keep_alive_timeout,
cb=>sub {
my $max_timeout=$self->stats->{last_msg_on} + 3 * $self->keep_alive_timeout;
if(time < $max_timeout) {
if(time > $self->stats->{last_msg_on} + $self->keep_alive_timeout) {
$self->log_info("sending keep alive to server");
$connection->send(to_json({
id=>$self->next_id,
type=>'ping',
timestamp=>time,
}));
%{$self->{ignore}}=();
$self->on_idle->($self);
$self->stats->{last_idle_on}=time;
}
} else {
return $self->handle_reconnect;
}
}
);
$self->connection->on(finish=>sub {
return $self->handle_reconnect;
});
$self->connection->on(each_message=> sub {
my ($connection,$message)=@_;
$self->stats->{last_msg_on}=time;
$self->stats->{total_messages_recived}++;
if($message->is_text) {
my $ref=eval { from_json($message->body) };
if($@) {
$self->log_error("Failed to parse json body, error was: $@");
return $self->handle_reconnect;
}
if(exists $ref->{type} and $ref->{type} eq 'pong') {
$self->log_info("got keep alive response from server");
} else {
if($self->stats->{running_posts}!=0) {
# Don't try to handle unknown commands while we are waiting on a post to go out!
push @{$self->unknown_que},[$self,$ref,$data];
$self->log_info("HTTP Post response pending.. will hold off on responding to commands until we know if we sent it or not");
return;
} else {
return if $self->we_sent_msg($ref,$data);
$self->log_info("real time response");
$self->debug('Inbound message: ',Dumper($ref));
$self->on_event->($self,$ref,$data);
}
}
}
});
});
}
=item * $self->handle_reconnect
Internal method used to reconnect.
=cut
sub handle_reconnect {
my ($self)=@_;
$self->connection->close if $self->connection;
$self->{connection}=undef;
if($self->auto_reconnect) {
my $result=$self->connect_and_run;
if($result) {
$self->log_info("auto reconnected without an error, flushing backlog of outbound messages");
while(my $msg=shift @{$self->{backlog}}) {
$self->send($msg);
}
} else {
$self->log_error("Failed to reconnect will try again in 15 seconds, error was: $result");
$self->{timer}=AnyEvent->timer(
interval=>$self->keep_alive_timeout,
after=>$self->keep_alive_timeout,
cb=>sub { $self->handle_reconnect },
);
}
}
}
=back
=head1 See Also
The slack api documentation - L<https://api.slack.com/rtm>
The AnyEvent WebSocket Client library - L<AnyEvent::WebSocket::Client>
The AnyEvent HTTP Client library - L<AnyEvent::HTTP::MultiGet>
=head1 AUTHOR
Michael Shipper L<mailto:AKALINUX@CPAN.ORG>
=cut
1;
( run in 0.811 second using v1.01-cache-2.11-cpan-f0fbb3f571b )