Group
Extension

AnySan-Provider-Slack/lib/AnySan/Provider/Slack.pm

package AnySan::Provider::Slack;
use strict;
use warnings;
our $VERSION = '0.07';

use base 'AnySan::Provider';
our @EXPORT = qw(slack);
use AnySan;
use AnySan::Receive;
use HTTP::Request::Common;
use AnyEvent::HTTP;
use AnyEvent::SlackRTM;
use JSON;
use Encode;

sub slack {
    my(%config) = @_;

    my $self = __PACKAGE__->new(
        client => undef,
        config => \%config,
    );

    # join channels
    my @channels = keys %{ $config{channels} };
    for my $channel (@channels) {
        $self->_call('channels.join', [
            name => $channel,
        ], sub {});
    }

    $self->start;

    return $self;
}

sub metadata { shift->{rtm}->metadata }

sub user {
    my ($self, $id) = @_;
    return $self->{_users}{$id};
}

sub bot {
    my ($self, $id) = @_;
    return $self->{_bots}{$id};
}

sub start {
    my $self = shift;

    my $client_opt = $self->{config}{timeout} ? { timeout => $self->{config}{timeout} } : undef;
    my $rtm = AnyEvent::SlackRTM->new($self->{config}{token}, $client_opt);
    $rtm->on('hello' => sub {
        # create hash table of users
        my $users = {};
        for my $user (@{$self->metadata->{users}}) {
            $users->{$user->{id}} ||= $user;
            $users->{$user->{name}} ||= $user;
        }
        $self->{_users} = $users;

        my $bots = {};
        for my $bot (@{$self->metadata->{bots}}) {
            $bots->{$bot->{id}} ||= $bot;
            $bots->{$bot->{name}} ||= $bot;
        }
        $self->{_bots} = $bots;
    });
    $rtm->on('message' => sub {
        my ($rtm, $message) = @_;
        my $metadata = $self->metadata or return;
        if ($message->{subtype}) {
            my $filter = $self->{config}{subtypes} || [];
            return unless grep { $_ eq 'all' || $_ eq $message->{subtype} } @$filter;
        }

        # search user nickname
        my $nickname = '';
        my $user_id = encode_utf8($message->{user} || '');
        my $user = $self->user($user_id);
        my $bot = $self->bot($user_id);
        $nickname = $user->{name} if $user;
        $nickname = $bot->{name} if $bot;

        my $receive; $receive = AnySan::Receive->new(
            provider      => 'slack',
            event         => 'message',
            message       => encode_utf8($message->{text} || ''),
            nickname      => encode_utf8($metadata->{self}{name} || ''),
            from_nickname => $nickname,
            attribute     => {
                channel => $message->{channel},
                subtype => $message->{subtype},
                user    => $user,
                bot     => $bot,
            },
            cb            => sub { $self->event_callback($receive, @_) },
        );
        AnySan->broadcast_message($receive);
    });
    $rtm->on('finish' => sub {
        # reconnect
        undef $self->{rtm};
        while (1) {
            eval { $self->start };
            last unless $@;
        }
    });
    $rtm->on('user_change' => sub {
        my ($rtm, $message) = @_;
        my $user = $message->{user};
        my $user_id = $user->{id};

        # remove from cache
        if (my $old_user = $self->{_users}{$user_id}) {
            delete $self->{_users}{$user_id};
            delete $self->{_users}{$old_user->{name}};
        }

        # add new user info to cache
        $self->{_users}{$user_id} = $user;
        $self->{_users}{$user->{name}} = $user;
    });
    $rtm->start;
    $self->{rtm} = $rtm;
}

sub event_callback {
    my($self, $receive, $type, @args) = @_;

    if ($type eq 'reply') {
        $self->_call('chat.postMessage', [
            channel    => $receive->attribute('channel'),
            text       => $args[0],
            as_user    => $self->{config}->{as_user}    ? 'true' : 'false',
            link_names => $self->{config}->{link_names} ? 'true' : 'false',
        ], sub {});
    }
}

sub send_message {
    my($self, $message, %args) = @_;

    $self->_call('chat.postMessage', [
        text       => $message,
        channel    => $args{channel},
        as_user    => $self->{config}->{as_user}    ? 'true' : 'false',
        link_names => $self->{config}->{link_names} ? 'true' : 'false',
        %{ $args{params} || +{} },
    ], sub {});
}

sub _call {
    my ($self, $method, $params, $cb) = @_;
    my $req = POST "https://slack.com/api/$method", [
        token   => $self->{config}{token},
        @$params,
    ];
    my %headers = map { $_ => $req->header($_), } $req->headers->header_field_names;
    my $jd = $self->{json_driver} ||= JSON->new->utf8;
    my $r;
    $r = http_post $req->uri, $req->content, headers => \%headers, sub {
        my $body = shift;
        my $res = $jd->decode($body);
        $cb->($res);
        undef $r;
    };
}

1;
__END__

=head1 NAME

AnySan::Provider::Slack - AnySan provider for Slack

B<THE SOFTWARE IS ALPHA QUALITY. API MAY CHANGE WITHOUT NOTICE.>

=head1 SYNOPSIS

  use AnySan;
  use AnySan::Provider::Slack;
  my $slack = slack(
      token => 'YOUR SLACK API TOKEN',
      channels => {
          'general' => {},
      },

      as_user => 0, # post messages as bot (default)
      # as_user => 1, # post messages as user

      subtypes => [], # ignore all subtypes (default)
      # subtypes => ['bot_message'], # receive messages from bot
      # subtypes => ['all'], # receive all messages(bot_message, me_message, message_changed, etc)
  );
  $slack->send_message('slack message', channel => 'C024BE91L');

  AnySan->register_listener(
      slack => {
          event => 'message',
          cb => sub {
              my $receive = shift;
              return unless $receive->message;
              warn $receive->message;
              warn $receive->attribute->{subtype};
              $receive->send_reply('hogehoge');
          },
      },
  );

=head1 AUTHOR

Ichinose Shogo E<lt>shogo82148@gmail.com E<gt>

=head1 SEE ALSO

L<AnySan>, L<AnyEvent::IRC::Client>, L<Slack API|https://api.slack.com/>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut


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