読者です 読者をやめる 読者になる 読者になる

handlename's blog

コード片など

IRC に issue 番号に反応する bot がいると捗る

IRC bot のはなし。 #123 みたいな文字列に反応して、

  • github の issue のタイトル
  • github の issue の URL

を返してくれる bot をつくったら思いの外便利だった。

f:id:handlename:20130612233939p:plain

#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use AnyEvent::IRC::Client;
use AnyEvent;
use Config::Pit;
use Encode;
use Furl;
use HTTP::Request;
use JSON::XS;
use Log::Minimal;

use App::Options(
    option => {
        nick         => { type => 'string', default => 'issue-bot' },
        channel      => { type => 'string' },
        join_message => { type => 'string', default => '' },
        org_name     => { type => 'string' },
        project_name => { type => 'string' },
    },
);
my %opts = %App::options;

$Log::Minimal::PRINT = sub {
    my ($time, $type, $message, $trace, $raw_message) = @_;
    print STDERR "$time [$type] $message at $trace\n";
};

my $github = pit_get('github', require => {
    username => 'github username',
    password => 'github password',
});

my $irc_conf = pit_get('irc', require => {
    host     => 'irc host',
    port     => 'irc port',
    password => 'irc password',
});

my $json = JSON::XS->new->utf8;
my $furl = Furl->new();
my $cv   = AnyEvent->condvar;
my $con  = AnyEvent::IRC::Client->new();

$con->reg_cb( connect => sub {
    my ($con, $err) = @_;

    if (defined $err) {
        warnf "connection error: %s", $err;
        $cv->send();
    }
    else {
        infof 'connected';
    }
} );

$con->reg_cb( registered => sub {
    infof 'registered';

    if ($opts{join_message}) {
        $con->send_chan(
            $opts{channel},
            'PRIVMSG',
            $opts{channel},
            $opts{join_message},
        );
    }
} );

$con->reg_cb( disconnect => sub {
    infof 'disconnected';
    $cv->broadcast();
} );

$con->reg_cb( publicmsg => sub {
    my ($con, $channel, $irc_message) = @_;

    return unless $irc_message->{command} eq 'PRIVMSG';

    my @issue_nums = $irc_message->{params}[1] =~ /#([0-9]+)/g;
    return unless @issue_nums;

    for my $issue_num (@issue_nums) {
        my $message = retrieve_issue_info($issue_num);
        next unless $message;

        infof $message;

        send_message($con, $message);
    }
} );

$con->enable_ssl();

$con->connect($irc_conf->{host}, $irc_conf->{port}, {
    nick     => $opts{nick},
    password => $irc_conf->{password},
});

$con->send_srv('JOIN', $opts{channel});

$cv->recv();

$con->disconnect();

sub retrieve_issue_info {
    my ($issue_num) = @_;

    my $url = sprintf 'https://api.github.com/repos/%s/%s/issues/%d',
                      $opts{org_name},
                      $opts{project_name},
                      $issue_num;
    my $req = HTTP::Request->new( GET => $url );
    $req->authorization_basic(
        $github->{username},
        $github->{password},
    );

    my $res = $furl->request($req);

    if ($res->status != 200) {
        warnf '#%d %s', $issue_num, $res->content;
        return;
    }

    my $issue = $json->decode($res->content);

    return encode_utf8(sprintf(
        '#%d %s %s',
        $issue->{number},
        $issue->{title},
        $issue->{html_url},
    ));
}

sub send_message {
    my ($con, $message) = @_;

    $con->send_chan(
        $opts{channel},
        'NOTICE',
        $opts{channel},
        $message,
    );
}