summaryrefslogblamecommitdiffstats
path: root/lib/Bot/Flowdock/IRC.pm
blob: 7b199e1872ceee85e89c706d85d609fe0862f293 (plain) (tree)
1
2
3
4
5
6
7
8
9



                           
         
                          
                       
                          
                             
                          





                        
                                                               


                 








                                                                       


















































                                                                   


                              


                                     


      




                 








                                              
                                                            


                                             









                                                           
 

                                                
                                      
                                   
 












                                                                             
         
                                                                   





                                                                          

              



                                                                                             
         




             







                                                   

                                 
                                                                            
      

 






                                    


                        




                                             
                                                             
 
                                        

 











                                                              
                                                        




                                                                    
                                                                       











                                                                               



                     








                                                                             



                                                                               
                                                



           







                               






                                                         

                     
                                    

                         
                                                                    

          
                            
     
 


                                                 

                                        
                         
      





















                                                



                                  
package Bot::Flowdock::IRC;
use Moose;
use MooseX::NonMoose;

use JSON;
use List::MoreUtils 'any';
use Net::Flowdock 0.03;
use Net::Flowdock::Stream;
use String::Truncate 'elide';
use WWW::Shorten 'GitHub';

extends 'Bot::BasicBot';

sub FOREIGNBUILDARGS {
    my $class = shift;
    my (%args) = @_;
    delete $args{$_} for qw(email token key organization flow);
    return %args;
}

# XXX require this for now so that we can tell which account is the bot
# (to allow us to filter those out so we don't get echos)
# ideally, there would be a better way to detect this
has email => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has token => (
    is  => 'ro',
    isa => 'Str',
    required => 1,
);

has key => (
    is  => 'ro',
    isa => 'Str',
);

has organization => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has flow => (
    is       => 'ro',
    isa      => 'Str',
    required => 1,
);

has flowdock_api => (
    is      => 'ro',
    isa     => 'Net::Flowdock',
    lazy    => 1,
    default => sub {
        my $self = shift;
        Net::Flowdock->new(
            token => $self->token,
            ($self->key ? (key => $self->key) : ()),
        );
    }
);

has flowdock_stream => (
    is      => 'ro',
    isa     => 'Net::Flowdock::Stream',
    lazy    => 1,
    default => sub {
        my $self = shift;
        Net::Flowdock::Stream->new(
            token => $self->token,
            flows => [join('/', $self->organization, $self->flow)],
        );
    },
);

has _id_map => (
    traits  => ['Hash'],
    isa     => 'HashRef[Str]',
    default => sub { {} },
    handles => {
        _set_name_for_id => 'set',
        name_from_id     => 'get',
        flowdock_names   => 'values',
    },
);

has _my_id => (
    is  => 'rw',
    isa => 'Int',
);

sub connected {
    my $self = shift;

    my $flow = $self->flowdock_api->get_flow({
        organization => $self->organization,
        flow         => $self->flow,
    });

    for my $user (@{ $flow->body->{users} }) {
        $self->_set_name_for_id($user->{id}, $user->{nick});
        if ($user->{email} eq $self->email) {
            $self->_my_id($user->{id});
        }
    }
}

sub tick {
    my $self = shift;

    for (1..20) {
        my $event = $self->flowdock_stream->get_next_event;

        last unless $event;

        next if $event->{user} == $self->_my_id;

        my $app = $event->{app} || '';
        my $type = $event->{event};

        if ($app eq 'chat') {
            if ($type eq 'message' || $type eq 'line') {
                $self->flowdock_message($event);
            }
            elsif ($type eq 'status') {
                $self->flowdock_status($event);
            }
            elsif ($type eq 'user-edit') {
                $self->flowdock_user_edit($event);
            }
            else {
                warn "Unknown chat event type $type: " . encode_json($event);
            }
        }
        elsif ($app eq 'influx') { # i think this is timeline stuff
            if ($type eq 'vcs' && any { /^github/ } @{ $event->{tags} }) {
                $self->flowdock_github_event($event);
            }
            else {
                $self->flowdock_unknown_inbox_event($event);
            }
        }
        else {
            # we don't care activity events
            if ($type ne 'activity.user') {
                warn "Unknown event type $type for unknown app $app: " . encode_json($event);
            }
        }
    }

    return 1;
}

sub flowdock_message {
    my $self = shift;
    my ($event) = @_;

    # skip if this is a message that we just sent
    return if exists $event->{external_user_name};

    my $name = $self->name_from_id($event->{user});
    $self->_say_to_channel(
        $event->{content}, $name,
        emoted => ($event->{event} eq 'line' || $event->{event} eq 'status')
    );
}

sub flowdock_status {
    my $self = shift;
    my ($event) = @_;

    $self->flowdock_message($event);
}

sub flowdock_user_edit {
    my $self = shift;
    my ($event) = @_;

    my $id = $event->{user};
    my $nick = $event->{content}{user}{nick};
    my $oldnick = $self->name_from_id($id);

    $self->_say_to_channel("$oldnick is now known as $nick");

    $self->_set_name_for_id($id, $nick);
}

sub flowdock_github_event {
    my $self = shift;
    my ($event) = @_;

    my $content = $event->{content};

    my $pusher = $content->{pusher}{name};
    $pusher = $content->{commits}[0]{author}{username} . '(?)'
        if $pusher eq 'none'; # XXX ?
    my $commits = @{ $content->{commits} };
    my $repo = $content->{repository}{name};
    (my $branch = $content->{ref}) =~ s{^refs/heads/}{};
    my $compare = makeashorterlink($content->{compare});
    my $commit_messages = join(' / ', map { $_->{message} }
                                          @{ $content->{commits} });

    my $msg = "[github] $pusher pushed $commits commit"
            . ($commits == 1 ? '' : 's')
            . " to $repo/$branch (compare $compare): $commit_messages";

    $self->_say_to_channel($msg);
}

sub flowdock_unknown_inbox_event {
    my $self = shift;
    my ($event) = @_;

    warn "unknown timeline event type $event->{event}: " . encode_json($event);
    $self->_say_to_channel("[$event->{event}] unknown");
}

sub said {
    my $self = shift;
    my ($args) = @_;

    my $address = $args->{address} || '';

    return if $address eq 'msg';

    # XXX: Bot::BasicBot does a lot of "helpful" munging of messages that we
    # receive. this is annoying for this use case. look into switching to raw
    # poco::irc at some point.
    my $msg = ($address ? "$address: " : '') . $args->{body};

    # XXX when they allow external users to post status update events, fix this
    $msg = '*' . $msg . '*'
        if $args->{emoted};

    $self->_say_to_flowdock($msg, $args->{who});

    return;
}

around emoted => sub {
    my $orig = shift;
    my $self = shift;
    my ($args) = @_;
    $args->{emoted} = 1;
    return $self->$orig($args);
};

sub nick_change {
    my $self = shift;
    my ($old, $new) = @_;

    $self->_say_to_flowdock("$old is now known as $new");
}

sub _say_to_channel {
    my $self = shift;
    my ($body, $from, %params) = @_;

    if (defined($from)) {
        $body = $params{emoted} ? "* $from $body" : "<$from> $body";
    }
    else {
        $body = "-!- $body";
    }

    $body =~ s/\n/ /g;
    $body = elide($body, 275, { at_space => 1 });

    $self->say(
        channel => ($self->channels)[0],
        body    => $body,
    );
}

sub _say_to_flowdock {
    my $self = shift;
    my ($body, $from) = @_;

    if (defined($from)) {
        $self->flowdock_api->push_chat({
            external_user_name => $from,
            content            => $body,
        });
    }
    else {
        $self->flowdock_api->send_message({
            organization => $self->organization,
            flow         => $self->flow,
            event        => 'status',
            content      => $body,
        });
    }
}

__PACKAGE__->meta->make_immutable;
no Moose;

1;