<< Webチャット >>

じゃぁ調子に乗って今度はWebチャット作っちゃえ!
しかもフレームを使っちゃいます。
フレームを使う場合、それを定義するHTMLファイルが必要なので、
サンプルを「.txt」の形式で置いておきます。もちろん実際使用するときは
拡張子を「.html」に変えてね。

フレームのサンプル(chat_fr.txt)はここ

それではメモ帳を起動!ファイルに名前をつけようね。
「chat.cgi」とか。
メモ帳で編集してる間は、拡張子を「.txt」にしておくといいかもね。
じゃ、始めましょうか。
そうそう、「一行伝言板」で説明したことは、いちいち
説明しなおさないと思うので、そっちも必ず見てね。

サンプルスクリプト(chat.txt)はここ

#!/usr/local/bin/perl

「一行伝言板」でも説明しましたが、この部分は、
Perl(CGIを動かす言語)のありかを示すものです。
あなたの加入しているプロバイダによって違うので注意!
(ビワローブなら、このままで大丈夫です。)
ただ、これは必ず一行目に置くこと。この行の前に改行だけの行とかあっても
エラーが出ますよ。

# chat v0.1 by 1999 Deihaz

はい、「#」はコメントの記号です。
プログラムはその部分を無視してくれます。
(一行目の「#!」は例外なので省略不可ですが。)
この行では、チャットの名前・バージョン・著作権を明示してます。

require 'jcode.pl';

はい、日本語チャットにしたいならこれは必須ですね。
「jcode.txt」を置いておきますので、DLしたあと
拡張子を「.pl」にかえてご利用ください。

jcode.txtです

# 設定

$title = 'Webチャット';
$homepage = 'http://www.biwa.ne.jp/~bighair/';
$max = 25;

$method = 'POST';
$script = 'chat.cgi';
$logfile = 'chat.log';

# 設定終わり

ここは、いろんな準備をしているところです。
「$」というのは、「変数」を意味しております。
詳しくは、「一行伝言板」の説明を参考にしてね。
ちなみに、変数の名前は大文字と小文字が区別されるのでご注意を。

$title:チャットの名前
$homepage:ホームページのアドレス(チャットから脱出するためのアドレス)
$max:メッセージを何行蓄えておくか。つまりこれだと25行
$method:データを送信する方法。「POST」か「GET」ですな
$script:このスクリプトのファイル名
$logfile:ログを保存しておくためのファイルのファイル名

$maxに入れる「25」は数値ですから、「'」では囲みません。
「POST」「GET」については、詳しく説明しませんが、
まあ必要がない限り、「POST」にしておいて下され。

if ($ENV{'REQUEST_METHOD'} eq 'POST') {
	read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
}
else {
	$buffer = $ENV{'QUERY_STRING'};
}

はい、お決まりの部分です。
フォームから送られてきたデータを「POST」「GET」で場合分けして、
「$buffer」という変数に入れているわけです。
では「if」のおさらいね。
if (条件1) {処理A} elsif (条件2) {処理B} else {処理C}
もし条件1が成立しているなら処理Aを実行し、
そうでなくとも、もし条件2が成立しているなら処理Bを実行し、
どれにもあてはまらなければ、処理Cを実行する、という構文でした。

&decode;

「&」は、サブルーチンに飛んでいく記号でした。
この場合、「&decode;」ですから、スクリプトの後ろの方の
sub decode {・・・}
に飛んでいくわけですな。
ちなみに「sub decode」では、送られてきたデータを使えるように変換するという
作業をしております。
サブルーチンが終わると、またここに戻ってきます。

if ($form{'mode'} eq 'init') {
	&initform;
}
elsif ($form{'mode'} eq 'enter') {
	&msgform;
}
elsif (($form{'mode'} eq 'msg') and $form{'comment'}) {
	&regist;
}
elsif ($form{'mode'} eq 'leave') {
	&finish;
}

&viewlog;

はい。「$form{'mode'}」の内容によって行き先を変えてます。

「init」なら、「sub initform」(名前入力フォームを表示)へ、
「enter」なら、「sub msgform」(メッセージ入力フォーム表示)へ、
「msg」なら、「sub regist」(ファイルへ書き込む)へ、
「leave」なら、「sub finish」(退室処理)へ飛びます。
その後、「sub viewlog」に飛ぶわけですが、
「&initform;」「&msgform;」「&finish;」はここには戻ってきませんので、
「&viewlog;」にたどり着くのは、「&regist;」に行った場合と、if文がすべて
成立しなかった場合だけです。

sub initform {
	&header1;
	&header2;
	print <<"EOH";
<p><font size="5" color="blue"><b>$title</b></font></p>
<table><tr>
<td><form method="$method" action="$script" target="form">
<p><input type="hidden" name="mode" value="enter">
<input type="hidden" name="reload" value="10">
お名前(タグ可)<input type="text" name="name" size="20"><br>
<input type="submit" value="入室してみる"></p>
</form></td>
<td><form action="$homepage" target="_top">
<input type="submit" value="ホームへ戻る"></p>
</form></td>
</tr></table>
</body>
</html>
EOH
	exit;
}

さて、サブルーチンの解説にいきますか。
sub initform {・・・}は、サブルーチンの宣言です。
「&initform」から、ここに飛んでくるわけですね〜。
で、今回は「&header1;」「&header2;」と、
ヘッダを記述するサブルーチンが2つあるけど、
JavaScriptを使うときにこの2つの間にはさめるように
2つに分けただけだよん。

「print <<"EOH";」ですが、
この次の行から「EOH」まで(ヒアドキュメントという)を、
まとめて出力するという命令であります。

「EOH」が出てくるまではHTMLの記述です。
もちろん、このまま出力されるので、好きなようにいじることが出来ますよん。
ここでは、名前入力フォームの記述をしています。
<form>タグに、「target」というのが出てきてますが、
これは<a href=・・・>で使う「target」と役目は一緒です。
このチャットではフレームを使用しているので、「target」の指定が
重要になってくるわけです。
「target="form"」で、次のターゲットを上半分のフレームにします。
「target="_top"」で、フレームを解除します。
ついでに「target="_blank"」だと、新しいウィンドウで開きます。

はい。フォームを出力すれば後は用はないので、
「exit;」でCGIの実行を終わります。
「ついでにログも表示させればいいのに」と思う方もいらっしゃるでしょうが、
CGIが一度に出力できるHTMLは1ページ分だけなんですねぇ。
だからこのサブルーチンはこれで終わり。

sub msgform {
	&regist('in');
	&header1;
	print <<"EOH";
<SCRIPT LANGUAGE="JavaScript">
<!--
function clr() {
	document.mform.comment.value = "";
	document.mform.comment.focus();
}
// -->
</SCRIPT>
EOH
	&header2;
	print <<"EOH";
<p><font size="5" color="blue"><b>$title</b></font></p>
<table><tr>
<td><form name="mform" method="$method" action="$script" target="log" onSubmit="setTimeout(&quot;clr()&quot;, 10)">
<p><input type="hidden" name="mode" value="msg">
<input type="hidden" name="name" value="$form{'name'}">
台詞:<input type="text" name="comment" size="50"><br>
<input type="submit" value="発言/リロード"><input type="reset" value="クリア">
リロード:
<select name="reload">
	<option value="0">手動
	<option value="10" selected>10秒
	<option value="30">30秒
	<option value="60">1分
</select></p>
</form></td>
<td><form method="$method" action="$script" target="form">
<input type="hidden" name="mode" value="leave">
<input type="hidden" name="name" value="$form{'name'}">
<input type="submit" value="退室しとく">
</form></td>
</tr></table>
</body>
</html>
EOH
	exit;
}

うわ〜長いねぇ。
でもメッセージ入力フォームを書いてるだけだから、
むろんHTML分かってる人は、理解出来る事でしょう。

JavaScriptは、発言自動消去をするためのものです。
理解できない人はあまり気にしないように。つまりそこはいじらないように。

リロードの辺りとかは、「option value」のところの数字を
てきとーにいじって、好きな時間でリロード出来るようにするとよいでしょう。

はい、説明終わるよ!

sub viewlog {
	open LOG, "$logfile" or &error('Logfile cannot be opened');
	@lines = <LOG>;
	close LOG;
	&header1;
	if ($form{'reload'} != 0) {
		print qq(<META HTTP-EQUIV="refresh" CONTENT="$form{'reload'};URL=$script?reload=$form{'reload'}">\n);
	}
	&header2;
	print qq(<div align="right">リロード:);
	if ($form{'reload'} == 0) {
		print qq(手動);
	}
	else {
		print qq($form{'reload'}秒);
	}
	print qq(\n</div>\n);
	foreach $line (@lines) {
		($date, $name, $comm) = split(/\"/, $line);
		print qq(<b>[$name]</b> $comm <small>\($date\)</small><br>\n);
	}
	print qq(</body>\n</html>\n);
	exit;
}

これは、ログの表示ね。
一行伝言板でやったのとほぼ一緒です。
違うのは、自動リロードをするかしないかの部分だけです。

sub regist {
	($sec, $min, $hour, $mday, $mon, $year) = localtime(time);
	$mon++;
	if ($hour < 10) {
		$hour= "0$hour";
	}
	if ($min < 10) {
		$min = "0$min";
	}
	if ($sec < 10) {
		$sec = "0$sec";
	}
	$date = "$mon/$mday\ $hour:$min:$sec";
	if ($_[0] eq 'in') {
		$form{'comment'} = "<b><i>$form{'name'}さんが来ました</i></b>";
		$logname = '<font color=gray><i>謎の声</i></font>';
	}
	elsif ($_[0] eq 'fin') {
		$form{'comment'} = "<b><i>$form{'name'}さんは去りました</i></b>";
		$logname = '<font color=gray><i>謎の声</i></font>';
	}
	else {
		$logname = "$form{'name'}";
	}
	open LOG, "$logfile" or &error('Logfile cannot be opened');
	@lines = <LOG>;
	close LOG;
	@newlines = @lines;
	unshift (@newlines, "$date\"$logname\"$form{'comment'}\"$ENV{'REMOTE_HOST'}\n");
	if (@newlines > $max) {
		pop (@newlines);
	}
	open LOG, ">$logfile" or &error('Logfile cannot be written');
	eval 'flock(LOG, 2);';
	print LOG @newlines;
	close LOG;
}

はい。ファイルへの書き込みサブルーチンです。
入室時、退室時には、「来ました」「去りました」などのメッセージを
書き込むようになってます。

sub finish {
	&regist('fin');
	&header1;
	&header2;
	print qq(<p>$form{'name'}さん、また来て下さい。</p>\n);
	print qq(<p><a href="$homepage" target="_top">ホームページへ</a></p>\n);	
	print qq(</body>\n</html>\n);
	exit;
}

退室処理。
「&regist('fin')」で、「〜は去りました」を表示してます。
あとはあいさつをして、「exit;」で終わってます。

sub header1 {
	print qq(Content-type: text/html\n\n);
	print qq(<html>\n<head>\n);
}

sub header2 {
	print qq(</head>\n);
	print qq(<body text="#000000" bgcolor="#EEEEFF">\n);
}

はい。HTMLを表示する頭の部分ですね。
JavaScriptやリロードの設定がある場合は、
「header1」と「header2」の間にそれがはさまります。

sub decode {
	@pairs = split(/&/, $buffer);
	foreach $pair (@pairs) {
		($name, $value) = split(/=/, $pair);
		$value =~ tr/+/ /;
		$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
		$value =~ s/\"//g;
		$value =~ s/<!--(.|\n)*-->//g;
		$value =~ s/<html(.|\n)*>//ig;
		$value =~ s/<body(.|\n)*>//ig;
		$value =~ s/<script(.|\n)*>//ig;
		$value =~ s/<table(.|\n)*>//ig;
		$value =~ s/<form(.|\n)*>//ig;
		$value =~ s/<img(.|\n)*>//ig;
		$value =~ s/\r\n//g;
		$value =~ s/\r|\n//g;

		&jcode'convert(*value, 'sjis');
		$form{$name} = $value;
	}
	if ($form{'name'} eq '') {
		$form{'name'} = '???';
	}
}

え〜と、伝言板にも出てきた、デコード処理です。
今回は、名前の入力がなかった場合、名前を強制的に「???」にしています。
「???」を「だれかさん」とかにしてみるのも面白いかも。

sub error {
	&header1;
	&header2;
	print qq(<font color=red><p>システムエラーが発生しました!</p>\n);
	print qq(<p><b>$_[0]</b></p></font>\n);
	print qq(</body>\n</html>\n);
	exit;
}

おなじみエラー処理です。
伝言板のやつとまったく同じ事をやっとります。

さて、ログファイル「chat.log」も作っておきましょう。
メモ帳などで空のファイルを作り、ファイル名を「chat.log」に変更するだけです。
「chat.cgi」をCGIサーバにアップするときに一緒にアップしましょ〜ね。

はいお疲れ様。どうでしたか?
これでチャットも出来ました。
ぜひ使いやすいチャットを研究して、あなたのHPに設置しましょう!

戻る