#!/usr/local/bin/perl

#┌─────────────────────────────────
#│ ChatRobo v1.11 (2002/10/12)
#│ Copyright(C) Kent Web 2002
#│ webmaster@kent-web.com
#│ http://www.kent-web.com/
#└─────────────────────────────────
$ver = 'ChatRobo v1.11';
#┌─────────────────────────────────
#│ [注意事項]
#│ 1. このスクリプトはフリーソフトです。このスクリプトを使用した
#│    いかなる損害に対して作者は一切の責任を負いません。
#│ 2. 設置に関する質問はサポート掲示板にお願いいたします。
#│    直接メールによる質問は一切お受けいたしておりません。
#└─────────────────────────────────
#
# 【ファイル構成例】
#
#  public_html (ホームディレクトリ)
#      |
#      +-- robo / robo.cgi    [755]
#            |    robomgr.cgi [755]
#            |    robo.log    [666]
#            |    member.dat  [666]
#            |    deny.dat    [666]
#            |    robo1.dat   [666]
#            |    robo2.dat   [666]
#            |    jcode.pl    [644]
#            |
#            +-- lock [777] /
#

#============#
#  設定項目  #
#============#

# コード変換ライブラリ取込
require './jcode.pl';

# 戻り先のURL(index.htmlなど)
$homepage = '../index.html';

# 最大記事数
$max = 30;

# リロード時間の選択秒数
@reload = (0,30,40,50,60);

# リロード時間の初期値
$re_sec = 40;

# 表示行数
@line = (10,20,30,40,50);

# 文字色を指定（必ず偶数で。上下の配列はペアで)
@COLORS = ('#0000FF','#DF0000','#008040','#800000','#C100C1','#FF80C0','#FF8040','#000080');
@IROIRO = ('青','赤','みどり','茶','紫','ピンク','オレンジ','あい色');

# タイトル名
$title = "メイドさん";

# タイトル色
$t_color = '#008040';

# タイトルサイズ
$t_size = '16pt';

# 本文文字サイズ
$b_size = '10pt';

# methodの形式 (POST/GET)
$method = 'POST';

# ポインタの形状（ログ表示部で「名前」の前につく）
$pointer = "★";

# スクリプトファイル名
$script  = './robo.cgi';

# ログファイル名
$logfile = './robo.log';

# 参加者表示ファイル名
$memfile = './member.dat';

# ロックファイル機構 (0=no 1=symlink関数 2=mkdir関数)
$lockkey = 0;

# ロックファイル名
$lockfile = './lock/robo.lock';

# 入退室案内名
$master = "MASTER";

# 入退室メッセージ
$in_msg  = "さん、いらっしゃいであります。";  # 入室時
$out_msg = "さん、さようならであります。";  # 退室時

# 入退室メッセージの色
$rep_color = "#808080";

# 入室時にIPアドレスを表示 (0=no 1=yes)
$host_view = 1;

# bodyタグ（背景色・文字色などを定義）
$body = '<body bgcolor="#F1F1F1" text="#000000" link="#0000FF" vlink="#0000FF">';

# Chat Robo Managerを指定
$chatmgr = './robomgr.cgi';

# 上記の場合の拒否アドレスのログ
$denyfile = './deny.dat';

# 顔文字
@faces = ('(＾_＾)', '(＾_＾；)', '(*＾＾*)', '(；_；)', '(ーー；', '  ｍ（_ _）ｍ', '(・_・)',	'(＾＾）/~~', '(＠_＠)', '＼（＾Ｏ＾）／', '(？_？)');

# ロボットの名前
$robot = 'ヴィルヘルミナ';

# ロボットの発言色
$robocolor = '#006400';

# 応答ログ1 (通常辞書）
$robo1 = './robo1.dat';

# 応答ログ2 (応用辞書）
$robo2 = './robo2.dat';

# 学習機能 (0=no 1=yes)
#  → yesにすると誰からでも学習します
#  → コメント欄に「キーワード === 応答メッセージ」というように
#     === (イコールを3つ）で区切って入力します
$study = 1;

#============#
#  設定完了  #
#============#

$string = &decode;
&deny;
if (!$string) { &frame; }
if ($mode eq 'form') { &form1; }
if ($mode eq 'into') { &form2; }
if ($in{'comment'} && $mode eq 'msg') { &regist('comment'); }
if ($mode eq 'bye') { &byebye; }
&log_view;

#------------------#
#  フレームを生成  #
#------------------#
sub frame {
	# クッキーを取得
	&get_cookie;

	# クッキーにリロード時間／行数のない場合は初期値を代入
	if ($ck{'retime'} eq "") { $ck{'retime'} = $re_sec; }
	if ($ck{'line'} eq "") { $ck{'line'} = $line[1]; }

	&header;
	print <<"EOM";
<frameset rows="147,*">
<frame name="form" src="$script?mode=form&retime=$ck{'retime'}&line=$ck{'line'}">
<frame name="log" src="$script?retime=$ck{'retime'}&line=$ck{'line'}">
<noframes>
<body>
フレーム非対応のブラウザの方は利用できません
</body></noframes>
</frameset>
</html>
EOM
	exit;
}

#------------------#
#  HTMLのヘッダー  #
#------------------#
sub header {
	$headflag=1;
	print "Content-type: text/html\n\n";
	print <<"EOM";
<html>
<head>
<STYLE TYPE="text/css">
<!--
body,tr,td,th { font-size: $b_size }
small         { font-size: 9pt }
-->
</STYLE>
<META HTTP-EQUIV="Content-type" CONTENT="text/html; charset=Shift_JIS">
<title>$title</title>
EOM
}

#-----------------------#
#  フォーム1 : 入室画面 #
#-----------------------#
sub form1 {
	# クッキーを取得
	&get_cookie;

	# HTMLを出力
	&header;
	print <<"EOM";
</head>
$body
<form method="$method" action="$script" target="form" name="entry">
<input type=hidden name=mode value="into">
<font color="$t_color" size=5><b style="font-size:$t_size">$title</b></font>
<hr size=1><table border=0 cellspacing=0>
<tr><td>
<b>おなまえ</b> <input type=text name=name size=25 value="$ck{'name'}"><br>
<b>Ｅメール</b> <input type=text name=email size=25 value="$ck{'email'}"><br>
リロード <select name=retime>
EOM
	foreach (@reload) {
		if ($in{'retime'} == $_) {
			print "<option value=\"$_\" selected>$_秒\n";
		} else {
			print "<option value=\"$_\">$_秒\n";
		}
	}
	print "</select> 行数 <select name=line>\n";
	foreach (@line) {
		if ($in{'line'} == $_) {
			print "<option value=\"$_\" selected>$_行\n";
		} else {
			print "<option value=\"$_\">$_行\n";
		}
	}
	print "</select><br><input type=submit value=\"入室する\">\n";
	print "&nbsp; [<a href=\"$homepage\" target=\"_top\">もどる</a>]\n";
	print "[<a href=\"$chatmgr\" target=log>管理用</a>]<td width=40><br>\n";
	print "<td align=center valign=top nowrap>文字色を選んでください<br>\n";

	if ($ck{'color'} eq "") { $ck{'color'} = $COLORS[0]; }
	$key = int (@COLORS / 2);
	$i=0;
	foreach (0 .. $#COLORS) {
		$i++;
		if ($ck{'color'} eq "$COLORS[$_]") {
		    print "<input type=radio name=color value=\"$COLORS[$_]\" checked>";
		    print "<font color=\"$COLORS[$_]\">■</font>\n";
		} else {
		    print "<input type=radio name=color value=\"$COLORS[$_]\">";
		    print "<font color=\"$COLORS[$_]\">■</font>\n";
		}
		if ($i == $key) { print "<br>"; }
	}

	print "</td></form></tr></table>\n<SCRIPT LANGUAGE=\"JavaScript\">\n<!--\n";
	print "self.document.entry.name.focus();\n";
	print "//-->\n</SCRIPT>\n</body>\n</html>\n";
	exit;
}

#----------------------------#
#  フォーム2 : 発言フォーム  #
#----------------------------#
sub form2 {
	&regist('into');

	# クッキーを提供
	&set_cookie;

	# MSIEの場合フォーム長を調整
    	if ($ENV{'HTTP_USER_AGENT'} =~ /MSIE/) { $width = 90; } else { $width = 65; }

	# 以下のJavaScript（発言コメントの自動消去機能）は
	# ゆいちゃっと (http://www.cup.com/yui/) から移植しました。
	&header;
	print <<"EOM";
<SCRIPT LANGUAGE="JavaScript">
<!--
function autoclear() {
 if (self.document.send) {
  if (self.document.cmode && self.document.cmode.autoclear) {
   if (self.document.cmode.autoclear.checked) {
    if (self.document.send.face) {
      self.document.send.face.options[0].selected = true;
    }
    if (self.document.send.comment) {
      self.document.send.comment.value = "";
      self.document.send.comment.focus();
    }
   }
  }
 }
}
// -->
</SCRIPT>
</HEAD>
$body
<form name="send" method="$method" action="$script" target="log" ONSUBMIT="setTimeout(&quot;autoclear()&quot;,10)">
<input type=hidden name=mode value="msg">
<input type=hidden name=name value="$in{'name'}">
<input type=hidden name=email value="$in{'email'}">
<table border=0><tr><td colspan=3>
<b>名前</b>：<font color="$in{'color'}"><b>$in{'name'}</b></font>
  <input type=submit value="発言／リロード">
<input type=reset value="クリア"><br>
<b>発言</b>：<input type=text size="$width" name=comment><br>
<tr><td>かお文字 <select name=face>
EOM
	print "<option value=\"\">なし\n";
	foreach (@faces) {
		print "<option value=\"$_\">$_\n";
	}
	print "</select> 文字色 <select name=color>\n";
	foreach (0 .. $#COLORS) {
		if ($in{'color'} eq $COLORS[$_]) {
			print "<option value=\"$COLORS[$_]\" selected>$IROIRO[$_]\n";
		} else {
			print "<option value=\"$COLORS[$_]\">$IROIRO[$_]\n";
		}
	}
	print "</select> リロード <select name=retime>\n";
	foreach (@reload) {
		if ($in{'retime'} == $_) {
			print "<option value=\"$_\" selected>$_秒\n";
		} else {
			print "<option value=\"$_\">$_秒\n";
		}
	}
	print "</select> 行数 <select name=line>\n";
	foreach (@line) {
		if ($in{'line'} == $_) {
			print "<option value=\"$_\" selected>$_\n";
		} else {
			print "<option value=\"$_\">$_\n";
		}
	}
	print "</select></td></form><td valign=top>\n";
	print "<form action=\"$script\" method=\"$method\" target=\"form\">\n";
	print "<input type=submit value=\"退室する\">\n";
	print "<input type=hidden name=mode value=\"bye\">\n";
	print "<input type=hidden name=name value=\"$in{'name'}\"></td></form>\n";
	print "<td><form name=cmode><input type=checkbox name=autoclear checked>";
	print "発言自動消去</td></form></table>\n</body>\n</html>\n";
	exit;
}

#--------------#
#  記事表示部  #
#--------------#
sub log_view {
	&header;
	if ($in{'retime'} != 0) {
		local($ename) = &url_enc($in{'name'});
		print "<META HTTP-EQUIV=\"refresh\" CONTENT=\"$in{'retime'}; URL=$script?retime=$in{'retime'}&line=$in{'line'}&name=$ename\">\n";
	}

	# 参加者表示
	&member;
	print "</head>\n$body\n<table width='100%'><tr><td>参加者($num)：$member\n";
	print "<td align=right>リロード: \n";
	if ($in{'retime'} == 0) { print "手動モード"; } else { print "$in{'retime'}秒"; }
	print " 行数: $in{'line'}行</table>\n";

	$i=0;
	open(IN,"$logfile") || &error("Open Error : $logfile");
	while (<IN>) {
		$i++;
		last if ($i > $in{'line'});
		($date,$name,$email,$comment,$color) = split(/<>/);
		print "<hr><font color=\"$color\">$name ＞ $comment</font> ";
		print "<font color=\"$rep_color\" size=2>($date)</font><br>\n";
	}
	close(IN);

	# 著作権を表示（削除禁止）
	print "<hr><div align=center><small><!-- $ver -->\n";
	print "- <a href='http://www.kent-web.com/' target='_top'>ChatRobo</a> -\n";
	print "</small></div>\n</body>\n</html>\n";
	exit;
}

#--------------------#
#  ログ書き込み処理  #
#--------------------#
sub regist {
	# 名前の入力がなければエラー
	if ($in{'name'} eq "") { &error("名前の入力がありません"); }

	# ファイルロック
	&lock if ($lockkey);

	$bye=0;
	if ($_[0] eq 'into') {
		$in{'comment'} = "<b>$in{'name'}</b>$in_msg";
		if ($host_view) { $in{'comment'} .= " &lt;$host&gt;"; }
		$email = "";
		$name  = $master;
		$color = $rep_color;
	} elsif ($_[0] eq 'bye') {
		$in{'comment'} = "<b>$in{'name'}</b>$out_msg";
		$email = "";
		$name  = $master;
		$color = $rep_color;
		$bye=1;
	} else {
		if ($in{'email'}) {
			$pointer = "<a href=\"mailto:$in{'email'}\">$pointer</a>";
		}
		$name = "$pointer <b>$in{'name'}</b>";
		$email = $in{'email'};
		$color = $in{'color'};
	}

	# ログを開く
	open(IN,"$logfile") || &error("Can't open $logfile");
	@lines = <IN>;
	close(IN);

	## clear と入力することで自分の記事を削除
	if ($in{'comment'} eq 'clear'){
		@temp=();
		local($match)=0;
		foreach (@lines) {
			($ho) = (split(/<>/))[5];
			if ($host eq $ho) { $match=1; }
			else { push(@temp,$_); }
		}
		if ($match) { @lines=@temp; $in{'comment'}="All Clear (^-^)v"; }
	}
	if ($study) {
		if ($in{'comment'} =~ /(.+)\=\=\=(.+)/) {
			$regist_flag=2;
			$keywd=$1; $resp=$2;
			&jcode'convert(*keywd, "euc");
			&jcode'convert(*resp, "euc");

			$flag=0;
			open(IN,"$robo1") || &error("Open Error : $robo1");
			while (<IN>) {
				chop;
				($key,$res) = split(/<>/);
				if ($key eq $keywd && $res eq $resp) { $flag=1; last; }
			}
			close(IN);
			if (!$flag) {
				# 基本辞書に追加
				open(OUT,">>$robo1") || &error("Write Error : $robo1");
				print OUT "$keywd<>$resp\n";
				close(OUT);
			}
			$in{'comment'} = "$robotに教育中・・・";
		}
	}

	# 最大記事数
	while ($max <= @lines) { pop(@lines); }

	# ロボット応答処理
	open(IN,"$memfile") || &error("Open Error : $memfile");
	@data = <IN>;
	close(IN);
	$now_member = @data;

	# 学習時の返事
	if ($regist_flag == 2) {
		if ($flag) { $res = "ダメ〜!! このキーワードと応答は知ってるよ"; }
		else { $res = "オ〜ケ〜!! 覚えたよ"; }
	} else {
		# 在室者が１名のとき、又は名前を呼ばれた時に返答する
		if ($_[0] eq "comment" && $now_member <= 1) { $regist_flag=1; }
		else {
			if (index($in{'comment'},$robot) >= 0) { $regist_flag=1; }
			elsif (index($in{'comment'},"誰か") >= 0) { $regist_flag=1; }
			else { $regist_flag=0; }
		}
	}

	if ($regist_flag == 1) {
		$e_com  = $in{'comment'};
		$e_name = $in{'name'};
		&jcode'convert(*e_com,'euc');
		&jcode'convert(*e_name,'euc');

		$flag=0;
		@temp=();
		open(IN,"$robo1") || &error("Open Error : $robo1");
		while ($line = <IN>) {
			($key,$res) = split(/<>/, $line);
			$key =~ s/([\+\*\.\?\^\$\[\-\]\|\(\)\\])/\\$1/g;
			$key =~ s/\\\.\\\*/\.\*/g;
			if ($e_com =~ /$key/) { $flag=1; push(@temp,$res); }
		}
		close(IN);

		srand;
		if ($flag) { $res = $temp[int(rand(@temp))]; }
		else {
			open(IN,"$robo2") || &error("Open Error : $robo2");
			rand($.) < 1 && ($res = $_) while <IN>;
			close(IN);
		}
		$res =~ s/NAME/$e_name/g;
		&jcode'convert(*res, "sjis");
		$res =~ s/\n//g;
	}

	# ログをフォーマットして更新
	$in{'comment'} =~ s/\(\*\＾\＾\*\)/\(<font color=red>\*<\/font>\＾\＾<font color=red>\*<\/font>\)/;
	unshift (@lines,"$date<>$name<>$email<>$in{'comment'}<>$color<>$host<>\n");
	if ($regist_flag) {
		local($sec,$min,$hour,$mday,$mon) = localtime(time+3);
		$date2 = sprintf("%s/%s-%02d:%02d:%02d", $mon+1,$mday,$hour,$min,$sec);
		unshift (@lines,"$date2<>$robot<><>$res<>$robocolor<>I_am_a_robot<>\n");
	}
	open(OUT,">$logfile") || &error("Write Error : $logfile");
	print OUT @lines;
	close(OUT);

	# ロック解除
	&unlock if ($lockkey);
}

#----------------#
#  デコード処理  #
#----------------#
sub decode {
	local($buf, $key, $val);
	if ($ENV{'REQUEST_METHOD'} eq "POST") {
		read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});
	} else { $buf = $ENV{'QUERY_STRING'}; }
	foreach (split(/&/, $buf)) {
		($key, $val) = split(/=/);

		$val =~ tr/+/ /;
		$val =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

		# 文字コードをシフトJIS変換
		&jcode'convert(*val, "sjis", "", "z");

		# タグ処理
		$val =~ s/&/&amp;/g;
		$val =~ s/</&lt;/g;
		$val =~ s/>/&gt;/g;
		$val =~ s/"/&quot;/g;
		$val =~ s/\r//g;
		$val =~ s/\n//g;

		$in{$key} = $val;
	}
	$mode = $in{'mode'};

	# IPアドレスを取得
	$host = $ENV{'REMOTE_ADDR'};

	if ($in{'face'} ne "") { $in{'comment'} = "$in{'comment'} $in{'face'}"; }
   	if ($in{'name'} eq "") { $in{'name'} = $host; }

	# 時間を取得
	$ENV{'TZ'} = "JST-9";
	$times = time;
	($sec,$min,$hour,$mday,$mon) = localtime($times);
	$date = sprintf("%02d/%02d-%02d:%02d:%02d", $mon+1,$mday,$hour,$min,$sec);

	if ($buf) { return 1; } else { return 0; }
}

#------------#
#  退室処理  #
#------------#
sub byebye {
	&regist('bye');
	&member('bye');
	&header;
	print <<"EOM";
</head>
$body
<div align=center><h3>$in{'name'}さん、ご利用ありがとうございました</h3>
<form action="$homepage" target="_top">
<input type=submit value="ホームページへもどる"></form></div>
</body>
</html>
EOM
	exit;
}

#------------------#
#  クッキーの発行  #
#------------------#
sub set_cookie {
	local($gmt, $cook, @t);

	@t = gmtime(time + 60*24*60*60);
	$gmt = sprintf("%s, %02d-%s-%04d %02d:%02d:%02d GMT",
			(qw(Sun Mon Tue Wed Thu Fri Sat))[$t[6]],
			$t[3],
			(qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$t[4]],
			$t[5]+1900, $t[2], $t[1], $t[0]);

	$cook = "name<>$in{'name'}<>email<>$in{'email'}<>color<>$in{'color'}<>retime<>$in{'retime'}<>line<>$in{'line'}";
	print "Set-Cookie: CHAT_ROBO=$cook; expires=$gmt\n";
}

#------------------#
#  クッキーを取得  #
#------------------#
sub get_cookie {
	local($key, $val, @cook);
	@cook = split(/;/, $ENV{'HTTP_COOKIE'});
	foreach (@cook) {
		($key, $val) = split(/=/);
		$key =~ s/\s//g;
		$tmp{$key} = $val;
	}
	%ck = split(/<>/, $tmp{'CHAT_ROBO'});
}

#------------------#
#  エラー表示処理  #
#------------------#
sub error {
	&unlock if ($lockflag);
	&header if (!$headflag);
	print "</head>\n$body\n";
	print "<div align=center><hr width=400><h3>ERROR !</h3>\n";
	print "<font color=red><B>$_[0]</B></font>\n";
	print "<P><hr width=400></div>\n</body>\n</html>\n";
	exit;
}

#--------------#
#  ロック処理  #
#--------------#
sub lock {
	local($retry)=5;
	if (-e $lockfile) {
		local($mtime) = (stat($lockfile))[9];
		if ($mtime < time - 60) { &unlock; }
	}
	# symlink関数式ロック
	if ($lockkey == 1) {
		while (!symlink(".", $lockfile)) {
			if (--$retry <= 0) { &error('LOCK is BUSY'); }
			sleep(1);
		}
	# mkdir関数式ロック
	} elsif ($lockkey == 2) {
		while (!mkdir($lockfile, 0755)) {
			if (--$retry <= 0) { &error('LOCK is BUSY'); }
			sleep(1);
		}
	}
	$lockflag=1;
}

#--------------#
#  ロック解除  #
#--------------#
sub unlock {
	if ($lockkey == 1) { unlink($lockfile); }
	elsif ($lockkey == 2) { rmdir($lockfile); }
	$lockflag=0;
}

#--------------#
#  在室者処理  #
#--------------#
sub member {
	if ($mode ne "regist") {
		open(IN,"$memfile") || &error("Open Error : $memfile");
		@data = <IN>;
		close(IN);
	}

	$member="$robot◆";
	@new=();
	$flag=0;
	$flag2=0;
	foreach (@data) {
		($time2,$name2,$host2) = split(/<>/);
		# 60秒以上発言のない者は削除
		if ($times-60 > $time2) { next; }
		elsif ($host2 eq "$host") {
			# 退室者は削除
			if ($_[0] eq 'bye') { next; }

			# ホスト名が同一であれば時間と名前を更新
			$_ = "$times<>$in{'name'}<>$host<>\n";
			$name2 = $in{'name'};
			$flag=1;
		}
		# 更新用配列 @new に追加
		if ($name2 ne "$host2") {
			push(@new,$_);

			# 参加者表示用文字列を作成
			if (!$flag2) { $member .= "$name2◇"; $flag2=1; }
			else { $member .= "$name2◆"; $flag2=0; }
		}
	}
	# 新規参加者を追加
	if (!$flag && !$bye) {
		if ($in{'name'} ne "$host") {
			push(@new,"$times<>$in{'name'}<>$host<>\n");
			if (!$flag2) { $member .= "$in{'name'}◇"; }
			else { $member .= "$in{'name'}◆"; }
		}
	}
	# 参加者数を認識
	$num = @new + 1;

	# ファイル更新
	if ($mode || $in{'retime'}) {
		open(OUT,">$memfile") || &error("Write Error : $memfile");
		eval "flock(OUT,2);";
		truncate(OUT,0);
		seek(OUT,0,0);
		print OUT @new;
		close(OUT);
	}
}

#--------------------------------#
#  IPアドレスによるアクセス拒否  #
#--------------------------------#
sub deny {
	local(@lines, $flag);
	open(IN,"$denyfile") || &error("Open Error : $denyfile");
	@lines = <IN>;
	close(IN);

	$flag=0;
	foreach (@lines) {
		chop;
		s/\*/\.\*/g;
		if ($host =~ /$_/i) { $flag=1; last; }
	}
	if ($flag) { &error('申し訳ありませんが、現在ご利用できません'); }
}

#-----------------#
#  URLエンコード  #
#-----------------#
sub url_enc {
	local($_) = @_;

	s/(\W)/'%' . unpack('H2', $1)/eg;
	s/\s/+/g;
	$_;
}

__END__

