diff -urN tiarra-20040609/ChangeLog tiarra-20040619/ChangeLog
--- tiarra-20040609/ChangeLog	2004-06-09 18:28:31.000000000 +0900
+++ tiarra-20040619/ChangeLog	2004-06-19 18:34:44.000000000 +0900
@@ -1,3 +1,26 @@
+2004-06-19  Topia  <topia@clovery.jp>
+
+	* doc-src/conf-main.tdoc:
+	  - ./tiarra --make-password のことを書き加えた。
+
+	* main/Timer.pm:
+	  - code 中で die が起こっても abort しないようにした。
+
+	* module/Channel/Mode/Oper/Grant.pm:
+	  - $myself が undef でないかチェックするようにした。
+
+	* module/Client/Cotton.pm:
+	  - 追加。いくつかの Cotton の不具合を回避する(予定)。
+	  - 今は network rejoin 時の自動 part を無視する。
+
+	* module/Client/Eval.pm:
+	  - runloop に括弧を付け(て関数形式に認識させ)るのを
+	    忘れていたので修正。
+
+	* module/Client/GetVersion.pm:
+	  - 追加。クライアントの接続時に CTCP Version を発行して
+	    クライアントのバージョンを取得する。
+
 2004-06-09  Topia  <topia@clovery.jp>
 
 	* main/IrcIO/Server.pm:
@@ -1697,7 +1720,7 @@
 
 	* これ以前のログは書いていません。
 
-#       Id: $Id: ChangeLog,v 1.153 2004/06/09 09:27:37 topia Exp $
+#       Id: $Id: ChangeLog,v 1.154 2004/06/19 09:33:41 topia Exp $
 #   Author: $Author: topia $
-#     Date: $Date: 2004/06/09 09:27:37 $
-# Revision: $Revision: 1.153 $
+#     Date: $Date: 2004/06/19 09:33:41 $
+# Revision: $Revision: 1.154 $
diff -urN tiarra-20040609/NEWS tiarra-20040619/NEWS
--- tiarra-20040609/NEWS	2004-06-09 18:28:31.000000000 +0900
+++ tiarra-20040619/NEWS	2004-06-19 18:34:44.000000000 +0900
@@ -1,3 +1,14 @@
+2004-06-19  Topia  <topia@clovery.jp>
+
+	* Client::Cotton
+	  - 追加。いくつかの Cotton の不具合を回避する(予定)。
+	    今は network rejoin 時の自動 part を無視します。
+	    Client::GetVersion と組み合わせると良いと思います。
+
+	* Client::GetVersion
+	  - 追加。クライアントの接続時に CTCP Version を発行して
+	    クライアントのバージョンを取得します。
+
 2004-06-04  Topia  <topia@clovery.jp>
 
 	* 全般
@@ -14,7 +25,7 @@
 
 2004-02-23  Topia  <topia@clovery.jp>
 
-	* Debug::RawLog.pm
+	* Debug::RawLog
 	  - 追加。生の IRC メッセージ(のようなもの?)を ::printmsg を使って
 	    表示する。
 
diff -urN tiarra-20040609/doc/module/Client.html tiarra-20040619/doc/module/Client.html
--- tiarra-20040609/doc/module/Client.html	2004-06-09 18:28:39.000000000 +0900
+++ tiarra-20040619/doc/module/Client.html	2004-06-19 18:34:46.000000000 +0900
@@ -36,6 +36,26 @@
     <hr />
     
     
+    <div id="Client::Cotton" class="element">
+      <span class="element-name">Client::Cotton</span> <span class="description">Cotton の行うおかしな動作のいくつかを無視する</span>
+      <div class="content">
+	<p class="comment">
+該当クライアントのオプション client-type に cotton や unknown と指定するか、<br />
+Client::GetVersion を利用してクライアントのバージョンを取得するように<br />
+してください。<br />
+</p>
+<p class="comment">
+part shield (rejoin 時に自動で行われる part の無視)を使用するか<br />
+</p>
+<span class="key">use-part-shield</span>:<span class="value">1</span><br />
+
+      </div>
+    </div>
+
+    
+    <hr />
+    
+    
     <div id="Client::Eval" class="element">
       <span class="element-name">Client::Eval</span> <span class="description">クライアントから Perl 式を実行できるようにする。</span>
       <div class="content">
@@ -50,6 +70,21 @@
     </div>
 
     
+    <hr />
+    
+    
+    <div id="Client::GetVersion" class="element">
+      <span class="element-name">Client::GetVersion</span> <span class="description">クライアントに CTCP Version を発行してバージョン情報を得る</span>
+      <div class="content">
+	<p class="comment">
+オプションはいまのところありません。<br />
+(開発者向け情報: 取得した情報は remark の client-version に設定されます。)<br />
+</p>
+
+      </div>
+    </div>
+
+    
     
 
   </body>
diff -urN tiarra-20040609/doc/module-toc.html tiarra-20040619/doc/module-toc.html
--- tiarra-20040609/doc/module-toc.html	2004-06-09 18:28:38.000000000 +0900
+++ tiarra-20040619/doc/module-toc.html	2004-06-19 18:34:46.000000000 +0900
@@ -88,8 +88,12 @@
 	  
 	  <li><a href="module/Client.html#Client::Cache">Client::Cache</a> <span class="module-description">データをキャッシュしてサーバに問い合わせないようにする</span></li>
 	  
+	  <li><a href="module/Client.html#Client::Cotton">Client::Cotton</a> <span class="module-description">Cotton の行うおかしな動作のいくつかを無視する</span></li>
+	  
 	  <li><a href="module/Client.html#Client::Eval">Client::Eval</a> <span class="module-description">クライアントから Perl 式を実行できるようにする。</span></li>
 	  
+	  <li><a href="module/Client.html#Client::GetVersion">Client::GetVersion</a> <span class="module-description">クライアントに CTCP Version を発行してバージョン情報を得る</span></li>
+	  
 	</ul>
       </li>
       
diff -urN tiarra-20040609/doc-src/conf-main.tdoc tiarra-20040619/doc-src/conf-main.tdoc
--- tiarra-20040609/doc-src/conf-main.tdoc	2004-06-09 18:28:32.000000000 +0900
+++ tiarra-20040619/doc-src/conf-main.tdoc	2004-06-19 18:34:45.000000000 +0900
@@ -1,5 +1,5 @@
 -*- outline -*-
-$Id: conf-main.tdoc,v 1.4 2004/02/23 02:46:18 topia Exp $
+$Id: conf-main.tdoc,v 1.5 2004/06/19 09:33:42 topia Exp $
 
 perlのソースに使うpodパーサを流用しているので、package文と=pod〜=cutで書く必要があります。
 ヘッダのinfo-is-ommitedとno-switchはどちらも値を真に定義しなければなりません。
@@ -47,6 +47,7 @@
 
 # Tiarraにクライアントが接続する際に要求するパスワードをcryptした文字列。
 # 空の文字列が指定されたり省略された場合はパスワードを要求しない。
+# crypt は ./tiarra --make-password で行えます。
 tiarra-password: xl7cflIcH9AwE
 
 # 外部プログラムからtiarraをコントロールする為のUNIXドメインソケットの名前。
diff -urN tiarra-20040609/main/Timer.pm tiarra-20040619/main/Timer.pm
--- tiarra-20040609/main/Timer.pm	2004-06-09 18:28:31.000000000 +0900
+++ tiarra-20040619/main/Timer.pm	2004-06-19 18:34:44.000000000 +0900
@@ -1,5 +1,5 @@
 # -----------------------------------------------------------------------------
-# $Id: Timer.pm,v 1.7 2004/03/27 10:41:17 admin Exp $
+# $Id: Timer.pm,v 1.8 2004/06/19 09:33:42 topia Exp $
 # -----------------------------------------------------------------------------
 # RunLoopに登録され、指定された時刻に起動するタイマーです。
 # 現在の実装では、精度は秒となっています。
@@ -136,8 +136,14 @@
     unless ($package_of_caller->isa('RunLoop')) {
 	croak "Only RunLoop may call method execute of Timer.\n";
     }
-    
-    $this->{code}->($this);
+
+    eval {
+	$this->{code}->($this);
+    }; if ($@) {
+	$this->notify_error(
+	    "Exception in Timer.\n".
+		"   $@");
+    }
 
     if (defined $this->{interval}) {
 	$this->{fire_time} += $this->{interval};
diff -urN tiarra-20040609/module/Channel/Mode/Oper/Grant.pm tiarra-20040619/module/Channel/Mode/Oper/Grant.pm
--- tiarra-20040609/module/Channel/Mode/Oper/Grant.pm	2004-06-09 18:28:32.000000000 +0900
+++ tiarra-20040619/module/Channel/Mode/Oper/Grant.pm	2004-06-19 18:34:45.000000000 +0900
@@ -1,5 +1,5 @@
 # -----------------------------------------------------------------------------
-# $Id: Grant.pm,v 1.6 2004/02/23 02:46:19 topia Exp $
+# $Id: Grant.pm,v 1.7 2004/06/19 09:33:42 topia Exp $
 # -----------------------------------------------------------------------------
 package Channel::Mode::Oper::Grant;
 use strict;
@@ -43,7 +43,7 @@
 	    my $ch_short = Multicast::detatch($ch_full);
 	    my $ch = $sender->channel($ch_short);
 	    my $myself = $ch->names($sender->current_nick);
-	    if ($myself->has_o && (!defined $mode || $mode !~ /o/)) {
+	    if (defined $myself && $myself->has_o && (!defined $mode || $mode !~ /o/)) {
 		if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix,$ch_full)) {
 		    # waitで指定された秒数の経過後に、キューに入れる。
 		    # 同時にキュー消化タイマーを準備する。
diff -urN tiarra-20040609/module/Client/Cotton.pm tiarra-20040619/module/Client/Cotton.pm
--- tiarra-20040609/module/Client/Cotton.pm	1970-01-01 09:00:00.000000000 +0900
+++ tiarra-20040619/module/Client/Cotton.pm	2004-06-19 18:34:45.000000000 +0900
@@ -0,0 +1,95 @@
+# -----------------------------------------------------------------------------
+# $Id: Cotton.pm,v 1.1 2004/06/19 09:33:42 topia Exp $
+# -----------------------------------------------------------------------------
+# copyright (C) 2004 Topia <topia@clovery.jp>. all rights reserved.
+package Client::Cotton;
+use strict;
+use warnings;
+use base qw(Module);
+use Mask;
+use Multicast;
+
+sub PART_SHIELD_EXPIRE_TIME (){5 * 60;}
+
+sub new {
+    my $class = shift;
+    my $this = $class->SUPER::new(@_);
+    $this;
+}
+
+sub _yesno {
+    my ($this, $value, $default) = @_;
+
+    return $default || 0 if (!defined $value);
+    return 0 if ($value =~ /[fn]/); # false/no
+    return 1 if ($value =~ /[ty]/); # true/yes
+    return 1 if ($value); # 数値判定
+    return 0;
+}
+
+sub message_io_hook {
+    my ($this,$msg,$io,$type) = @_;
+
+    if ($io->isa('IrcIO::Client') &&
+	    $this->is_cotton($io)) {
+	if ($this->_yesno($this->config->use_part_shield) &&
+		$type eq 'in' &&
+		    $msg->command eq 'PART' &&
+			Multicast::channel_p($msg->param(0)) &&
+				!defined $msg->param(1)) {
+	    my ($chan_short, $network_name) = Multicast::detach($msg->param(0));
+	    my $network = RunLoop->shared_loop->network($network_name);
+	    if (defined $network) {
+		my $expire = $network->remark(__PACKAGE__.'/part-shield/expire');
+		my $remark = $io->remark(__PACKAGE__.'/part-shield/'.$network_name);
+		if (defined $expire &&
+			$expire >= time()) {
+		    if (!defined $remark ||
+			    (defined $remark->{channels} &&
+				 !defined $remark->{channels}->{$chan_short})) {
+			$remark->{channels}->{$chan_short} = 1;
+			return undef;
+		    }
+		} else {
+		    # remove expired network info
+		    $network->remark(__PACKAGE__.'/part-shield/expire', undef, 'delete');
+		    $io->remark(__PACKAGE__.'/part-shield/'.$network_name, undef, 'delete');
+		}
+	    }
+	}
+    }
+    return $msg;
+}
+
+sub connected_to_server {
+    my ($this,$server,$new_connection) = @_;
+
+    if (!$new_connection) {
+	# reconnect
+	$server->remark(__PACKAGE__.'/part-shield/expire', time() + PART_SHIELD_EXPIRE_TIME);
+    }
+}
+
+sub is_cotton {
+    my ($this, $client) = @_;
+
+    return 1 if defined $client->remark('client-version') &&
+	$client->remark('client-version') =~ /(Cotton|Unknown) Client/;
+    return 1 if defined $client->option('client-type') &&
+	$client->option('client-type') =~ /(cotton|unknown)/;
+    return 0;
+}
+
+1;
+=pod
+info: Cotton の行うおかしな動作のいくつかを無視する
+default: off
+
+# 該当クライアントのオプション client-type に cotton や unknown と指定するか、
+# Client::GetVersion を利用してクライアントのバージョンを取得するように
+# してください。
+
+# part shield (rejoin 時に自動で行われる part の無視)を使用するか
+use-part-shield: 1
+
+=cut
diff -urN tiarra-20040609/module/Client/Eval.pm tiarra-20040619/module/Client/Eval.pm
--- tiarra-20040609/module/Client/Eval.pm	2004-06-09 18:28:31.000000000 +0900
+++ tiarra-20040619/module/Client/Eval.pm	2004-06-19 18:34:44.000000000 +0900
@@ -1,5 +1,5 @@
 # -----------------------------------------------------------------------------
-# $Id: Eval.pm,v 1.4 2004/06/04 12:57:30 topia Exp $
+# $Id: Eval.pm,v 1.5 2004/06/19 09:33:42 topia Exp $
 # -----------------------------------------------------------------------------
 package Client::Eval;
 use strict;
@@ -57,7 +57,7 @@
 
 # useful functions to call from eval
 sub network {
-    return runloop->network(shift);
+    return runloop()->network(shift);
 }
 
 sub runloop {
diff -urN tiarra-20040609/module/Client/GetVersion.pm tiarra-20040619/module/Client/GetVersion.pm
--- tiarra-20040609/module/Client/GetVersion.pm	1970-01-01 09:00:00.000000000 +0900
+++ tiarra-20040619/module/Client/GetVersion.pm	2004-06-19 18:34:44.000000000 +0900
@@ -0,0 +1,59 @@
+# -----------------------------------------------------------------------------
+# $Id: GetVersion.pm,v 1.1 2004/06/19 09:33:43 topia Exp $
+# -----------------------------------------------------------------------------
+# copyright (C) 2004 Topia <topia@clovery.jp>. all rights reserved.
+package Client::GetVersion;
+use strict;
+use warnings;
+use base qw(Module);
+use CTCP;
+
+sub CTCP_VERSION_EXPIRE_TIME (){5 * 60;}
+
+sub client_attached {
+    my ($this,$client) = @_;
+
+    my $msg = CTCP::make('VERSION', RunLoop->shared_loop->current_nick, 'PRIVMSG');
+    $msg->prefix(RunLoop->shared_loop->sysmsg_prefix(qw(system)));
+
+    $client->send_message($msg);
+    $client->remark(__PACKAGE__.'/fetching-version-expire',
+		    time() + CTCP_VERSION_EXPIRE_TIME);
+}
+
+sub message_io_hook {
+    my ($this,$msg,$io,$type) = @_;
+
+    if ($io->isa('IrcIO::Client')) {
+	if ($type eq 'in' && $msg->command eq 'NOTICE' &&
+		!Multicast::channel_p($msg->param(0)) &&
+		    defined $msg->param(1) &&
+			defined $io->remark(__PACKAGE__.'/fetching-version-expire')) {
+	    if ($io->remark(__PACKAGE__.'/fetching-version-expire')
+		    >= time()) {
+		my $ctcp = CTCP::extract($msg);
+		if (defined $ctcp) {
+		    my ($command, $text) = split(/ /, $ctcp, 2);
+		    if ($command eq 'VERSION') {
+			$io->remark('client-version', $text);
+			return undef;
+		    }
+		}
+	    } else {
+		$io->remark(__PACKAGE__.'/fetching-version-expire', undef, 'delete');
+	    }
+	}
+    }
+
+    return $msg;
+}
+
+1;
+=pod
+info: クライアントに CTCP Version を発行してバージョン情報を得る
+default: off
+
+# オプションはいまのところありません。
+# (開発者向け情報: 取得した情報は remark の client-version に設定されます。)
+
+=cut
diff -urN tiarra-20040609/sample.conf tiarra-20040619/sample.conf
--- tiarra-20040609/sample.conf	2004-06-09 18:28:36.000000000 +0900
+++ tiarra-20040619/sample.conf	2004-06-19 18:34:45.000000000 +0900
@@ -88,6 +88,7 @@
   
   # Tiarraにクライアントが接続する際に要求するパスワードをcryptした文字列。
   # 空の文字列が指定されたり省略された場合はパスワードを要求しない。
+  # crypt は ./tiarra --make-password で行えます。
   tiarra-password: xl7cflIcH9AwE
   
   # 外部プログラムからtiarraをコントロールする為のUNIXドメインソケットの名前。
@@ -861,6 +862,17 @@
   use-who-cache: 1
 }
 
+- Client::Cotton {
+  # Cotton の行うおかしな動作のいくつかを無視する
+
+  # 該当クライアントのオプション client-type に cotton や unknown と指定するか、
+  # Client::GetVersion を利用してクライアントのバージョンを取得するように
+  # してください。
+  
+  # part shield (rejoin 時に自動で行われる part の無視)を使用するか
+  use-part-shield: 1
+}
+
 - Client::Eval {
   # クライアントから Perl 式を実行できるようにする。
 
@@ -870,6 +882,13 @@
   command: eval
 }
 
+- Client::GetVersion {
+  # クライアントに CTCP Version を発行してバージョン情報を得る
+
+  # オプションはいまのところありません。
+  # (開発者向け情報: 取得した情報は remark の client-version に設定されます。)
+}
+
 - Debug::RawLog {
   # 標準出力にクライアントやサーバとの通信をダンプする。
 
