Categorymunou

秋山智俊・著『恋するプログラム – Ruby でつくる人工無脳』

恋するプログラム – Ruby でつくる人工無脳

恋するプログラム – Ruby でつくる人工無脳』を読みました。人工無脳を直接取り扱う書籍としては最初のものになるのでしょうか。って、あーそうだ。『人工無能 – “考えないマシン”と話す法』なんて本がかなり昔に出てたんだった(「Margarine – 無脳か無能か」にて少し紹介されています)。それはともかくとして。

Ruby のごくごく基本的な解説から始まり、あらかじめファイルに記録しておいた言葉をなんらかのきっかけで適宜しゃべるところから、感情パラメタによってしゃべるフレーズを変える機能を付加したり、あるいは形態素解析した文字列を記憶・学習、さらにそれらをマルコフ連鎖で文章らしきものへ組み立て、最後には Google で検索したページから言葉を学習する無脳を作り上げるといった内容。感情の表現として、たとえば怒っているときは怒っているふうなフレーズをしゃべらせるという方法は当然として、VisualuRuby を用いた GUI アプリケーションとして無脳を作成することで、感情を表現した画像(怒っているときは「ぷんすか!」みたいな表情)を表示するといった試みもなされています。

人工無脳に興味があって、その辺のリソースをあれこれ見ているひとにとっては、本書から特に新しい知見を得られるということはあまりないと思います。また、VisualuRuby を用いて GUI アプリケーションとして作成しているので、それに付随する説明が煩雑だし、正直いってイラネとか思いました。そういう説明をするスペースは、データ構造や面白い無脳を作る上でのテクニックの解説に裂いて、言語の遊びとしての無脳という側面をもっと押していく方がよかったかなぁ、と。まぁでも、サンプルを動かしながら読む分には、そういうしかけがある方がとっつき易くていいのかな。

あと、帯に「Ruby プログラミングの入門書としても最適です」とありますが、確かに本書のいう通り、人工無脳作成にはプログラミングを学ぶ上での基礎があれこれ必要になるので、そういう意味では「人工無脳を作成することは、プログラミング入門としては最適」ということになるかもしれないけど、本書を読んで「Ruby 学んじゃおー」という用途には使えないと思います。が、こういうきっかけであれこれ調べて学習しなきゃダメなんですよ、というメッセージであるなら、それはそれで、という感じ。つーか、まずは僕がそうしろ!(w

…とかゆってなんかくさしてばっかりいるような感じですがそういうこともなくて、最初はつまんないなーとか思ったけど、学習の章あたりからはだんだん面白くなってきて、僕も楽しい人工無脳を作りたいなぁという気分になりました。

みみずのおしゃべりを微妙に改善した

>

これまでのところ「マルコフ連鎖による文章の自動生成」にて述べた、2 語のプレフィクスと 1 語のサフィックスのデータ構造をもとにマルコフ連鎖アルゴリズムによって発言らしきものを捏造していたのですが、今日はそのデータ構造を 3 語のプレフィクス + 1 語のサフィックスに変更しました。また、形態素解析器に「茶筌」を使っていたのですが、これを「MeCab については、当初 debian sid から ver.0.77 のパッケージをインストールしたのですが、入力文字列をなぜかぶったぎって最初の数十文字しか解析してくれなかったので、ソースからインストールしたらうまくいきました。

とか書いてもどれぐらい改善されたのかわからないので、そのうちログをテケトに掲載することにしようと思った。

人工無脳「みみず」始動

6 月 4 日金曜日の夕方、人工無脳うさぎの弟分(?)であるところのホスティング無脳こうさぎがいっせいにブログへの投稿を行うのを、IRC チャンネル #yomiusa にて、うさぎの中の人こと kudo さんの実況に一喜一憂(謎)しながら眺めておりました。それはそれは大変に興奮的な、僕的には大事件だったわけですが、まぁそれは措くとして、ともあれ、こうさぎ、マジでヤバい!!! なにがヤバいかってのはそのうち書きますけど、そんなこんなで大興奮の余勢をかって、「作りますよ」とあちこちでいうだけいって放置していた無脳の制作を開始しました。これまでの経緯は以下にまとめてあります。

ちなみに名前は「みみず」です。主に PHP5 で制作しています(バックエンドの処理は Perl も使うかも)。名前にはまぁ特に意味はなくて、なんとなく思いついたから、という投げ遣りなノリで。以下、みみず関連リンク。

「エージェント・みみず」にはまだなんにもないですが、近いうちにここでみみずがブログを書くことになるでしょう。

とりあえず昨夜は、先に枠組みだけ作っておいたマルコフ連鎖アルゴリズムを汎用的なクラスに仕立て上げて実装し、今日は細かい調整とデータの自動収集処理を作りました。「データの自動収集処理」といってもたいしたことをやってるわけじゃなくて、RSSURI リストを読み込んで、RSS から言葉をおぼえるようにしただけですが。それでも、なんかものすごい量の言葉をすでに憶えています。しかしまぁ、それをうまく活用できないと意味ないのですが。

僕の目論見としては、会話する存在としての無脳にももちろん興味があるのですが、どちらかというといろんな情報をがんがん集めて提供してくれるエージェント的な存在を作りたいなぁと思っています。IRC での会話からも言葉を憶えるけれど、Web の情報をどんどん取り込んでいきたいなーと思ったり。

というかアレだ。無脳作成、マジ面白過ぎ!!! ヘタレなのはわかってるけど、とりあえずそんなことはどうでもいい。

マルコフ連鎖による文章の自動生成

PEAR::Net_SmartIRC を使って、一定間隔でニュースを配信する IRC BOT を作成する」で作成したごく簡単な BOT はしかし、外部のリソースをひっぱってきて、それを単にそのまま流すことしかできません(RSS をパースする処理はあるけど、本質的には垂れ流してるだけ)。通常 IRC BOT というと、チャンネルのメンバが喋った言葉を憶え、それらをアレンジしたデータを用いて、時には当意即妙に会話に介入することもあればまるで的はずれな発言で場を微妙な雰囲気に陥れることもあるといったものですし、また、なかには日記や Blog を書くすごい BOT さんもいます。

そうなると当然、次の目標は「おしゃべりをする、あるいは日記を書く BOT を作成する」というものになるわけですが、まぁ僕の頭ではいきなりそんなことを実現することは不可能ですし、また、そのような方向で BOT を作成するにしても、少なくないステップをひとつひとつクリアしていかなければなりません。そこで、前準備として任意の入力データをアレンジして文章を自動的に生成する仕組みを作ってみたいと思います。

「文章を自動的に生成する」と書くのは簡単ですが、さて実際にそれを実現しようとなると、どのようにすればいいのか。たとえばごくごく単純に、文字列を一文字ずつ分割して、それらの文字を適当な長さになるまでランダムに配置する、とまずは考えたのですが、まぁ考えるまでもなく、そのようにしてできあがった「文章」はほぼ確実に意味不明なものになるでしょう。とすれば、入力文字列をなんらかの方法で分割する必要はあるにしても、その分割のしかたが問題になるわけです。

そこであれこれ調べてみたところ、形態素解析なる手法を用いて文字列を分割するのがよさそうだということがわかりました。形態素解析 (Morphological Analysis) とは自然言語処理の基礎技術のひとつで、自然言語で書かれた文章を形態素 (Morpheme, おおまかにいえば「単語」) の列に分割し、品詞 (Part-of-speech) を見分ける作業であるとのことであり、つまりは先に考えた文字列を一文字ずつ分割するという手法とはまったく異なり、文法的な最小単位で分割するというものであり、この手法を用いれば文字列の分割についてはなんとかなりそうです。

その形態素解析を行ってくれるソフトウェアにはいくつかの種類があるようですが、ここでは「茶筌」を使うことにします(とはいえ、この選択には特に意味はなくて、単に最初にみつけたものをインストールしてみたらうまく動いたから使い続けているという、まぁアレな話)。ChaSen’s Wiki – ソースからのインストール」を参考に、僕の環境では次のソフトのそれぞれ最新版らしきものを、以下の順序でインストールしました。

  1. Darts
  2. 茶筌
  3. ipadic

茶筌により、たとえば「僕は、電波を受信しません。受信するのはラジオです。僕はラジオではない。ラジオは僕の脳内にある。僕の脳は、電波を受信する。」を形態素に分割すると、以下のようになります(形態素ごとに、半角スペースで区切っています)。

$ echo 僕は、電波を受信しません。受信するのはラジオです。僕はラジオではない。ラジオは僕の脳内にある。僕の脳は、電波を受信する。 | chasen -F "%m "
$ 僕 は 、 電波 を 受信 し ませ ん 。 受信 する の は ラジオ です 。 僕 は ラジオ で は ない 。 ラジオ は 僕 の 脳 内 に ある 。 僕 の 脳 は 、 電波 を 受信 する 。 

さて、文字列をなんかしら意味ある方法でもって分割することができました。「文章を自動生成する」ためには、この分割した形態素群を、なんらかの方法で再配置しなければなりません。その際、また単純な考えで形態素をランダムに配置するというのでは、先の一文字ずつをランダムに配置するよりはいくらかはマシとはいえ、そう変わりのない意味不明な文章になってしまうでしょう。どうすればいいか。

形態素に分割した文字列を再配置することで文章を生成する手法にはどういったものがあるのでしょうか。僕は文系っつか、まぁ算数的な方面にはまるで疎くて、どのようにすればいい具合に文章を作成することができるのか、その方法を思いつくこともできませんし、また、すでにあるのだろうそのような手法も知りませんでした。そこで、いつも楽しませてもらっている読兎さんや、またなまこさんという IRC BOT が採用しているらしい「マルコフ連鎖」なる手法を使ってみることにしました。このマルコフ連鎖信州大学サイト内の確率論について述べた文章中に 1 章を使って説明されている(「確率論 – 第 4 章 「マルコフ連鎖」」)のですが、なんつーか、まったく理解できません! 理屈を述べられてもどうしようもないので、とりあえず実装例をくれ! というわけで検索してみたところ、markov.pl なる、いかにもソレっぽい perl スクリプトを見つけました。が、まぁ…それでもよくわかりません。そこで、このスクリプトについて教えを請うつもりで「#順列都市」に質問を投げるつもりが、間違えてつい前日に初めて参加させていただいたばかりの「#yomiusa」(読兎さんがいらっしゃるチャンネルです。楽しい)に誤爆してしまいました…。わけのわからないやつが不躾なことをしでかしたにも関わらず、親切な方が、マルコフ連鎖について知りたければ「Namako Project – bots/Markov1.pm」を見るといいんじゃないか、という感じで教えてくださいました! そうです。僕がそもそもわけわかってないのは、そもわかち書きした後の形態素群をどのような構造のデータにすればいいのかということで、上にリンクしたページにはその辺りを含めてばっちり書かれてあります。いけそうな気がしてきました。

さてマルコフ連鎖とは、JMarkov という、マルコフ連鎖による文章の自動生成を行う Java アプレットに付された解説によれば文章を、複数語からなるプレフィクス(接頭語句)と、プレフィクスに続く1語のサフィックス(接尾語)に分割します。そしてオリジナルのテキストの統計に基づいてプレフィクスの後ろにくるサフィックスをランダムに選び、文章を出力するというものであるとのこと。具体的には以下の通りになります。

プレフィクス(接頭語句)を 2 語とすると、先に茶筌により形態素群に分割した例(「僕 は 、 電波 を 受信 し ませ ん 。 受信 する の は ラジオ です 。 僕 は ラジオ で は ない 。 ラジオ は 僕 の 脳 内 に ある 。 僕 の 脳 は 、 電波 を 受信 する 。 」)をまずは次のようなペアに分割します(長くなるので、適当なところで端折ってます)。

接頭語句 接尾語
(開始)僕 は ラジオ
は ラジオ
ラジオ で
で は ラジオ
ない
省略 省略
受信 する
する 。 (終了)

このようにして得た「接頭語句 + 接尾語」のペア群を文章として再配置するにあたり、「接頭語句 + ランダムに選ばれた接尾語」 -> 「直前の接尾語を含む接頭語句 + その接尾語からランダムに選ばれた語」 -> 「さらにまたその直前の接尾語を含む接頭語句 + その接尾語からランダムに選ばれた語」…というふうに連鎖させていきます。イメージ的にいうと「2 歩進んで 1 歩下がり、また 2 歩進んで 1 歩下がる」という感じで、その 2 歩進む際の語をランダムに選択することで、元の文章をアレンジしていくわけです。また、ここでは接頭語句を 2 語としましたが、3 語、4 語と増やしていけばさらに意味のある文章を生成することもできますが、ただ単純に語数を増やすだけでは、元とあまり変わりのない文章が生成されるだけで、あまり面白くないことになってしまいます。まぁ、いろいろ考えればもっと面白くできるのでしょうが、僕の頭の限界がここら辺にあるようなので、このままいくことにします。

それではマルコフ連鎖による文章の自動生成の概要をなんとなく理解したところで、PHP により実装してみたいと思います。以下にスクリプトを示します。僕の目論見としては、「Namako Project – bots/Markov2.pm」と同様のやり方で文章を生成するようにしたつもりです。まぁ、perl スクリプトを読んだわけじゃない(perl わかんない…)ので、実際はどうなのかはわからないけどw

markov.php

<?php
/**
* 入力文字列を形態素解析し、マルコフ連鎖アルゴリズムを用いて文章を生成する
*
*  入力文字列:     $text
*  生成された文章: $data
*
**/
$max_cnt = 100;       // ループの最大回数
$EOS = "EOS";         // 終端文字列
$break_mark = "。";   // 改行に変換する目印
$break_frequency = 2; // 改行の頻度
// 入力文字列を整形
$text = mb_convert_kana($text, "R");                   // 半角英字を全角に変換
$text = htmlspecialchars($text);
$patterns = array("/[\n\r]/", "/ +/");
$replacements = array("", "");
$text = preg_replace($patterns, $replacements, $text); // ウザい文字列を置換
// 茶筅で分かち書きした結果を配列に格納
$escaped_text = EscapeShellCmd($text);
exec("/bin/echo $escaped_text | /usr/local/bin/chasen -e -F \"%m\n\"", $parsed_text);
$cnt = count($parsed_text);
$ary = array();
// perfix と suffix のペアに分割
for ($i = 0; $i < $cnt; $i++) {
if ($i == 0) {
$prefix1 = $parsed_text[$i];
$prefix2 = $parsed_text[($i + 1)];
$suffix = $parsed_text[($i + 2)];
$ary[$prefix1] = array($prefix2 => array($suffix));
} elseif ($i < ($cnt - 1)) {
$prefix1 = $parsed_text[($i - 1)];
$prefix2 = $parsed_text[$i];
$suffix = $parsed_text[($i + 1)];
if (!(isset($ary[$prefix1]))) {
$ary[$prefix1] = array($prefix2 => array($suffix));
} elseif (isset($ary[$prefix1]) && !(isset($ary[$prefix1][$prefix2]))) {
$ary[$prefix1][$prefix2] = array($suffix);
} else {
if (!in_array($suffix, $ary[$prefix1][$prefix2])) {
$ary[$prefix1][$prefix2] = array_merge($ary[$prefix1][$prefix2], array($suffix));
}
}
} elseif ($i == ($cnt - 1)) {
$prefix1 = $parsed_text[($i - 1)];
$prefix2 = $parsed_text[$i];
$suffix = $parsed_text[$i];
if (!(isset($ary[$prefix1]))) {
$ary[$prefix1] = array($prefix2 => array($suffix));
} else {
$ary[$prefix1][$prefix2] = array($suffix);
}
}
}
// スタート文字列は 1 番目の文字
// 2, 3 番目は配列からランダムにキー・値を取得
$first = key($ary);
$second = array_rand($ary[$first]);
srand();
$num = rand(0, (count($ary[$first][$second]) - 1));
$third = $ary[$first][$second][$num];
$data .= $first . $second;
// マルコフ連鎖により、文章を生成
for ($i = 0; $i < $max_cnt; $i++) {
// 1 番目の文字列
$first = $third;
// 2 番目の文字列
$second = array_rand($ary[$first]);
// 3 番目の文字列
srand();
$num = rand(0, (count($ary[$first][$second]) - 1));
$third = $ary[$first][$second][$num];
// 文字列の終端にぶちあたったら、ループ終わり
if ( ($second == $EOS) || ($third == $EOS) ) {
break;
}
// $break_frequency な頻度で $break_mark に改行文字(\n)を付す
srand();
$num = rand(1, $break_frequency);
if ((($break_frequency - $num) == 0) && ($first == $break_mark)) {
$data .= $first . "\n" . $second;
} else {
$data .= $first . $second;
}
}
$data = mb_convert_kana($data, "a");  // 全角英字を半角に変換
?>

…とまぁ、スクリプトをぼこんと置かれてもなんのことやらという感じでしょうから、ともあれ Web から試してみることができるようにしたページを用意したので、実際にいろいろやってみていただきたいと思います。また、スクリプトの実行後、入力文字列がどのような構造のデータに変換されるのかを表示するようにしましたので、実際にどのような処理が行われているのかを理解したいという奇特な方は、御利用下さい。

まずは、あらかじめ入力されている文字列がどのようにアレンジされるか、試してみてください。感じがつかめたら、今度はなにか適当な文章を書いて、入力してみてください。また、2ch 等のテンプレや、あるいはニュースサイトの文章、もしくはそれらを様々に組み合わせた文章を入力してみても面白いでしょう。

さて、こうして不完全ではあるものの文章を自動生成する仕組みのとっかかりを作ることができました。次はこの結果を利用して、なんらかのソース(IRC でのおしゃべりや他サイトの文章)から取得したデータをアレンジして、BOT に日記を書かせてみたいと思います。まだまだ先は長い…。

また、今回の試みの参考資料として、wiki にリンク集を作りました。この辺に興味がある方はぜひとも参考にしたり、あるいは追記をよろしくお願いします。

PEAR::Net_SmartIRC を使って、一定間隔でニュースを配信する IRC BOT を作成する

最近は、IRC#順列都市なるチャンネルにておしゃべり暮らしているわけですが、IRC というものに触れるのが初めてなので、いろんなことが面白い。そんな中でやっぱりいちばん感銘を覚えたのが BOT なる存在。世の中にはいろんな BOT さんがいるようで、#順列都市ではロイディさんに来ていただいています。んで、召還した彼(?)に語りかけるなどして寂しさを紛らわしたり、と、そんな毎日です。

そのような BOT さんたちを見ていると、ヘタレながらも「こういうのって、自分で作れないかなぁ」と、まぁ思うわけです。とはいえ、一から作るのは無理があり過ぎるので、例によって PEAR 漁り。すると、Net_SmartIRC という、ちょうどいい感じのパッケージがありました。そこでこの Net_SmartIRC パッケージを用いて、ごく簡単な IRC BOT を作成してみたいと思います。かといって特に「これだ!」というオリジナルなネタもないので、とりあえず「Going My Way: IRCを使ったニュースとBlog情報の自動配信」でその概要が紹介されている、ニュースを自動配信してくれる BOT を…パクる…とかそんなんじゃなくて、えーと、インスパイア! されました! ってな、そんな感じで。もちろん、劣化コピーです!

まずはともあれ、具体的なイメージをつかんでいただくため、以下のチャンネルで実際の BOT を動かしていますので、join してみてください。

まずは PEAR パッケージをインストールします。また、RSS をパースするのに XML_RSS も必要とするので、一緒にインストールしておきます。

$ su -
# pear install Net_SmartIRC
# pear install XML_RSS

パッケージのインストールを終えたら、以下の 4 つのファイルをどこか適当なディレクトリに設置します(rss_list.txt 以外については、拡張子が .txt になっているのを事前に外してください)。

最後の rss_list.txt には登録した RSS データを書き込むので、設置や実行の条件によっては rss_list.txt に対して書き込みすることができるように権限を設定する必要があるかもしれません(666 とか)。

では、それぞれのファイルについて、解説。まずは設定ファイルである conf.inc について。とりあえず「BOT の基本設定」と「IRC 関連設定」という箇所を変更します。BOT に素敵な名前をつけてあげましょう。IRC 関連の設定については「国内 IRC サーバリスト (Mar/9/2004 現在)」というページを参考にするといいでしょう。その他の設定については、スクリプト内のコメントに従って適当に変更したり変更しなかったりしてください。また、エンコーディングEUC-JP にしておくのが無難かもしれません。

conf.inc:

<?php
/**
* Bot 設定
*
* IRC 関連の設定については、以下のページを参照してください
* 「国内 IRC サーバリスト (Mar/9/2004 現在)」[http://irc.kyoto-u.ac.jp/list-of-servers.html]
*
**/
// BOT の基本設定
define("BOT_NICK", "news_bot"); // 英数字で
define("BOT_NAME", "php_bot");  // 英数字で
// IRC 関連設定
define("SERVER_HOST", "irc.nara.wide.ad.jp"); // なんか適当なサーバ
define("SERVER_PORT", 6668);                  // 適当なポート
define("CHANNEL", "#_順列都市");              // BOT をログインさせるチャンネル名
// コマンド(変更不要)
define("DELIMITER", ":");                // BOT のニックネームとコマンドとのつなぎ目
define("COMMAND_FEED_NEWS", "feedNews"); // 一定時間ごとにニュースを配信するコマンド
define("COMMAND_ADD_RSS", "addRSS");     // RSS を新規に登録するコマンド
define("COMMAND_LIST_RSS", "listRSS");   // 登録されている RSS のリストを表示するコマンド
define("COMMAND_QUIT", "quit");          // BOT を終了するコマンド
// コマンド関連設定ここから
// COMMAND_FEED_NEWS 関連
define("RSS_FEED_INTERVAL", 10);  // ニュースを配信する間隔(秒単位)
define("RSS_COUNT", 1);           // 読み込む RSS の数
define("ITEM_COUNT", 1);          // 読み込む item の数
define("RSS_NEWS_PREFIX", "■ "); // 書き出す item の前に付す文字
define("RSS_NEWS_HOOK", ":");     // 記事のタイトルと URI の間に付す文字
// COMMAND_ADD_RSS 関連(それぞれの意味については、察してください)
define("RSS_ADD_SUCCESS_MSG", "指定された RSS をリストに追加しますた!");
define("RSS_ADD_ERR_MSG", "そんな RSS はないっぽですよ?");
define("RSS_ADD_ERR2_MSG", "指定された RSS はすでにリストに存在します。");
// COMMAND_LIST_RSS 関連
define("RSS_LIST_FILE", "./rss_list.txt"); // RSS リスト
//define("RSS_LIST_FILE_PATH", "/home/kentaro/public_html/tmp/rss_list.txt"); // RSS リストをコピーするパス
define("RSS_LIST_FILE_URI", "http://antipop.zapto.org/tmp/rss_list.txt");     // RSS リストの URI
// COMMAND_QUIT 関連(それぞれの意味については、察してください)
define("QUIT_MSG", "終了します");
// コマンド関連設定ここまで
// 共通設定(それぞれの意味については、察してください)
define("FILE_OPEN_ERR_MSG", "ファイルを開けませんですた。。。");;
define("URL_PATTERN", "|(http://[a-zA-Z0-9@:%_.~#-\?&]+[a-zA-Z0-9@:%_~#\?&/])|");
define("IRC_ENCODING", "ISO-2022-JP"); // 変更不要
?>

次に、bot.php について。これが bot の本体です。conf.inc で設定したチャンネルにログインした後、registerTimehandler() で登録したメソッドを conf.inc で設定した間隔で実行したり、registerActionhandler() で登録したメソッドを IRC 参加者からのコマンド呼び出しによって実行したりします。

bot.php:

<?php
/**
* Bot メイン
*
**/
include_once "Net/SmartIRC.php";
include_once "./bot.class.inc";
include_once "./conf.inc";
$bot = &new mybot();
$irc = &new Net_SmartIRC();
$irc->setDebug(SMARTIRC_DEBUG_ALL);
$irc->setUseSockets(TRUE);
// ニュースを表示
$irc->registerTimehandler*1;
$irc->listen();
?>

最後に、BOT の挙動を定義するクラス bot.class.inc について。実際に IRC 上から使用するメソッドとして、以下の 3 つが定義されています。

addRSS
RSS を新規に登録するコマンド
listRSS
登録されている RSS のリストを表示するコマンド
quit
BOT を終了するコマンド

それぞれの使い方については後述します。また、feedNews については、BOT が自動的に実行するメソッドなので、IRC 参加者が直接コマンドから呼び出すものではありません。

bot.class.inc:

<?php
/**
* Bot クラス
*
**/
include_once "XML/RSS.php";
class mybot {
/**
* RSS リストからランダムに読み込んだ RSS をパースして表示
*
*
*/
function feedNews (&$irc) {
// RSS リスト読み込み
$list = file(RSS_LIST_FILE);
// RSS_COUNT の回数だけ RSS を読み込む
for ($i = 0; $i < RSS_COUNT; $i++) {
// 乱数生成
// リストから RSS をランダムに取得
srand();
$num = rand(0, count($list));
// RSS パーサ生成 & RSS をパース
$rss = &new XML_RSS(trim($list[$num]));
$rss->parse();
// channel タイトルを書き出し
$channel_info = $rss->getChannelInfo();
$channel_title = $this->_convert($channel_info['title']);
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $channel_title);
// ITEM_COUNT の回数だけ item を書き出す
$item = $rss->getItems();
for ($j = 0; $j < ITEM_COUNT; $j++) {
$title = $this->_convert($item[$j]['title']);
$link = $this->_convert($item[$j]['link']);
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $this->_convert(RSS_NEWS_PREFIX) . $title . $this->_convert(RSS_NEWS_HOOK) . $link);
}
unset($rss);
}
}
/**
* RSS をリストに追加
*
*
*/
function addRSS (&$irc, &$data) {
// URI が妥当ならば、RSS を検証後、リストに追加
if (!(preg_match(URL_PATTERN, $data->message, $matches))) {
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $this->_convert(RSS_ADD_ERR_MSG));
} else {
// RSS パーサ生成 & RSS をパース
$rss_uri = trim($matches[1]);
$rss = &new XML_RSS($rss_uri);
$rss->parse();
$channel_info = $rss->getChannelInfo();
$channel_link = $channel_info['link'];
// RSS をパースした結果、channel の link を取得できなかったらエラー
// でなければ、リストに追加
if (!$channel_link) {
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $this->_convert(RSS_ADD_ERR_MSG));
} else {
// リストを開けなかったらエラー
if (!($fp = fopen(RSS_LIST_FILE, "a+"))) {
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $this->_convert(FILE_OPEN_ERR_MSG));
} else {
flock($fp, LOCK_EX);
while (!(feof($fp))) {
$lines .= fgets($fp);
}
$lines = explode("\n", $lines);
if (!in_array($rss_uri, $lines)) {
fputs($fp, $rss_uri . "\n");
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $this->_convert(RSS_ADD_SUCCESS_MSG));
} else {
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), $this->_convert(RSS_ADD_ERR2_MSG));
}
flock($fp, LOCK_UN);
fclose($fp);
}
}
unset($rss);
}
}
/**
* RSS のリストを示す
*
*
*/
function listRSS (&$irc, &$data) {
exec("cp " . RSS_LIST_FILE . " " . RSS_LIST_FILE_PATH);
$irc->message(SMARTIRC_TYPE_NOTICE, $this->_convert(CHANNEL), RSS_LIST_FILE_URI);
}
/**
* BOT を終了する
*
*
*/
function quit (&$irc) {
$irc->quit($this->_convert(QUIT_MSG));
}
/**
* IRC のエンコーディングへ変換
*
*/
function _convert ($str) {
$str = mb_convert_encoding($str, IRC_ENCODING, "auto");
return $str;
}
}
?>

設定等が済んだら、スクリプトを設置したディレクトリに移動した後、コンソールから以下の通り実行します。

$ cd /path/to/script/dir
$ php bot.php &

うまくいけば、指定したチャンネルに BOT がログインし、即座にニュースの配信を開始します。以下にスクリーンショットを示します(クリックで拡大画像を表示)。青っぽい色のメッセージが、BOT が配信している内容です。rss_list.txt に登録された RSS をランダムに読み込んで、conf.inc で設定した間隔でニュースを配信します。

BOT がニュースを配信している様子

その他のコマンドについて解説します。BOT に対して送るコマンドは、デフォルトの設定では、例えば次のようになります。

news_bot:addRSS

conf.inc の設定でいうと、”BOT_NICK + DELIMITER + COMMAND_[コマンド名]” と指定してください。

コマンド addRSS は、RSSURI を指定することで、その RSS が妥当な RSS であり、かつ、リストに登録されていなければ、新規に登録します。

コマンド listRSS は、現在登録されている RSS のリストを Web から見える場所に書き出します(正確にいうと、cp コマンドでコピーした後、リストの URI を書き出します)。

コマンド quit は、BOT を終了させます。

以上の通り、ここではごくごく簡単な BOT を作成してみました。適当なチャンネルにこの BOT を常駐させて、配信されるニュースを暇な時につらつらと眺めるなどすると、RSS リーダを普通に使用するのでは見えてこない面白さがあったりもするのではないかと思います。

ここで使用した Net_SmartIRC には上で例示したもの以外にも様々な機能が用意されていますし、また、その他の PEAR パッケージと組み合わせることで、アイディア次第でさらに便利で面白い BOT を作成することができるのではないかと思います。みなさんがそれぞれでオリジナルな BOT を作成して、ぜひともパク…いや、リスペクトかつオマージュさせてください!

この項、今回作成した IRC BOT の機能を今後拡充するにあたり、PEAR のいろんなパッケージを使い、かつ、Blog 関連のコアなテクノロジをからめつつ、そのあたりひっくるめてお勉強ってなニュアンスで続けていきたいなぁ、ってな所存、だけど、まぁ飽きそう。というわけで、次回に続く…かも。

この項、シリーズ IRC BOT を作ろう!第 2 回「マルコフ連鎖による文章の自動生成」に続く。

*1:RSS_FEED_INTERVAL * 1000), $bot, COMMAND_FEED_NEWS);
// RSS を追加
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘^’ . BOT_NICK . DELIMITER . COMMAND_ADD_RSS, $bot, COMMAND_ADD_RSS);
// RSS のリストを示す
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘^’ . BOT_NICK . DELIMITER . COMMAND_LIST_RSS, $bot, COMMAND_LIST_RSS);
// BOT を終了する
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘^’ . BOT_NICK . DELIMITER . COMMAND_QUIT, $bot, COMMAND_QUIT);
$channel = $bot->_convert(CHANNEL);
$irc->connect(SERVER_HOST, SERVER_PORT);
$irc->login(BOT_NICK, BOT_NAME);
$irc->join(array($channel

© 2020 栗林健太郎

Theme by Anders NorénUp ↑