##### TADChat版豪華絢爛チャットライブラリ
### Kenran Chat Lib / 2007 &copy; 結城由羅＠世界忍者国 / BSD Lisence
### $Id: kenranlib.pl,v 1.7 2007/07/14 14:00:19 jyugoya Exp $
### 本体から分離、yadchatlib.plにマージ中

# YADChat Lib 読み込み
require './yadchatlib.pl';

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

###
### ユーティリティ
###

##### 関数：日付生成 #####
sub getDate {
    $ENV{'TZ'} = $CONF{'timezone'};
    local($times) = time;
    local($sec,$min,$hour,$mday,$mon) = localtime($times);
    return sprintf("%s/%s-%02d:%02d:%02d",$mon+1,$mday,$hour,$min,$sec);
}

##### 関数：ダイス #####
sub dice {
    local($num, $type) = @_;
    srand;
    #"$num個の$type面体";
    local($ret) = $num."d".$type.": ";
    for ($i=0; $i<$num; $i++) {
        $d = int(rand($type)) + 1;
        $sum += $d;
        $ret .= $d;
        if ($i < $num -1) { $ret .= " + " }
    }
    $ret .= " = $sum";
}

##### 関数：入力処理 #####
sub checkInput {
    $IN{'room'} = $query->param('room');
    $IN{'name'} = $query->param('name');
    $IN{'weight'} = $query->param('weight');
    $IN{'reverse'} = $query->param('reverse');
    $IN{'comment'} = $query->param('comment');
    $IN{'mode'} = $query->param('mode');
    $IN{'face'} = $query->param('face');
    $IN{'email'} = $query->param('email');
    $IN{'color'} = $query->param('color');
    $IN{'retime'} = $query->param('retime');
    $IN{'line'} = $query->param('line');

    foreach $key (keys %IN) {
        # 文字コードをEUC変換 (文字コードの統一とサニタイジングのため)
        local($c) = &jcode::getcode(\$IN{$key});
        #print "code: $c\n";
        if ($c eq 1 || $c eq 2) {
            &jcode::convert(\$IN{$key}, 'euc');
        }

        # タグ処理 (サニタイジング)
        $IN{$key} =~ s/&/&amp;/g;
        $IN{$key} =~ s/</&lt;/g;
        $IN{$key} =~ s/>/&gt;/g;
        $IN{$key} =~ s/"/&quot;/g; # "
        $IN{$key} =~ s/\r//g;
        $IN{$key} =~ s/\n//g;

        # 文字コードを処理系に合わせる
        local($code) = 'euc';
        if ($CONF{'charset'} eq 'Shift_JIS') {
            $code = 'sjis';
        } elsif ($CONF{'charset'} eq 'ISO-2202-JP') {
            $code = 'jis';
        }
        &jcode::convert(\$IN{$key}, $code);
    }
}

##### 関数：トリップ取得処理 #####
sub getTrip {
    local($name) = $_[0];
    local($tripped) = $name;
    # トリップ機能が有効ならば、トリップをそれ以外ならばそのままを返す
    if ($CONF{'usetrip'} && $name =~ /(.*)(#|＃)(.*)/){
        $trip = &genTrip($1, $3);
        $tripped = $1 . "◆" . $trip;
    } else {
        $tripped =~ s/◆/◇/g;
    }
    return $tripped;
}

##### 関数：トリップ生成 #####
sub genTrip {
    local($name, $key) = @_;
    local($salt) = substr($key."H.", 1, 2);
    $salt =~ s/[^\.-z]/\./go;
    $salt =~ tr/:;<=>?@[\\]^_`/ABCDEFGabcdef/; #`
    return substr(crypt($key, $salt), -10);
}

##### 関数：アクセス拒否処理 #####
sub checkDeny {
    local($client) = $_[0];
    local($find)=0;
    open(DENY, "$CONF{'denyfile'}")
        || &error("Open Error : denyfile : $CONF{'denyfile'} : $!");
    while (<DENY>) {
        s/\n//g;
        next if (!$_);
        s/\*/\.\*/g;
        if ($client =~ /$_/i) { $find=1; last; }
    }
    close(DENY);
    if ($find) { &error($CONF{'denymsg'}); }
}

###
# 発言除去処理
sub clear {
    local($client, @lines) = @_;
    local(@temp) = ();
    local($match)=0;
    foreach (@lines) {
        local($cip) = (split(/<>/))[5];
        if ($client eq $cip) {
            $match=1;
        } else {
            push(@temp, $_);
        }
    }
    if ($match) {
        $IN{'comment'}="All Clear (^-^)v";
        return @temp;
    }
}

###
#  ログ書き込み処理
sub writeDat {
    # モード選択
    local($mode) = $_[0];
    local($datfile) = $CONF{'datdir'}.'/'.$IN{'room'}.'.dat';
    local($lockfile) =  $CONF{'lockdir'}.'/'.$IN{'room'}.'.lock';

    # 名前の入力がなければエラー
    if ($IN{'name'} eq "") {
        &error("御記名願います。");
    }
    # トリップ処理
    local($tripped) = &getTrip($IN{'name'});

    $bye = 0; # グローバル変数
    local($name, $email, $color)= ("", "", "");
    # 入室モード
    if ($mode eq 'login') {
        # 入室メッセージ設定
        $IN{'comment'} = "$tripped $CONF{'in_msg'}";
        if ($CONF{'showip'}) {
            $IN{'comment'} .= " &lt;$client&gt;";
        }
        $email = "";
        $name  = $CONF{'navi'};
        $color = $CONF{'navicolor'};
    }
    # 退室モード
    elsif ($mode eq 'logout') {
        $IN{'comment'} = "$tripped $CONF{'out_msg'}";
        $email = "";
        $name  = $CONF{'navi'};
        $color = $CONF{'navicolor'};
        $bye=1;
    }
    # 通常モード
    else {
        # 入力がない場合はそのまま返る
        if ($IN{'comment'} eq "") { return; }

        if ($IN{'email'} eq "") {
            $name = "<B>$CONF{'pointer'}</B>";
        }
        else {
            $name = "<B><a href=\"mailto:$IN{'email'}\">";
            $name .= "$CONF{'pointer'}</a></B>";
        }
        $name .= " $tripped ";
        $email = $IN{'email'};
        $color = $IN{'color'};
    }

    # ログファイルのロック開始
    if ($CONF{'lockmode'}) { &lock($lockfile); }

    # ログファイルを開いてデータ読み込み (配列最大値に注意)
    open(DAT, "<$datfile") || &error("Open Error : datfile : $datfile");
    local(@lines) = <DAT>;
    close(DAT);

    # 発言除去処理 (オプション)
    # 除去コマンドを入力することで自分の記事を削除
    if ($CONF{'clearlog'} && $IN{'comment'} eq $CONF{'clearcom'}) {
        @lines = &clear($client, @lines);
    }

    # 最大記事数まで削る
    while ($CONF{'max'} <= @lines) {
        pop(@lines);
    }

    # 時間を取得
    local($date) = &getDate();

    # RP切り出し機能がONならばRPのみ抜き出し
    if ($CONF{'rplog'} == 1) {
        &writeRPLog($date, $IN{'name'}, $IN{'comment'});
    }

    # ログをフォーマットして更新
    local($newline) = "$date<>$name<>$email<>";
    $newline .= "$IN{'comment'}<>$color<>$client<>\n";
    unshift (@lines, $newline);
    open(DAT, ">$datfile") || &error("Write Error : datfile : $datfile");
    print DAT @lines;
    close(DAT);

    # ロック解除
    if ($CONF{'lockmode'}) { &unlock($lockfile); }
}

###
#  RP切り出し機能
sub writeRPLog {
    local($date, $name, $comment) = @_;
    # RP保存ファイル
    local($txtfile) = $CONF{'rpdir'}.'/'.$IN{'room'}.'.txt';
    local($lockfile) =  $CONF{'lockdir'}.'/'.$IN{'room'}.'.rplock';

    if ($comment =~ /「.+」/) {
        local($line) = "$name ：$comment ($date)\n";
        # ログファイルのロック開始
        if ($CONF{'lockmode'}) { &lock($lockfile); }
        open(TXT, ">>$txtfile") || &error("Write Error : txtfile : $txtfile");
        print TXT $line;
        close(TXT);
        # ロック解除
        if ($CONF{'lockmode'}) { &unlock($lockfile); }
    }
}

###
#  RPクリア機能（バックアップ機能つき）
sub clearRPLog {
    local($date, $name, $comment) = @_;
    # RP保存ファイル
    local($txtfile) = $CONF{'rpdir'}.'/'.$IN{'room'}.'.txt';
    local($lockfile) =  $CONF{'lockdir'}.'/'.$IN{'room'}.'.rplock';
    # RPバックアップ先ファイル
    local($bakfile) = $CONF{'rpdir'}.'/'.$IN{'room'}.'-bak.txt';

    # ログファイルのロック開始
    if ($CONF{'lockmode'}) { &lock($lockfile); }
    open(TXT, "<$txtfile") || &error("Read Error : txtfile : $txtfile");
    open(BAK, ">>$bakfile") || &error("Write Error : bakfile : $bakfile");
    while(<TXT>) {
        print BAK $_;
    }
    close(BAK);
    close(TXT);
    open(TXT, ">$txtfile") || &error("Write Error : txtfile : $txtfile");
    print TXT "old log is in $bakfile\n\n";
    close(TXT);
    # ロック解除
    if ($CONF{'lockmode'}) { &unlock($lockfile); }
}

###
### HTML生成関係
###

###
# ヘッダー出力処理 (共通)
sub printHeader {
    local($extra) = $_[0];

    print "Content-type: $CONF{'content-type'}\n";
    # GZIP圧縮対応コード (2006/02/10)
    if (${'HTTP_ACCEPT_ENCODING'} =~ /gzip/ && $gzip ne '') {
        if (${'HTTP_ACCEPT_ENCODING'} =~ /x-gzip/) {
            print "Content-encoding: x-gzip\n";
        } else {
            print "Content-encoding: gzip\n";
        }
        open (STDOUT, "|$gzip -1 -c"); # 圧縮は最速(-1)で
    }
    # GZIP圧縮対応、ここまで
    print "\n"; # ヘッダ終了
    print <<"EOM";
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="$CONF{'lang'}">
<head>
<meta http-equiv="Content-type" content="$CONF{'content-type'}" />
<meta http-equiv="Content-Language" content="$CONF{'lang'}" />
<meta name='robots' content='noindex, nofollow' />
<script type='text/javascript' src='$CONF{'javascript'}'></script>
<link rel="stylesheet" type="text/css" href="$CONF{'chatstyle'}">
<title>$CONF{'title'}</title>
$extra
</head>
EOM
}

##### 関数：ログファイルへの移動表示 #####
sub printGoToLog {
    &printHeader();

    print $query->p("このチャットルームは既に終了しています");
    print $query->p("<a href=\"$CONF{'logdir'}/$IN{'room'}.html\" target=\"_top\">[ ログを見る ]</a>");

    # 表示終了
    print $query->end_html;
    exit(0);
}

###
# フレーム出力処理
sub printFrame {
    # クッキーにリロード時間／行数指定がない場合は初期値を代入
    if ($CK{'retime'} eq "") { $CK{'retime'} = $CONF{'retime'}; }
    if ($CK{'line'} eq "") { $CK{'line'} = $CONF{'line'}; }

    $formsrc = "$CONF{'chatcgi'}?room=$IN{'room'}&mode=form&retime=$CK{'retime'}&line=$CK{'line'}";
    $logsrc = "$CONF{'chatcgi'}?room=$IN{'room'}&mode=view&retime=$CK{'retime'}&line=$CK{'line'}";

    &printHeader();
    print <<"EOM";
<frameset rows="177,*" border=0>
<frame name="form" src="$formsrc">
<frame name="log" src="$logsrc">
<noframes>
<body>フレーム非対応のブラウザの方は利用できません</body>
</noframes>
</frameset>
</html>
EOM
    exit(0);
}

###
# プルダウンメニュー生成
sub genPullDown {
    local($script) = "$CONF{'chatcgi'}?room=$IN{'room'}&mode=view&retime=0";
    local($pulldown) =<<"EOM";
<b><a href="javaScript:pullDown()">コチラから入室せずにログが読めます</a></b> <div id="menu" style="position:absolute; visibility:hidden; background-color:#ffbdde; width:60px;">
<a href="$script&line=500" target="_blank">　５００</a><BR>
<a href="$script&line=1000" target="_blank">　１０００</a><BR>
<a href="$script&line=1500" target="_blank">　１５００</a><BR>
<a href="$script&line=2000" target="_blank">　２０００</a><BR>
</div>
EOM
    return $pulldown;
}

###
# 更新時間選択部の文字列生成
sub genReloadTimeSelection {
    # 入力による設定があればそちらを優先
    if ($IN{'retime'} ne "") {
        $CK{'retime'} = $IN{'retime'};
    }
    # 空ならば初期値に設定
    if ($CK{'retime'} eq "") {
        $CK{'retime'} = $CONF{'retime'};
    }

    local($reload) = "更新 <select name=retime>\n";
    foreach (@reload) {
        if ($CK{'retime'} == $_) {
            $reload .= "<option value=\"$_\" selected>$_秒\n";
        } else {
            $reload .= "<option value=\"$_\">$_秒\n";
        }
    }
    $reload .= "</select>\n";
    return $reload;
}

###
# 入室情報入力部の文字列生成
sub genLoginInput {
    local($login) =<<"EOM";
<b>NAME</b> <input type="text" name="name" size="25" value="$CK{'name'}"><br>
<b>EMail</b> <input type="text" name="email" size="25" value="$CK{'email'}"><br>
<input type="hidden" name="room" value="$IN{'room'}">
EOM
    return $login;
}

###
# チャット情報入力部の文字列生成
sub genChatInput {
    # トリップ名の処理
    local($tripped) = &getTrip($IN{'name'});
    local($chat) = << "EOM";
<input type="hidden" name="mode" value="chat">
<input type="hidden" name="room" value="$IN{'room'}">
<input type="hidden" name="name" value="$IN{'name'}">
<input type="hidden" name="email" value="$IN{'email'}">
<b>NAME</b>：　<font color="$IN{'color'}">$tripped</font>
<input type="submit" value="発言／リロード">
<input type="reset" value="クリア"><br>
<b>発言</b>　：　<input type="text" size="85" name="comment"><br>
EOM
    return $chat;
}

###
# 行数選択部の文字列生成
sub genLineNumSelection {
    # 入力による設定があればそちらを優先
    if ($IN{'line'} ne "") {
        $CK{'line'} = $IN{'line'};
    }
    # 空ならば初期値に設定
    if ($CK{'line'} eq "") {
        $CK{'line'} = $CONF{'line'};
    }

    local($lines) = "行数 <select name=line>\n";
    foreach (@line) {
        if ($CK{'line'} == $_) {
            $lines .= "<option value=\"$_\" selected>$_行\n";
        } else {
            $lines .= "<option value=\"$_\">$_行\n";
        }
    }
    $lines .= "</select><br>\n";
    return $lines;
}

###
# 文字色選択(ラジオボタン)の出力処理
sub genColorRadioBottun {
    # 入力による設定があればそちらを優先
    if ($IN{'color'} ne "") {
        $CK{'color'} = $IN{'color'};
    }
    # 空ならば初期値に設定
    if ($CK{'color'} eq "") {
        $CK{'color'} = $CONF{'color'};
    }

    local($colors) = "<B>文字色選択</B><BR><BR>\n";

    local($half) = int (@COLORS / 2); # 半分に割る
    local($i=0);
    foreach (@COLORS) {
        $i++;
        if ($CK{'color'} eq $_) {
            $colors .= "<input type=radio name=color value=\"$_\" checked>";
        } else {
            $colors .= "<input type=radio name=color value=\"$_\">";
        }
        $colors .= "<font color=\"$_\"><B>**</B></font>\n";
        if ($i == $half) { $colors .= "<br>\n"; }
    }
    return $colors;
}

###
# 文字色選択(セレクトメニュー)の出力処理
sub genColorSelection {
    # 入力による設定があればそちらを優先
    if ($IN{'color'} ne "") {
        $CK{'color'} = $IN{'color'};
    }
    # 空ならば初期値に設定
    if ($CK{'color'} eq "") {
        $CK{'color'} = $CONF{'color'};
    }

    local($colors) = "文字色 <select name=color>\n";
    for ($i = 0; $i < scalar(@COLORS); $i++) {
        if ($IN{'color'} eq $COLORS[$i]) {
            $colors .= "<option value=\"$COLORS[$i]\" selected>$IROIRO[$i]\n";
        } else {
            $colors .= "<option value=\"$COLORS[$i]\">$IROIRO[$i]\n";
        }
    }
    $colors .= "</select>\n";
}

###
# 顔文字選択部の文字列生成
sub genFaceSelection {
    local ($face) = "かお文字 <select name=\"face\">";
    $face .= "<option value=\"\">なし\n";
    foreach (@faces) {
        $face .= "<option value=\"$_\">$_\n";
    }
    $face .= "</select>\n";
    return $face;
}

###
#  入室用フォーム出力
sub printEntryForm {
    # プルダウンメニューの生成
    local($loglink) = '';
    if ($CONF{'loglink'} == 1) {
        $loglink = &genLogLinkString($IN{'room'});
    } elsif ($CONF{'loglink'} == 2) {
        $loglink = &genPullDown();
    }

    local($login) = &genLoginInput();
    local($reload) = &genReloadTimeSelection();
    local($lines) = &genLineNumSelection();
    local($colors) = &genColorRadioBottun();

    # ヘッダーを出力
    &printHeader();
    local($kazari) = "<font color=\"#DB5673\">＊＊＊</font>";
    print <<"EOM";
<body>
<form method="$CONF{'method'}" action="$CONF{'chatcgi'}"
 target="form" name="entry">
<input type=hidden name=mode value="login">
[<a href="$CONF{'indexurl'}" target="_top">BACK</a>]
 $CONF{'topdesc'} $loglink <br><br>
<h1>$kazari $CONF{'title'} $kazari</h1>
<hr>
<table border="0" cellspacing="0" cellpadding="0" height="100">
<tr align="right">
<td>$login $reload $lines</td>
<td width="100" align="center"><br>
<input type="submit" value="入室"></td>
<td align=center valign=top nowrap><br>
$colors
</td></tr></table></form>
<SCRIPT LANGUAGE="JavaScript">
<!--
self.document.entry.name.focus();
//-->
</SCRIPT>
</body></html>
EOM
    exit(0);
}

### 関数：軽量シンプル版ログイン画面 ###
sub printEntryFormAndDat {
    local($login) = &genLoginInput();
    local($colors) = &genColorSelection();

    # ヘッダーを出力
    &printHeader();

    print <<"EOM";
<body>
<form method="$CONF{'method'}" action="$CONF{'chatcgi'}"
 target="_top" name="entry">
<input type="hidden" name="mode" value="login">
<input type="hidden" name="weight" value="light">
<input type="hidden" name="line" value="20">
<input type="hidden" name="retime" value="0">
[<a href="$CONF{'indexurl'}" target="_top">戻る</a>]
[<a href="$CONF{'chatcgi'}?room=$IN{'room'}&weight=light&line=$CONF{'lightline'}">リロード</a>]
<h1>$CONF{'title'}</h1>
<hr>
$login
$colors
<input type="submit" name="login" value="入室">
<hr>
EOM

    print &genMember();
    local($datfile) = $CONF{'datdir'}."/".$IN{'room'}.".dat";
    print &formatDat($datfile, $CONF{'lightline'}, 'light', $IN{'reverse'});

    print $query->hr;

    print &genCopyright();
    print $query->end_html;
    exit(0);
}

### 関数：軽量シンプル版チャット画面 ###
sub printChatFormAndDat {
    local($chat) = &genChatInput();;
    local($colors) = &genColorSelection();

    # ログ書き込み処理
    &writeDat($IN{'mode'});

    # クッキーの設定
    local($ck) = "name<>$IN{'name'}";
    $ck .= "<>email<>$IN{'email'}";
    $ck .= "<>color<>$IN{'color'}";
    $ck .= "<>retime<>$IN{'retime'}";
    $ck .= "<>line<>$IN{'line'}";
    &setCookie($CONF{'cookiekey'}, $ck, $CONF{'expire'});

    # ヘッダーを出力
    &printHeader();

    print <<"EOM";
<body>
<br>
<h1>$CONF{'title'}</h1>
<hr>
<form method="$CONF{'method'}" action="$CONF{'chatcgi'}"
 target="_top" name="chat">
<input type="hidden" name="line" value="$CONF{'lightline'}">
<input type="hidden" name="weight" value="light">
<input type="hidden" name="retime" value="0">
$chat
$colors
</form>

<form action="$CONF{'chatcgi'}" method="$CONF{'method'}" target="_top"
 name="logout">
<input type="submit" name="logout" value="退室">
<input type="hidden" name="mode" value="logout">
<input type="hidden" name="name" value="$IN{'name'}">
<input type="hidden" name="room" value="$IN{'room'}">
<input type="hidden" name="weight" value="light">
</form>
<hr>
EOM

    print &genMember();
    local($datfile) = $CONF{'datdir'}."/".$IN{'room'}.".dat";
    print &formatDat($datfile, $IN{'line'}, 'light', $IN{'reverse'});

    print $query->hr;
    print &genCopyright();
    print $query->end_html;
    exit(0);
}

###
# 発言フォーム出力
sub printChatForm {
    # プルダウンメニューの生成
    local($loglink) = '';
    if ($CONF{'loglink'} == 1) {
        $loglink = &genLogLinkString($IN{'room'});
    } elsif ($CONF{'loglink'} == 2) {
        $loglink = &genPullDown();
    }

    local($chat) = &genChatInput();
    local($face) = &genFaceSelection();
    local($colors) = &genColorSelection();
    local($lines) = &genLineNumSelection();
    local($reload) = &genReloadTimeSelection();

    # ランキングの生成
    if ($CONF{'ranking'}) {
        local($ranking) = &genRanking();
    }

    # ログ書き込み処理
    &writeDat('login');

    # クッキーの設定
    local($ck) = "name<>$IN{'name'}";
    $ck .= "<>email<>$IN{'email'}";
    $ck .= "<>color<>$IN{'color'}";
    $ck .= "<>retime<>$IN{'retime'}";
    $ck .= "<>line<>$IN{'line'}";
    &setCookie($CONF{'cookiekey'}, $ck, $CONF{'expire'});

    # ヘッダ出力
    &printHeader();
    print <<"EOM";
<body>
<form name="send" method="$CONF{'method'}" action="$CONF{'chatcgi'}"
 target="log" onSubmit="setTimeout(&quot;autoclear()&quot;,10)">
<table border=0><tr><td colspan=3>
<b><font color="#DB5673" size="2">＊＊＊</font>
$CONF{'title'}
<font color="#DB5673" size="2">＊＊＊</font></b>　
<br> $CONF{'chatalart'} $loglink
<br><br>$chat
</td></tr>
<tr><td>$face $colors $reload $lines</td></form>
<td valign="top">
<form action="$CONF{'chatcgi'}" method="$CONF{'method'}" target="form">
<input type="submit" value="退室宣言">
<input type="hidden" name="mode" value="logout">
<input type="hidden" name="name" value="$IN{'name'}">
<input type="hidden" name="room" value="$IN{'room'}">
</td></form>
<td>
<form name="cmode">
<input type="checkbox" name="autoclear" checked>
発言自動消去</td></form></tr></table><br>
[<a href="$CONF{'adminscript'}?mode=enter&retime=$IN{'retime'}&line=$IN{'line'}" target="log">管理用</a>]
$ranking
</body></html>
EOM
    exit(0);
}

###
#  在室者処理
sub genMember {
    local($mode) = $_[0];
    local($memfile) = $CONF{'memdir'}.'/'.$IN{'room'}.'.dat';
    local($memlock) = $CONF{'lockdir'}.'/'.$IN{'room'}.'.memlock';

    # ロックをかける
    if ($CONF{'lockmode'}) { &lock($memlock); }

    # メンバー一覧取得
    open(MEM, "<$memfile")
        || &error("Open Error : memfile : $memfile");
    local(@line) = <MEM>;
    close(MEM);

    # 変数初期化
    local(@new) = ();
    local($member, $update, $even) = ("", 0, 0);
    local($time0, $name0, $client0) = (0, '', '');
    # トリップの処理
    local($tripped) = &getTrip($IN{'name'});
    # 現在時刻を取得
    local($nowtime) = time;
    foreach (@line) {
        ($time0, $name0, $client0) = split(/<>/);

        # 一定時間以上発言のない者は削除
        if ($nowtime - $CONF{'memexp'} > $time0) { next; }

        # クライアントIPが同一かつ名前が一緒であれば更新処理をする
        elsif ($client0 eq $client && $name0 eq $tripped) {
            # 退室者は削除
            if ($mode eq 'logout') { next; }

            # 時間と名前を更新して追加
            push (@new, "$nowtime<>$tripped<>$client<>\n");
            $name0 = $tripped;
            $update=1;
        }
        # それ以外は問答無用で更新用配列 @new に追加
        else { push(@new, $_); }

        # 参加者表示用文字列を作成 (偶数奇数でマーク変更)
        if (!$even) {
            $member .= "$name0★"; $even=1;
        }
        else {
            $member .= "$name0☆"; $even=0;
        }
    }

    # 更新処理以外でログアウトもしていないならば新規参加者を追加
    if (!$update && !$bye) {
        if ($IN{'name'} ne $client) {
            push(@new, "$nowtime<>$tripped<>$client<>\n");
            if (!$even) {
                $member .= "$tripped★";
            }
            else {
                $member .= "$tripped☆";
            }
        }
    }

    # ファイルを更新する
    if ($IN{'mode'} || $IN{'retime'}) {
        open(MEM,">$memfile")
            || &error("Write Error : memfile : $memfile");
        print MEM @new;
        close(MEM);
    }

    # ロック解除
    if ($CONF{'lockmode'}) { &unlock($memlock); }

    # 参加者数を認識
    local($num) = 0;
    $num = @new;
    return "参加者($num)：".$member;
}

###
#  退室処理
sub printLogout {
    &writeDat('logout');
    local($member) = &genMember('logout');
    local($tripped) = &getTrip($IN{'name'});
    &printHeader();
    print <<"EOM";
<body>
<center><BR><BR><h3>お疲れ様でした、$tripped さん。またお会いしましょう。</h3>
<form action="$CONF{'indexurl'}" target="_top">
<input type=submit value="離脱"></form></center>
</body></html>
EOM
    exit(0);
}

###
#  ログ表示
sub viewDat {
    # リロードヘッダ
    local($extra) = '';
    if ($IN{'retime'} != 0) {
        local($ename) = &url_enc($IN{'name'});
        local($script) = "$CONF{'chatcgi'}?room=$IN{'room'}&mode=view&retime=$IN{'retime'}&line=$IN{'line'}&name=$ename";
        $extra = "<meta http-equiv=\"refresh\"";
        $extra .= " content=\"$IN{'retime'}; URL=$script\">\n";
    }
    &printHeader($extra);

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

    local($datfile) = $CONF{'datdir'}."/".$IN{'room'}.".dat";
    print &formatDat($datfile, $IN{'line'}, 'normal', $IN{'reverse'});

    print $query->hr . "\n";

    # 著作権を表示（削除禁止）
    print &genCopyright();
    print $query->end_html . "\n";
    exit(0);
}

# End of kenranlib
1;
