Showing with 276 additions and 50 deletions.
  1. +16 −8 README.md
  2. +8 −2 lib/Logbot.pm
  3. +39 −29 lib/Logbot/Controller/Webhook.pm
  4. +7 −7 lib/Logbot/Github.pm
  5. +2 −2 lib/Logbot/IRC.pm
  6. +199 −0 t/data/push_mcl.json
  7. +3 −0 t/test_mcl_push.t
  8. +2 −2 t/test_push.t
@@ -1,26 +1,34 @@
# logbot
simple IRC bot to serve github notifications and such for the S44 project, but written (so far) in a fairly general way.

##usage
##usage
clone, create logbot.conf with contents like this:

{
github => {
webhook_secret => 'something secret that went into the GH webhook interface',
},
orgs => {
<github organization login> => {
github => {
webhook_secret => 'something secret that went into the GH webhook interface',
},
channels => [ '#channelOne', '#channelTwo' ],
},
<other github organization login> => {
... same as first, but different secret and channels
}
},

irc => {
channels => [ '#channelOne', '#channelTwo' ],
nick => 'LogBot',
user => 'logbotUser',
pass => 'something secret that identifies the user on the IRC server',
server => 'irc.coolircserver.com:6667'
}
};
then, run it with:

then, run it with:

./script/logbot daemon -m production -l http://*:8080

...a wild bot appears!

## tech
@@ -17,9 +17,15 @@ sub startup ($app) {
$c->render(status => 500, text => "server error\n");
});

my @channels;
for my $org_name (keys $app->config->{orgs}->%*) {
my $org = $app->config->{orgs}->{$org_name};
push @channels, $org->{channels}->@*;
}

$app->helper(irc => sub {
state $irc = Logbot::IRC->new(
channels => $config->{irc}->{channels},
channels => \@channels,
user => $config->{irc}->{user},
nick => $config->{irc}->{nick},
server => $config->{irc}->{server},
@@ -41,7 +47,7 @@ sub startup ($app) {
});

my $r = $app->routes;
$r->post('/event')->to('webhook#event');
$r->post('/event/:organization')->to('webhook#event');
}


@@ -10,37 +10,47 @@ use experimental qw(postderef signatures);

sub event ($c) {
my $content = $c->req->body;

my $secret = $c->config->{github}->{webhook_secret};
my $hash = hmac_sha1_hex($content, $secret);
my $signature = $c->req->headers->header('X-Hub-Signature') // '';
# thanks, github
$signature =~ s/^sha1=//g;

if ($signature and $hash eq $signature) {
my $event_type = $c->req->headers->header('X-Github-Event') // 'UNKNOWN EVENT';

Mojo::IOLoop->delay(
sub ($d) {
parse_event($event_type, $c->req->json, $d->begin(0));
},
sub ($d, $parsed_event) {
# empty message -> we decided not to notify for this event
if (scalar $parsed_event->@*) {
$c->irc->announce($parsed_event, $d->begin);
} else {
$d->pass;
my $org = $c->stash('organization');
my $org_config = $c->config->{orgs}->{$org};

if ($org_config) {
my $secret = $org_config->{github}->{webhook_secret};
my $hash = hmac_sha1_hex($content, $secret);
my $signature = $c->req->headers->header('X-Hub-Signature') // '';

# thanks, github
$signature =~ s/^sha1=//g;

if ($signature and $hash eq $signature) {
my $event_type = $c->req->headers->header('X-Github-Event') // 'UNKNOWN EVENT';

Mojo::IOLoop->delay(
sub ($d) {
parse_event($event_type, $c->req->json, $d->begin(0));
},
sub ($d, $msg_chunks) {

my $channels = $org_config->{channels} // [];
my ($has_chan, $has_message) = (scalar $msg_chunks->@*, scalar $channels->@*);

if ($has_chan and $has_message) {
$c->irc->announce($channels, $msg_chunks, $d->begin);
} else {
$d->pass;
}
},
sub ($d) {
$c->render(text => "ok thanks");
}
},
sub ($d) {
$c->render(text => "ok thanks");
}
)->catch( sub ($d, $err) {
$c->app->log->warn($err);
$c->render(status => 400, text => 'bad request/irc trouble');
})->wait;
)->catch( sub ($d, $err) {
$c->app->log->warn($err);
$c->render(status => 400, text => 'bad request/irc trouble');
})->wait;
} else {
$c->render(status => 401, text => "unauthorized");
}
} else {
$c->render(status => 401, text => "unauthorized");
$c->render(status => 400, text => "unknown organization: $org");
}
}

@@ -20,9 +20,8 @@ my %notification_blacklist = (
);

my %handlers = (
push => sub ($p, $cb) {
push => sub ($p, $repo, $cb) {
my $commit = $p->{head_commit};
my $repo = $p->{repository};
my $ref = $p->{ref} =~ s/refs\/heads\///gr;

Mojo::IOLoop->delay(
@@ -54,10 +53,9 @@ my %handlers = (
})->wait;
},

issue_comment => sub ($p, $cb) {
issue_comment => sub ($p, $repo, $cb) {
my $comment = $p->{comment};
my $issue = $p->{issue};
my $repo = $p->{repository};
Mojo::IOLoop->delay(
sub ($d) {
get_short_url($comment->{html_url}, $d->begin(0));
@@ -83,9 +81,8 @@ my %handlers = (
)->wait;
},

issues => sub ($p, $cb) {
issues => sub ($p, $repo, $cb) {
my $issue = $p->{issue};
my $repo = $p->{repository};
Mojo::IOLoop->delay(
sub ($d) {
get_short_url($issue->{html_url}, $d->begin(0));
@@ -122,8 +119,11 @@ sub get_short_url ($shortlink_target, $cb) {
}

sub parse_event ($event_type, $payload, $cb) {
my $repo = $payload->{repository};

die "unknown event: $event_type " . Dumper($payload). "\n" if not $handlers{$event_type};
$handlers{$event_type}->($payload, $cb);

$handlers{$event_type}->($payload, $repo, $cb);
}

sub _trim ($str) {
@@ -83,10 +83,10 @@ sub say ($self, $channel, $message, $cb = sub {}) {
$self->write(PRIVMSG => $channel, ":$message", $cb);
}

sub announce ($self, $message, $cb = sub {}) {
sub announce ($self, $channels, $message, $cb = sub {}) {
Mojo::IOLoop->delay(
sub ($d) {
for my $chan ($self->channels->@*) {
for my $chan ($channels->@*) {
for my $chunk ($message->@*) {
$self->say($chan, $chunk, $d->begin);
}