Twitter ボットの作り方 Perl 編 (3)

Twitter ボットの作り方解説第 3 弾。特定のワードが含まれたつぶやきに反応して、何らかの返信をするボット作ってみます。
以下は、第 2 弾までの状態を前提としています。

ボット作成の方針

基本的なボットの動作としては、まず、特定ワードの検索をして、検索結果に含まれるユーザに対して何らかの返信をするという方法になります。
ただ検索をする際、Twitter 全体を対象として検索すると、ワードによってはあり得ない件数の検索結果となってしまったり、プログラムに間違いがあった場合、不特定多数に迷惑をかけてしまう事になりかねません。
そこで検索対象としては、ボットに @ してくれた人の発言だけを対象とする事にします。
検索対象を全体に広げるのは、ボットの属性で考えたり、プログラムの正常動作を確認出来てからでいいでしょう。

また、ボットの動作は cron による一定の間隔になるので、1 回の動作の間に大量のつぶやきがあった場合、一度に取得できる検索結果件数には限度があるので、検索結果に漏れが出る可能性があります。
この漏れに対しても、厳密に検索を繰り返して遡っていく事は可能ですが、その部分には対応しない事にします。
さしあたってその程度の精度のボットを作ると思って下さい。まあ Twitter という文化からすれば許容出来る範囲でしょう。

あと返信の内容についてですが、何らかの反応があった方が面白いので、基本的にはオウム返しになりますが、「@username えー○○って( 艸`*)ププッ」(○○には相手の発言が入る) という感じにしましょう。

設定ファイルの追加

第 1 弾から使用している config.xml に対して、元々の username と password の後ろに、今回新たに 2 つの値を追加します。

query: "ダメ"
template: "えー「{text}」って( 艸`*)ププッ"

query が検索ワードで、template がそれに対する返信のひな形になります。
投げられた発言を埋め込む場所としてあらかじめ {text} を入れ込んでおいて、ここを置換します。

状態ファイルの準備

返信を送る際に、同じ人に何度も続けて返信をしてしまうと非常に失礼…というかこれはもうボットのバグなので、最後に返信をしたつぶやきを記録しておいて、次に検索する時はそれより後のつぶやきを検索するようにします。
最後に返信したつぶやきの id を次の検索時の検索条件として、since_id というもので指定するので、第 2 弾と同様、status.xml にそれを記録するようにしましょう。

sinceId: 0

初期値は最も小さい id、すなわち 0 を入れておきます。

実際のスクリプト

#!/usr/bin/perl

# 使用するモジュールの読み込み
use strict;
use warnings;
use Encode;
use FindBin;
use YAML::Tiny;
use Net::Twitter;

# yml ファイルの読み込みと、Twitter モジュールの初期化
my $config = (YAML::Tiny->read($FindBin::Bin . '/config.yml'))->[0];
my $status = (YAML::Tiny->read($FindBin::Bin . '/status.yml'))->[0];
my $twit = Net::Twitter->new(username => $config->{'username'}, password => $config->{'password'});

# 各種設定値の取得
my $username = $config->{'username'};
my $query = Encode::encode('utf8', $config->{'query'});
my $template = Encode::encode('utf8', $config->{'template'});
my $sinceId = $status->{'sinceId'};

# Twitter 検索。to: オプションでボット宛のつぶやきだけを絞り込み
my $response = $twit->search({q => 'to:' . $username . ' ' . $query, since_id => $sinceId});

# 検索結果を 1 件ずつ処理
my $result;
foreach $result ( reverse( @{$response->{'results'}} ) ) {
    my $text = Encode::encode('utf8', $result->{'text'});
    my $fromUser = $result->{'from_user'};
    my $statusId = $result->{'id'};
    my $reply = $template;
    $text =~ s/\s*\@${username}\s*//;
    $reply =~ s/{text}/$text/;
    $reply = '@' . "${fromUser} ${reply}";
    $twit->update({status => $reply, in_reply_to_status_id => $statusId});
    $sinceId = $statusId;
}

# 最後に処理したつぶやきの ID を保存
YAML::Tiny::DumpFile($FindBin::Bin . '/status.yml', {sinceId => $sinceId});

さて、ではポイントを絞って解説します。

まず重要なのが Twitter 検索の使い方です。
パラメータ q に検索文字列を指定して検索しますが、ここでは Twitter検索 で使用可能なオプションを指定することが出来ます。
検索時に指定出来るオプションについては、このあたりを参照して下さい。

パラメータ since_id は、つぶやきの id を指定することで、それより後のつぶやきを対象として検索します。
これがないと、重複返信をしてしまうのでとても重要なオプションです。

検索結果を 1 件ずつ処理するループでは、まず検索結果を配列として取得し、その順番を反転します。
検索結果は新しいものから順に返されるのですが、それに対する返信は古い順に行うからです。

検索結果の内容から、text, from_user, id を取り出します。
それぞれ、つぶやきの内容、つぶやいた人、つぶやき ID に相当します。

それ以降の部分は、ひな形の内容とつぶやきの内容を元に、返信する文章を組み立てています。
この部分まで詳しく解説するのは大変なので、詳細については別途 Perl について学んでみて下さい。

また、返信をつぶやく時の記述ですが、これまでになかった in_reply_to_status_id というオプションを指定しています。
このオプションは、どの発言に対する返信なのかを明示するもので、特に必須というわけではないです。
ですがこれをつけることで、返信から元の発言に対してリンクが張られるようになるので、返信の場合はつけておくのがマナーと言えます。

最後に動作確認が完了したら cron に登録しましょう。
あまり頻繁に検索すると API 制限にかかるので、5 分おきくらいにチェックするのが適当ではないでしょうか。

動作サンプル

otchy : @damedashi おれはもうダメだよ…
damedashi : @otchy えー「おれはもうダメだよ…」って( 艸`*)ププッ
otchy : @damedashi そんなダメとか繰り返さないでくれよ
damedashi : @otchy えー「そんなダメとか繰り返さないでくれよ」って( 艸`*)ププッ

※このボットは実在しません

改造のヒント

今回、ボットの返信に使うひな形は文章を 1 つだけにしましたが、第 2 弾で紹介した list.yml を使ったランダムテキストを組み込めば、バラエティーに富んだ返信をする事が可能です。
つぶやきの内容の一部を別の言葉に置換して返信しても面白そうですね。
list.yml の内容を配列ではなく、「キーワード:値」のペアにしておいて、「○○を△△に空目」ボットなんていうのも作れそうです。
また、検索自体のオプションを工夫すれば、もっと色々なボットも考えられるでしょう。

実は…

とここまで書いてきましたが、上記のスクリプトは期待したとおりの動作をしません。
それはなぜかというと、Twitter の検索 API では、日本語の検索結果がリアルタイムには検索出来ず、数時間の遅れがあるためです。
しかしながら、日本語の検索は 7 月末を目処に提供されるという話があるので、それまでは現状でリアルタイム検索が可能な英語表記でテストをしつつ待ちましょう。

さて、だんだんと本格的なボットになってきましたね。
第 4 弾以降では、ボット内だけで完結せず、外部の情報を読み込んで使用するようなボットを作ってみたいと思います。

5 thoughts on “Twitter ボットの作り方 Perl 編 (3)

  1. bot作りの参考にさせてもらっております.
    ありがとうございやす.

    ところで,第3弾のプログラムを
    % ./bot.pl
    などとして実行すると問題ないのですが,cronで実行させると
    my $response = $twit->search
    のところでコケて正常に動かないのですが,私だけでしょうか?

  2. どんなエラーになっているかによりますが、コマンドラインからの実行と cron からの実行によって、その API 自体の動作が変わる事は無いように思います。

    サーバの設定によって、実行時間、使用メモリ、外部接続等、cron の動作に制限が掛かっている可能性はないでしょうか?
    あるいは、Data::Dumper モジュールを導入して、エラー時の $response の内容を調べてみたら、状況が分かるかもしれませんね。

    個別の質問に対するサポートを詳しく解説は出来ませんが、頑張って下さい。

  3. 参考にさせて頂きBotを作っているのですが、
    OAuth認証に切り替えるべくいじりまわしていると、なぜか
    検索結果を1件ずつ処理する部分で

    can’t use an undefined value as an array reference 。。

    となってしまいます。

    OAuth認証にする事により、検索結果のデータが今迄と違うということなんでしょうか?・・

  4. Perl での実装ではないですが、別で OAuth を使った時は認証方式によって、API からのレスポンスが異なるという事は無かったです。
    ですので、未確認ですが、レスポンスの形式が変わった可能性があります。
    Data::Dumper
    を読み込んで、API からのレスポンスを解析すれば、何か分かるかも知れません。

コメントを残す

メールアドレスが公開されることはありません。