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

Portal:siro

ダイレクトマーケティングブログ

ちっサイラスBOT制作:ごはんたべる

ちっサイ実装続き!

今回の目標

ごはんを食べるようにします。

もぐもぐ。

スキンシップ以外の反応の追加

1ヶ月近くまともにソース触ってなかったのでちょっと不安になりつつ。やっていきましょう。

実装のアルゴリズムなのですが、きりのさんからアイディアをいただいたのでそれを採用します。
ただ、完全再現は難しそうなので一部簡略化しました。こんな感じです。

1回目:貰ってもすぐには食べない。これなになに?ってちょっと警戒。
2回目:ちょっと食べてみる。
3回目以降:もぐもぐ。

ね、簡単でしょ!
これをプログラミングでつくっていきます。

もうちょっと詳しく

1〜3回目までだったら実はカウント系の変数を使わなくても実装できます。

1回目(はじめて貰うもの)かどうかの判定は「データベースに登録されているか否か」での判定です。
なければ新規登録します。
2回目(はじめて食べるもの)かどうかの判定は「好き嫌いの値が登録されているか否か」での判定です。
食べた後に判定します。
以上どちらにも当てはまらない場合は3回目以降、です。

これは食べ物ですか?

ひとつ厄介な問題。
しろぼっとに関しては処理を放棄して手動でやっているのですが「与えられたものが何なのか」という判定です。

今回はなるべく自動でやりたいので、そこら辺を上手く工夫してみます。

まずは食べ物を受け取る処理

ひとまず、 つ「」、っ「」、つ【】、っ【】 に反応させます。

chi_silas_reply

		if (!$nextReply &&
			(strpos( $rep[$i]["text"], "つ「" ) !== false || 
			strpos( $rep[$i]["text"], "っ「" ) !== false || 
			strpos( $rep[$i]["text"], "つ【" ) !== false || 
			strpos( $rep[$i]["text"], "っ【" ) !== false )){
			// 反応
			feed_reply($i);

こいつはスキンシップとかの反応前に突っ込みます。
それから、chi_silas_feedlistというテーブルもつくっておきます。
id(PK),thing(unique),isDelicious(int)

それからfeed_replyを作成します。

function feed_reply($i){
	global $st,$rep;
	$conn = db_conn();
	if($conn !== false){
		// 貰い物を抽出
		$gift_temp = preg_replace("/.*(「|【)(.*)(」|】).*/" ,"\\2" , $rep[$i]["text"] );
		// エスケープ
		$gifts = mysql_real_escape_string($gift_temp,$conn);
		if($gifts !== false){
			// このアイテムは以前に貰ったことがあるか?
			$query = mysql_query("select * from chi_silas_feedlist where thing = '" . $gifts . "'");
			$obj = mysql_fetch_object($query);
			if($obj == false){
				// 貰ったことがない
				$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴぃ?  [これはなに?不思議そうに見ているよ。じー]";
				mysql_query("insert into chi_silas_feedlist (thing) values ('" . $gifts . "')");
			}else{
				// 貰ったことがある
				// 食べたことは?
				if($obj->isDelicious == 0){
					// ないよ
					$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴぃ!  [たべてみよう!" . $gift_temp . "もぐもぐ。ねえ、これっておいしいの?]";
					echo $reply_status;
				}elseif($obj->isDelicious == 1){
					// あるし、食べれるよ
					$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴー!ぴー!  [はぐはぐ!" . $gift_temp . "おいしいね!]";
				}else{
					// あるけど、食べれないよ
					$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴ……。  [" . $gift_temp . "はいらないみたい]";
				}
			}
			$st->setUpdate(array('status'=>$reply_status,'in_reply_to_status_id_str'=>$rep[$i]["id_str"]));
		}
		mysql_close($conn);
	}
}

順に解説していきます。

function feed_reply($i){
	global $st,$rep;
	$conn = db_conn();

ここまではもう呪文でいいです。マジメに書くと1行目が関数(function)宣言、2行目がグローバル変数の宣言(?)、3行目がDB接続です。今更な解説!

	if($conn !== false){
		// 貰い物を抽出
		$gifts = preg_replace("/.*(「|【)(.*)(」|】).*/" ,"\\2" , $rep[$i]["text"] );

4行目:DBへの接続がfalseでない(=接続できていたら)
6行目:文字列から貰い物を抽出します

 "/.*(?:「|【)(.*)(?:」|】).*/"

この部分がマジ暗号ですね!!!!!!1!111
正規表現に関してはちょっとすいません各自でぐぐってくだs……。
一応コレについてのみ説明しますと、

""←コレで囲まれたものが抽出条件ですよ
//←コレで囲まれてるのでPERLの正規表現ですよ
----- おまじないの壁 -----
.←任意の1文字ですよ
*←文字が0回以上繰り返されてますよ
 つまり、.* で「任意のn文字」を指します。
 .不要で*のみと解説しているものもあります。僕は.つけたほうが個人的にわかりやすいのでつけてるだけです。好みで。
()←数学の()と同じ。この中身は優先/ひとくくりですよという意味。
  なんですが、実はもうひとつ意味があるので、それはまた下で説明します。
|←OR、または、です。 A|Bで「AまたはB」という意味になります。
 今回は「|【で、"「または【"をあらわしています。前につけた.*と混じらないように、()を付けてORの範囲を明確にします。
----- 正規表現の基本の壁 -----
()←実はこれで括ったものは後方参照(後述)で取得できます。
?:←【ここは処理系によって書き方が違う。よく確かめること】後方参照として認識しないという設定です。
  ()で括ったものは上の通り後方参照の対象ですが、(?:A|B)みたいに書くと後方参照としては無視されます。
  「自体は抽出しても後で使うことはないので、この処理を入れています。
----- 後方参照の壁 -----
後方参照:主に置換処理で使います。"\\2"の部分です。
これの意味は、()で括った文字列を表します。 くそわかりやすく言うと、
 (ha)(na)という正規表現があったとします。このとき、\\1は「ha」\\2は「na」になります。
最初の()から順々に番号が振られていくので、その番号を使って後から中身を取り出すのに利用します。
今回は単純に つ「ここの中身」 を取り出すために使っているので"\\2"だけで書いています。
もっと複雑な利用方法はぐぐってみると面白いかもです。 テキストエディタで一斉置換とかするとき、大分楽になりますよ。

上の説明、割と適当なんで間違ってたらごめんなさいね……。
今回は正規表現が超絶日本語でおk状態なのでわかりにくいですが、例えばしろぼっとのユイ機能で「○○ってなーに?」と言うときの○○の抽出は、
$yui_word = preg_replace("/(.+)ってなーに?/", "\\1" , $yui_inreplyto["text"]);
こう書いています。+は、*の親戚みたいなものです。こうすると、「ってなーに?」の前の部分をとりあえず抽出してるんだってことがなんとなくわかっていただけるかと思います。


さて、続き。

$gifts = mysql_real_escape_string($gift_temp,$conn);
if($gifts !== false){

変な文字列が入ってたらそれを取り払う、という。まあ、おまじないです。
こいつを使う為に抽出より先にDBへのコネクションを確保しています。

その処理が成功した場合(falseでない場合)は次の処理に進みます。

// このアイテムは以前に貰ったことがあるか?
$query = mysql_query("select * from chi_silas_feedlist where thing = '" . $gifts . "'");
$obj = mysql_fetch_object($query);

上はSQLです。ちっサイの食べ物リストの中から渡されたアイテムを探してきます。

			if($obj == false){
				// 貰ったことがない
				$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴぃ?  [これはなに?不思議そうに見ているよ。じー]";
				mysql_query("insert into chi_silas_feedlist (thing) values ('" . $gifts . "')");
			}else{
				// 貰ったことがある
				// 食べたことは?
				if($obj->isDelicious == 0){
					// ないよ
					$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴぃ!  [たべてみよう!" . $gift_temp . "もぐもぐ。ねえ、これっておいしいの?]";
					echo $reply_status;
				}elseif($obj->isDelicious == 1){
					// あるし、食べれるよ
					$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴー!ぴー!  [はぐはぐ!" . $gift_temp . "おいしいね!]";
				}else{
					// あるけど、食べれないよ
					$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴ……。  [" . $gift_temp . "はいらないみたい]";
				}
			}

特に解説はいらないですよねここ。値の有無で分岐します。

$st->setUpdate(array('status'=>$reply_status,'in_reply_to_status_id_str'=>$rep[$i]["id_str"]));
}
mysql_close($conn);
}
}

ついーとして、DBへの接続を閉じて、おしまいです。

これは食べ物ですか?(2回目)

僕が手抜きをするために、食べ物かどうかの判定は皆さんに任せることにします。
つまり、「ねえ、これっておいしいの?」に渡した人が「おいしいよ!」と返せばちっサイはそれを美味しいものだと認識します。
「まずいよ!」って返せばちっサイはそれをまずいものだと認識します。

つまりその気になれば「はぐはぐ!ちぃディンおいしいね!」みたいなこともできr

何が「おいし」くて何が「まずい」のか

人間の反応ほど面倒くさいものはないです。
形態素解析、言語理論、人工知能、その辺りの方々が日夜腐心してます。
特に日本語はパターンが多すぎるんだよ馬鹿野郎。

chi_silas_feedReactionというテーブルを作ります。id,string,isDeliciousのみです。
これに、

美味しい
うまい
おいしい
オイシイ
旨い
うん
はい
yes

とか、

まずい
食べるな
だめ
ダメ
不味
おいしくない
美味しくない
ない
ねーよ
いいえ
no

……とか、味に関する言葉を片っ端から突っ込みます。


それから、「ねえ、これっておいしいの?」への返答(in_reply_to)であるかを見ます。
その食べ物をあげた人かどうかは、今のところチェックしません。これは敢えての仕様の穴です。(つまり横から誰かが「美味しくないよ!」って言える)
また、その食べ物の味を既に覚えたかどうかもチェックしません。これも敢えて、です。


※一般公開BOTであればいたずら防止の為にそういうチェックはつけたほうがいいと思っています。
ですが、今回のBOTは身内用鍵つきBOTであるため、敢えてそれは必要ないと判断しています。要するに「皆を信用しています」ということです。


返答を見る部分。は、例えばこう作りましょう。

	// おいしいの反応ワード読み込み
	$query = mysql_query("select * from chi_silas_feedReaction");
	while(($tempWord = mysql_fetch_object($query))){
		$feedWord[$tempWord->string] = $tempWord->isDelicious;
	}

まずはDBからおいしい/まずいの判定ワードを読み込みます。

それから、ここは使いまわしです。

if(!$nextReply){
	// おいしいの反応ワード
	foreach($feedWord as $string => $isDelicious){
		if(strpos( $rep[$i]["text"], $string ) !== false){
			// おいしい?
			isDelicious($i,$isDelicious);
			// 好感度の更新
			if($isDelicious == 1){
				$quantity = 1;
			}else{
				$quantity = -1;
			}
			update_likability($rep[$i]["user"]["screen_name"],$quantity,"feed");
			$nextReply = true;
			break;
		}
	}
}


ちょっと変わったのが、好感度のところですね。
おいしいものをあげると増えて、まずいものをあげるとfeed値が減ります。

最後に、isDelicious関数を実装しておわり。

function isDelicious($i,$isDelicious){
	global $st,$rep,$json;
	// 該当replyのin_reply_toが「ねえ、これっておいしいの?」宛てであるかを判定する
	$inreplyto = $json->decode($st->getStatusShow($rep[$i]["in_reply_to_status_id_str"]));
	if ( strpos( $inreplyto["text"] , "ねえ、これっておいしいの?" ) !== false ){
		$gifts = preg_replace("/.*たべてみよう!(.+)もぐもぐ。.*/", "\\1" , $inreplyto["text"]);
		$conn = db_conn();
		if($conn !== false){
			// 味を登録します
			$query = "update chi_silas_feedlist set isDelicious = " . $isDelicious . " where thing = '" . $gifts . "'";
			mysql_query($query);
			$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴい。  [そうなのか。おぼえたよ!]";
			mysql_close($conn);
		}
	}else{
		$reply_status = "@" . $rep[$i]["user"]["screen_name"] . " ぴ??  [よくわからないよ!]";
	}
	$st->setUpdate(array('status'=>$reply_status,'in_reply_to_status_id_str'=>$rep[$i]["id_str"]));
}


やっぱりここまで動作テストもリファレンスも何も引かずに(しろぼっとのソースコードは見てます)書いてますが……。
バグがなければ、いいなあ。

というわけで、こんな感じです。
もぐもぐ!