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

Portal:siro

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

ちっサイラスBOT制作:愛情度記録

ちっサイラス実装の続きです

今回は何をするか

そろそろ「replyもらったら喋るようにしようか?」って思うんですけど*1
ちっサイに関しては基礎機能から埋めていきます。
具体的には、話しかけてきた人への愛情度の記録です。主に弄るのはDB関係。
ただ、目に見える反応がないというだけで、reply処理についてはここでもう作ってしまいます。

その前に

DBへの接続が更に多くなるので、いい加減にラッパー関数を作ります。

$talk_conn = mysql_connect("うにゃうにゃ","うにゃ","にゃんにゃん");
if($talk_conn !== false){
	// おまじない
	mysql_select_db("にゃー",$talk_conn);
	mysql_query("SET NAMES UTF8");

この辺のおまじない部分をまとめます。ざっくり。
ただしこのラッパー関数はreply反応でも使うことになるので、(そしてreply用のphpと通常発言用のphpは分けたほうがcron呼び出しの際楽なので)、別ファイルに作り、共通関数とします。

chi_silas_common.php

function db_conn(){
	$conn = mysql_connect("うにゃうにゃ","うにゃ","にゃんにゃん");
	if($conn !== false){
		mysql_select_db("にゃー",$conn);
		mysql_query("SET NAMES UTF8");
	}
	return $conn;
}

接続に成功すれば、$conn(データベースへの接続)を返し、失敗すればfalseを返します。

こいつをちぃサイのphpからrequire_onceしておきます。

require_once "chi_silas_common.php";

そんでもってソースを置き換え。地味ですがこれで10行くらい減ります。

connect関数を作るのならdisconnect関数も作るべきと言われそうですが、閉じる際の処理が複雑になったら作ります。今は要りません。
ついでに、emotionUpdateもcommonに移動させておきます。

愛情度記録(table作成)

TwitterID単位でDBに記録していきます。
カラムは、id(PK),twitterid(unique),play,skinship,feed,anger,terror
like/dislikeだけではなく、細かく記録していきます。

id,twitterid→わかると思うので省略
play→あそぶ*2と値が上昇します
skinship→さわる*3と値が上昇します
feed→ご飯をあげると値が上昇します
anger→詳細は決まっていませんが意地悪すると値が上昇します
terror→怖がらせると値が上昇します


とりあえずこんな感じのテーブルを作ります。
chi_silas_followerとでもしておきましょう。

愛情度記録(記録用関数の作成)

記録用関数を作ります。
必要な情報は「誰が」「どれだけ」「何の」愛情度を変化させたか、です。
既に入っている値に新しく与えられた値を加算します。基本的に減算はしません。

あと先に言っておきますがシロさんは英語ができません。

function update_likability($person,$quantity,$column){
	$conn = db_conn();
	if($conn !== false){
		$query = mysql_query("update chi_silas_follower as temptbl set " . $column 
			. " = temptbl." . $column . "+ " . $quantity . " where twitterid = " . "'" . $person . "'");
		mysql_close($conn);
	}
}

SQL:update chi_silas_follower as temptbl set $column = temptbl.$column + $quantity where twitterid = $person
このSQL文あってるんですかね……。
なにせシロさんはSQL文も苦手なので勘弁してください。

ともかくこれをcommonに追加します。

愛情度記録(reply処理&記録)

人物の新規登録関数は今回は省略します*4
というわけで、replyの処理と記録に移りたいと思います。

replyの処理ですが、chi_silas_reply.phpというファイルを新たに作ります。
しろぼっとの流用……と言いつつしかし。折角新規に作るのですから色々変えましょう。

例えばしろぼっとでは「どのreplyまで反応を返したか」はファイルに記録されていますが、これをデータベースへの記録に変更します。
chi_silas_dataというテーブルをざっくり作ってしまって、lastReplyという項目をごりっと作ってしまいます。

また、酷い話なのですが、しろぼっとの場合はひたすらif-elseで該当する機能の呼び出しをしています。
しろぼっとは反応ワードと機能がほぼ1対1なのでまだよいのですが、ちぃサイラスの場合複数のワードに1つの機能が対応するという形になるので、
反応ワードを一括でデータベースに格納してしまい、その中から該当するものがあれば機能を呼び出すという方法を取ります。
日本語でおk?わかってるさ。自分も何が言いたいのかよくわからないからな!

というわけでchi_silas_skinshipWordというテーブルを作ってやって、中身をid(PK)、string(unique)、quantityとします。
{1,なでなで,1}のようにしておいて、なでなでされたら愛情度が1増える、みたいな感じで実装できるようにします。



まあ論より証拠、仕様よりソースコード、という感じで。
中身はこんな感じになります。

<?php
// include_path 自動設定
$path = "/home/****/pear/PEAR";
set_include_path(get_include_path() . PATH_SEPARATOR . $path);

// require_once
require_once "chi_silas_common.php";
require_once "Jsphon/Decoder.php";

// reply機能
// lastReply(statusID)の読み込み
$conn = db_conn();
$lastReply = 0;
if($conn !== false){
	$query = mysql_query("select lastReply from chi_silas_data");
	$obj = mysql_fetch_object($query);
	$lastReply = $obj->lastReply;
	mysql_close($conn);
}

// replyを取得
$json = new Jsphon_Decoder();
$rep = $json->decode($st->getReplies());

// 新着replyがない場合何もせず終了する
if ( floatval($rep[0]["id_str"]) <= floatval($lastReply) ){
	echo "no replies.";	// デバッグ用出力
	return;
}

// 反応ワード一覧を読み込みます。(上のif文にelseで続けて書くとスコープがややこしくなるので敢えて外に出してあります)
// lastReply取得時に読み込まないのは、新着replyが無い場合の無駄を削るためです。
$conn = db_conn();
$skinshipWord;	// スキンシップ反応ワード格納配列
if($conn !== false){
	// スキンシップ反応ワード読み込み
	$query = mysql_query("select * from chi_silas_skinshipWord");
	while(($tempWord = mysql_fetch_object($query))){
		// ↑のwhile文おかしいので後で直す
		// fetch_arrayでもfetch_objでもどちらでもいい気がするけど、今回はobjで。
		// 最終的に$skinshipWord["なでなで"] = 1みたいな感じになります
		$skinshipWord[$tempWord->string] = $tempWord->quantity;
	}
	// デバッグ用
	var_dump($skinshipWord);
	// 他の反応ワードも同様に読み込みを行ってからclose
	mysql_close($conn);
}

// 未処理のreplyに対して[古いほうから]繰り返す
for ($i = 19; $i >= 0 ;$i--){
	if ( floatval($rep[$i]["id_str"]) > floatval($lastReply) && strcmp("Chi_Silas",$rep[$i]["user"]["screen_name"]) != 0){
		// 自分(chi_silas)からのreplyには反応しない(将来的に必要になるので現時点で実装)

		// スキンシップ反応ワード
		foreach($skinshipWord as $string => $quantity){
			if(strpos( $rep[$i]["text"], $string ) !== false){
				// 好感度の更新
				update_likability($rep[$i]["user"]["screen_name"],$quantity,"skinship");
				continue;	// ここcontinueであってる? for($i = 19〜の行まで戻りたい
			}
		}
		$lastReply = $rep[$i]["id_str"];
	}
}

// lastReply(statusID)の書き込み
$conn = db_conn();
if($conn !== false){
	$query = mysql_query("update chi_silas_data set lastReply = " . $lastReply);
	mysql_close($conn);
}

?>


(そしてぶっちゃけるとこのコード動く気がしない)

とりあえず、まあこんなところです。はい。
後はなんかこう、ミスを直して……。
最終的に以下のようになりました。

(長いので続きにまとめてあります)

chi_silas.php
<?php
// 外部ファイル読み込み
require_once "chi_silas_common.php";

// ランダム発言部
// 発言はDBから取得

// 現在の感情を決定
emotionTimer();
// DBに接続
$talk_conn = db_conn();
if($talk_conn !== false){
	// 今の感情を取得
	$emo_query = mysql_query("select emotion from chi_silas_status where id = (select max(id) from chi_silas_status)");
	$obj = mysql_fetch_object($emo_query);
	$emotion = $obj->emotion;
	// 今の感情と合致する台詞を喋る
	$talk_query = mysql_query("select string from chi_silas_randomList where emotion = '" . $emotion . "' order by RAND() limit 1");
	$talk_obj = mysql_fetch_object($talk_query);
	$st->setUpdate($talk_obj->string);
	mysql_close($talk_conn);
}


function emotionTimer(){
  // 現在時刻によって感情を変化させる
  $now = getdate();
  switch($now["hours"]){
  	case 0:
  	case 1:
  	case 2:
  	case 3:
  	case 4:
  	case 5:
  	case 6:
  	case 7:
  		$emotion = "すやすや";
  		break;
  	case 8:
  	case 9:
  	case 22:
  	case 23:
  		$emotion = "ねむたい";
  		break;
  	case 12:
  	case 15:
  	case 19:
   		$emotion = "はらぺこ";
  		break;
  	default:
  		// 「うれしい」「おこった」「かなしい」「たのしい」「さみしい」からランダム
  		$random = rand(0,4);
  		if($random == 0){
  			$emotion = "うれしい";
  		}else if($random == 1){
  			$emotion = "おこった";
  		}else if($random == 2){
  			$emotion = "かなしい";
  		}else if($random == 3){
  			$emotion = "たのしい";
  		}else if($random == 4){
  			$emotion = "さみしい";
  		}
  		break;
  }
  emotionUpdate($emotion,"時報","");
}
?>
chi_silas_common.php
<?php
//====common======================================================================================

// require_once(共通)
require_once "Services/Twitter.php";
require_once "Services/twitteroauth.php";

// グローバル変数stの宣言
$st =& new Services_Twitter(
/* ないしょ */
TRUE );

// データベースへの接続を行う
// 接続成功:dbへのコネクション、接続失敗:falseが返る
function db_conn(){
	$conn = mysql_connect("うにゃにゃ","にゃん","にゃあ");
	if($conn !== false){
		mysql_select_db("にゃ",$conn);
		mysql_query("SET NAMES UTF8");
	}
	return $conn;
}

//====common======================================================================================


// 直前の感情と与えられた感情を比較し、新しければ感情を更新する
function emotionUpdate($emotion,$cause,$causePerson){
	$emotionPrev = "みていぎ";
	
	$conn = db_conn();
	if($conn !== false){
		$query = mysql_query("select emotion from chi_silas_status where id = (select max(id) from chi_silas_status)");
		$obj = mysql_fetch_object($query);
		$emotionPrev = $obj->emotion;
		mysql_close($conn);
	}

	if($emotionPrev !== $emotion){
		// 直前の感情と新しい感情が異なる場合、更新する
		$conn = db_conn();
		if($conn !== false){
			$query = mysql_query("insert into chi_silas_status(date,emotion,cause,causePerson) values (NOW(),'" . $emotion . "','" . $cause . "','" . $causePerson . "')");
			mysql_close($conn);
		}
	}
}

// 好感度を更新する
// 更新対象の「人」「加算量」「項目」を引数に与える
function update_likability($person,$quantity,$column){
	$conn = db_conn();
	if($conn !== false){
		$query = mysql_query("update chi_silas_follower as temptbl set " . $column 
			. " = temptbl." . $column . "+ " . $quantity . " where twitterid = " . "'" . $person . "'");
		mysql_close($conn);
	}
}

?>
chi_silas_reply.php
<?php
// include_path 自動設定
$path = "/home/****/pear/PEAR";
set_include_path(get_include_path() . PATH_SEPARATOR . $path);

// require_once
require_once "chi_silas_common.php";
require_once "Jsphon/Decoder.php";

// reply機能
// lastReply(statusID)の読み込み
$conn = db_conn();
$lastReply = 0;
if($conn !== false){
	$query = mysql_query("select lastReply from chi_silas_data");
	$obj = mysql_fetch_object($query);
	$lastReply = $obj->lastReply;
	mysql_close($conn);
}

// replyを取得
$json = new Jsphon_Decoder();
$rep = $json->decode($st->getReplies());

// 新着replyがない場合何もせず終了する
if ( floatval($rep[0]["id_str"]) <= floatval($lastReply) ){
	echo "no replies.";	// デバッグ用出力
	return;
}

// 反応ワード一覧を読み込みます。(上のif文にelseで続けて書くとスコープがややこしくなるので敢えて外に出してあります)
// lastReply取得時に読み込まないのは、新着replyが無い場合の無駄を削るためです。
$conn = db_conn();
$skinshipWord;	// スキンシップ反応ワード格納配列
if($conn !== false){
	// スキンシップ反応ワード読み込み
	$query = mysql_query("select * from chi_silas_skinshipWord");
	while(($tempWord = mysql_fetch_object($query))){
		// ↑のwhile文おかしいので後で直す
		// fetch_arrayでもfetch_objでもどちらでもいい気がするけど、今回はobjで。
		// 最終的に$skinshipWord["なでなで"] = 1みたいな感じになります
		$skinshipWord[$tempWord->string] = $tempWord->quantity;
	}
	// デバッグ用
	var_dump($skinshipWord);
	// 他の反応ワードも同様に読み込みを行ってからclose
	mysql_close($conn);
}

// 未処理のreplyに対して[古いほうから]繰り返す
for ($i = 19; $i >= 0 ;$i--){
	if ( floatval($rep[$i]["id_str"]) > floatval($lastReply) && strcmp("Chi_Silas",$rep[$i]["user"]["screen_name"]) != 0){
		// 自分(chi_silas)からのreplyには反応しない(将来的に必要になるので現時点で実装)
		// スキンシップ反応ワード
		foreach($skinshipWord as $string => $quantity){
			if(strpos( $rep[$i]["text"], $string ) !== false){
				// 好感度の更新
				update_likability($rep[$i]["user"]["screen_name"],$quantity,"skinship");
				continue;	// ここcontinueであってる? for($i = 19〜の行まで戻りたい
			}
		}
		$lastReply = $rep[$i]["id_str"];
	}
}

// lastReply(statusID)の書き込み
$conn = db_conn();
if($conn !== false){
	$query = mysql_query("update chi_silas_data set lastReply = " . $lastReply);
	mysql_close($conn);
}

?>

*1:反応があると動いた!って気がしますので

*2:後々ちぃサイと遊べるような何かを実装したいと思ってます。まだ何も思いついてないけd

*3:なでる、等、とりあえず優しく構うこと全般

*4:鍵つき身内BOTでFollower数が非常に少ないため、INSERTくらいなら手動でやったほうが早い