Group
Extension

Slack-RTM-Bot/lib/Slack/RTM/Bot/Client.pm

package Slack::RTM::Bot::Client;

use strict;
use warnings;

use JSON;
use Encode;
use Data::Dumper;
use HTTP::Request::Common qw(POST GET);
use LWP::UserAgent;
use LWP::Protocol::https;

use Protocol::WebSocket::Client;
use IO::Socket::SSL qw/SSL_VERIFY_NONE/;

use Slack::RTM::Bot::Information;
use Slack::RTM::Bot::Response;

my $ua = LWP::UserAgent->new(
	ssl_opts => {
		verify_hostname => 0,
		SSL_verify_mode => SSL_VERIFY_NONE
	}
);
$ua->agent('Slack::RTM::Bot');

sub new {
	my $pkg = shift;
	my $self = {
		@_
	};
	die "token is required." unless $self->{token};
	return bless $self, $pkg;
}

sub connect {
	my $self = shift;
	my ($token) = @_;

	my $res = $ua->request(POST 'https://slack.com/api/rtm.connect', [ token => $token ]);
	my $content;
	eval {
		$content = JSON::from_json($res->content);
	};
	if ($@) {
		die 'connect response fail:'.Dumper $res->content;
	}
	die 'connect response fail: '.$res->content unless ($content->{ok});

	$self->{info} = Slack::RTM::Bot::Information->new(%{$content});
	$self->_connect;
}

sub _connect {
	my $self = shift;
	my ($host) = $self->{info}->{url} =~ m{wss://(.+)/websocket};
	my $socket = IO::Socket::SSL->new(
		SSL_verify_mode => SSL_VERIFY_NONE,
		PeerHost => $host,
		PeerPort => 443
	);
	$socket->blocking(0);
	$socket->connect;

	my $ws_client = Protocol::WebSocket::Client->new(url => $self->{info}->{url});
	$ws_client->{hs}->req->{max_message_size} = $self->{options}->{max_message_size} if $self->{options}->{max_message_size};
	$ws_client->{hs}->res->{max_message_size} = $self->{options}->{max_message_size} if $self->{options}->{max_message_size};
	$ws_client->on(read => sub {
			my ($cli, $buffer) = @_;
			$self->_listen($buffer);
		});
	$ws_client->on(write => sub {
			my ($cli, $buffer) = @_;
			syswrite $socket, $buffer;
		});
	$ws_client->on(connect => sub {
			print "RTM (re)connected.\n" if ($self->{options}->{debug});
		});
	$ws_client->on(error => sub {
			my ($cli, $error) = @_;
			print STDERR 'error: '. $error;
		});
	$ws_client->connect;

	$self->{ws_client} = $ws_client;
	$self->{socket} = $socket;
}

sub disconnect {
	my $self = shift;
	$self->{ws_client}->disconnect;
	undef $self;
}

sub read {
	my $self = shift;
	my $data = '';
	while (my $line = readline $self->{socket}) {
		$data .= $line;
	}
	if ($data) {
		$self->{ws_client}->read($data);
		return $data =~ /.*hello.*/;
	}
}

sub write {
	my $self = shift;
	$self->{ws_client}->write(JSON::encode_json({@_}));
}

sub find_conversation_id {
	my $self = shift;
	my ($name) = @_;
	my $id = $self->{info}->_find_conversation_id($name);
	$id ||= $self->_refetch_conversation_id($name) or die "There are no conversations of such name: $name";
	return $id;
}

sub _refetch_conversation_id {
	my $self = shift;
	my ($name) = @_;
	$self->_refetch_conversations;
	return $self->{info}->_find_conversation_id($name);
}

sub find_conversation_name {
	my $self = shift;
	my ($id) = @_;
	my $name = $self->{info}->_find_conversation_name($id);
	$name ||= $self->_refetch_conversation_name($id) or warn "There are no conversations of such id: $id";
	$name ||= $id;
	return $name;
}

sub _refetch_conversation_name {
	my $self = shift;
	my ($id) = @_;
	$self->_refetch_conversations;
	return $self->{info}->_find_conversation_name($id);
}

sub _refetch_conversations {
	my $self = shift;
	my $cursor = "";
	do {
		my $res = $ua->request(POST 'https://slack.com/api/conversations.list', [ token => $self->{token}, types => "public_channel,private_channel,im", cursor => $cursor ]);
		my $content;
		eval {
			$content = JSON::decode_json($res->content);
		};
		if ($@) {
			die 'connect response fail:' . Dumper $res->content;
		}
		die 'connect response fail: ' . $res->content unless ($content->{ok});

		for my $channel (@{$content->{channels}}) {
			if ($channel->{is_im}) {
				my $user_id = $channel->{user};
				my $name = $self->{info}->_find_user_name($user_id);
				$name ||= $self->_refetch_user_name($user_id) or warn "There are no users of such id: $user_id";
				$self->{info}->{channels}->{$channel->{id}} = { %$channel, name => '@'.$name };
				next;
			}
			$self->{info}->{channels}->{$channel->{id}} = $channel;
		}

		$cursor = $content->{response_metadata}->{next_cursor};
	} until ($cursor eq "");
}

sub find_user_name {
	my $self = shift;
	my ($id) = @_;
	my $name = $self->{info}->_find_user_name($id);
	$name ||= $self->_refetch_user_name($id) or warn "There are no users of such id: $id";
	$name ||= $id;
	return $name;
}

sub _refetch_user_id {
	my $self = shift;
	my ($name) = @_;
	$self->_refetch_users;
	return $self->{info}->_find_user_id($name);
}

sub _refetch_user_name {
	my $self = shift;
	my ($id) = @_;
	$self->_refetch_users;
	return $self->{info}->_find_user_name($id);
}

sub _refetch_users {
	my $self = shift;
	my $res;
	eval {
		my $users = {};
		my $cursor = "";
		do {
			$res = $ua->request(GET "https://slack.com/api/users.list?token=$self->{token}&cursor=$cursor");
			my $args = JSON::from_json($res->content);
			for my $user (@{$args->{members}}) {
				$users->{$user->{id}} = $user;
			}
			if (defined($args->{response_metadata}->{next_cursor})) {
				$cursor = $args->{response_metadata}->{next_cursor};
			}
		} until ($cursor eq "");
		$self->{info}->{users} = $users;
       };
       if ($@) {
	       die '_refetch_users response fail:'.Dumper $res->content;
       }
}

sub _listen {
	my $self = shift;
	my ($buffer) = @_;
	my $buffer_obj;
	eval {
		$buffer_obj = JSON::from_json($buffer);
	};
	if ($@) {
		die "response is not json string. : $buffer";
	}
	if ($buffer_obj->{type} && $buffer_obj->{type} eq 'reconnect_url') {
		$self->{info}->{url} = $buffer_obj->{url};
	}

	my ($user, $channel);
	if ($buffer_obj->{user} && !ref($buffer_obj->{user})) {
		$user = $self->find_user_name($buffer_obj->{user});
		warn "There are no users of such id: $buffer_obj->{user}" unless $user;
	}
	if ($buffer_obj->{channel} && !ref($buffer_obj->{channel})) {
		$channel = $self->find_conversation_name($buffer_obj->{channel});
		warn "There are no conversations of such id: $buffer_obj->{channel}" unless $channel;

	}
	my $response = Slack::RTM::Bot::Response->new(
		buffer  => $buffer_obj,
		user    => $user,
		channel => $channel
	);
ACTION: for my $action(@{$self->{actions}}){
		for my $key(keys %{$action->{events}}){
			my $regex = $action->{events}->{$key};
			if(!defined $response->{$key} || $response->{$key} !~ $regex){
				next ACTION;
			}
		}
		eval {
			$action->{routine}->($response);
		};
		if ($@) {
			warn $@;
			kill 9, @{$self->{pids}};
			exit(1);
		}
	}
};

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.