Twitter bot with AnyEvent

ニコニコ生放送のアラートサーバにつないで検索条件に引っかかるとツイートするBOT(https://twitter.com/pikipiki_bot)
AnyEventの使い方が感覚的にわかってきた感じがします。が、「こんな書き方ねーよ」という点があればコメントお願いします。
Net::Twitterはおそらくnon-blockingではないので、ツイート時にブロッキングしている可能性が高い。AnyEvent::Twitterを使うことをTodoに追加。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use Unicode::Normalize qw/NFKC/;

use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::HTTP;
use AnyEvent::Handle;

use Net::Twitter;
use XML::Simple;
use Data::Dumper;

######################
# Word initialization

my @word_list = ( # regexp
    "プログラミング", "プログラマ", 
    "C言語", 'C\+\+', "C#", "Objective-C", "COBOL",
    "D言語", "Delphi", "FORTRAN", "Groovy",
    "JavaScript", "Java", "Lua", "Pascal",
    "Perl", # "PHP", 
    "Python", "Ruby",
    "機械語", "アセンブリ", "アセンブラ", "SQL",
    "Erlang", "Haskell", "LISP", "OCaml", "Scala",
    "ActionScript", "AWK", "Smalltalk",
);

my @normalized_word_list = map {
    my $word =$_;
    $word = uc NFKC $word;
    $word =~ tr/ぁ-ん/ァ-ン/;
    $word;
} @word_list;

###################################
# Twitter setting initialization

my $config = XMLin('/home/hoge/fuga.xml') or die $!;
my $nt = Net::Twitter->new(
    traits   => [qw/API::REST/],
    username => $config->{username},
    password => $config->{password},
);

### FLOW
# get alert info
# connect to the server 
# recieve chat xml tag
# get stream info
# NFKC, uc, katakanize recieved xml
# grep recieved xml
# if matched, then tweet

my $server = get_alertinfo();

######################
# callbacks

#######################
## http_request_cb

my $http_request_cb; $http_request_cb = sub { 
    my ($body, $hdr) =@_;
    my $formed_body = decode_utf8 $body;
    $formed_body = uc NFKC $formed_body;
    $formed_body =~ tr/ぁ-ん/ァ-ン/;

    my @matched_words = grep {
        my $word = $_;
        if ($formed_body =~ /$word/) {
            $word;
        }
    } @normalized_word_list;

    if (scalar @matched_words) {
        print encode_utf8 "matched: @matched_words\n";
        my $body_xml   = XMLin(decode_utf8 $body);
        my $request_id = $body_xml->{request_id};
        my $title      = substr $body_xml->{streaminfo}->{title}, 0, 20;
        my $co_name    = substr $body_xml->{communityinfo}->{name}, 0, 20;
        my $sentence   = "#nicolive http://nico.ms/$request_id ";
        $sentence .= "【word】$matched_words[0] 【community】$co_name 【title】$title ";

        my $result = $nt->update($sentence);
        print encode_utf8 $sentence;
    }
};

#######################
## socket_read_cb

my $socket_read_cb; $socket_read_cb = sub {
    my ($handle, $chat_tag) = @_;
    #print Dumper $chat_tag;
    $chat_tag = XMLin(decode_utf8 $chat_tag);

    if (defined $chat_tag->{content}) {
        my ($lv_num, $co_num, $user_id) = split/,/, $chat_tag->{content};
        if (defined $lv_num && defined $co_num && defined $user_id) {
            my $url = 'http://live.nicovideo.jp/api/getstreaminfo/lv';
            http_request(
                GET     => "$url$lv_num",
                timeout => 3,
                $http_request_cb
            );
        }
    }
    $handle->push_read(line => "\0", $socket_read_cb);
};

##################
## connection_cb

my $connection_cb; $connection_cb = sub {
    my ($fh) = @_ or die $!;

    my $thread_tag_attr = {
        thread   => $server->{thread},
        res_from => '-1',
        version  => '20061206',
    };

    my $thread_tag = XMLout($thread_tag_attr, RootName => 'thread') . "\0";

    my $handle; $handle = new AnyEvent::Handle(
        fh       => $fh,
        on_error => sub {
            warn "Error $_[2]\n";
            $_[0]->destroy;
        },
        on_eof   => sub {
            $handle->destroy;
            warn "Done\n";
        }
    );       
    $handle->push_write($thread_tag);
    $handle->push_read(line => "\0", $socket_read_cb);
};

#########################
# main 

my $cv = AE::cv;

my $connection = tcp_connect(
    $server->{addr},
    $server->{port},
    $connection_cb,
);

$cv->recv;

####################
# function : get_alertinfo 

sub get_alertinfo
{
    my $url = 'http://live.nicovideo.jp/api/getalertinfo';

    use LWP::UserAgent;
    my $ua = LWP::UserAgent->new();
    my $res = $ua->get($url);
    
    die "$res->status_line" unless ($res->is_success);

    my $xml = XMLin($res->decoded_content);

    die "Fatal: Server status $xml->{status}" if ($xml->{status} ne 'ok');

    return $xml->{ms};
}

次のエントリーでAnyEventとブロッキングについて検証した結果を書きます。