| 1 | # ----------------------------------------------------------------------------- |
|---|
| 2 | # $Id$ |
|---|
| 3 | # ----------------------------------------------------------------------------- |
|---|
| 4 | package Log::Logger; |
|---|
| 5 | use strict; |
|---|
| 6 | use warnings; |
|---|
| 7 | use Multicast; |
|---|
| 8 | |
|---|
| 9 | our $MARKER = { |
|---|
| 10 | myself => { |
|---|
| 11 | PRIVMSG => ['>','<'], |
|---|
| 12 | NOTICE => [')','('], |
|---|
| 13 | }, |
|---|
| 14 | priv => { |
|---|
| 15 | PRIVMSG => ['-','-'], |
|---|
| 16 | NOTICE => ['=','='], |
|---|
| 17 | }, |
|---|
| 18 | channel => { |
|---|
| 19 | PRIVMSG => ['<','>'], |
|---|
| 20 | NOTICE => ['(',')'], |
|---|
| 21 | }, |
|---|
| 22 | }; |
|---|
| 23 | |
|---|
| 24 | sub new { |
|---|
| 25 | my ($class,$enstringed_callback,$exception_object,@exceptions) = @_; |
|---|
| 26 | # enstringed_callback: |
|---|
| 27 | # メッセージをログ文字列化した時に呼ばれる関数。CODE型。 |
|---|
| 28 | # 引数を二つ取り、一つ目はチャンネル名、二つ目はログ文字列。 |
|---|
| 29 | # exception_object: |
|---|
| 30 | # exceptionsで指定されたメソッドを呼ぶとき、どのオブジェクトで呼ぶか。 |
|---|
| 31 | # exceptions: |
|---|
| 32 | # 特定のメッセージのログ文字列化をオーバーライドする |
|---|
| 33 | # 'S_PRIVMSG'等。 |
|---|
| 34 | # 引数は(Tiarra::IRC::Message,IrcIO)、戻り値は[チャンネル名,ログ文字列]の配列 |
|---|
| 35 | my $this = { |
|---|
| 36 | enstringed => $enstringed_callback, |
|---|
| 37 | exception_object => $exception_object, |
|---|
| 38 | exceptions => do { |
|---|
| 39 | my %hash = map { $_ => 1 } @exceptions; |
|---|
| 40 | \%hash; |
|---|
| 41 | }, |
|---|
| 42 | }; |
|---|
| 43 | bless $this,$class; |
|---|
| 44 | } |
|---|
| 45 | |
|---|
| 46 | sub log { |
|---|
| 47 | my ($this,$msg,$sender) = @_; |
|---|
| 48 | my $prefix = do { |
|---|
| 49 | if ($sender->isa('IrcIO::Server')) { |
|---|
| 50 | 'S'; |
|---|
| 51 | } |
|---|
| 52 | elsif ($sender->isa('IrcIO::Client')) { |
|---|
| 53 | 'C'; |
|---|
| 54 | } |
|---|
| 55 | }; |
|---|
| 56 | my $method_name = "${prefix}_".$msg->command; |
|---|
| 57 | my @results; |
|---|
| 58 | # このメソッドはexceptionsで定義されているか? |
|---|
| 59 | if (defined $this->{exceptions}->{$method_name}) { |
|---|
| 60 | eval { |
|---|
| 61 | @results = $this->{exception_object}->$method_name($msg,$sender); |
|---|
| 62 | }; if ($@) { |
|---|
| 63 | RunLoop->shared->notify_error($@); |
|---|
| 64 | } |
|---|
| 65 | } |
|---|
| 66 | else { |
|---|
| 67 | # このクラスにメソッドはあるか? |
|---|
| 68 | if ($this->can($method_name)) { |
|---|
| 69 | eval { |
|---|
| 70 | @results = $this->$method_name($msg,$sender); |
|---|
| 71 | }; if ($@) { |
|---|
| 72 | RunLoop->shared->notify_error($@); |
|---|
| 73 | } |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | foreach (@results) { |
|---|
| 78 | $this->{enstringed}->($_->[0],$_->[1]); |
|---|
| 79 | } |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | sub S_JOIN { |
|---|
| 83 | my ($this,$msg,$sender) = @_; |
|---|
| 84 | |
|---|
| 85 | $msg->param(0) =~ m/^([^\x07]+)(?:\x07(.*))?/; |
|---|
| 86 | my ($ch_name,$mode) = ($1,(defined $2 ? $2 : '')); |
|---|
| 87 | $mode =~ tr/ov/@+/; |
|---|
| 88 | |
|---|
| 89 | [$msg->param(0), |
|---|
| 90 | sprintf('+ %s%s (%s) to %s', |
|---|
| 91 | $mode,$msg->nick,$msg->prefix,$msg->param(0))]; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | sub S_PART { |
|---|
| 95 | my ($this,$msg,$sender) = @_; |
|---|
| 96 | if (defined $msg->param(1)) { |
|---|
| 97 | [$msg->param(0), |
|---|
| 98 | sprintf('- %s from %s (%s)', |
|---|
| 99 | $msg->nick,$msg->param(0),$msg->param(1))]; |
|---|
| 100 | } else { |
|---|
| 101 | [$msg->param(0), |
|---|
| 102 | sprintf('- %s from %s', |
|---|
| 103 | $msg->nick,$msg->param(0))]; |
|---|
| 104 | } |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | sub S_KICK { |
|---|
| 108 | my ($this,$msg,$sender) = @_; |
|---|
| 109 | # RFC2812には、「サーバはクライアントに複数のチャンネルやユーザのKICKメッセージを |
|---|
| 110 | # 送っては「いけません」。これは、古いクライアントソフトウェアとの下位互換のためです。」とある。 |
|---|
| 111 | [$msg->param(0), |
|---|
| 112 | sprintf('- %s by %s from %s (%s)', |
|---|
| 113 | $msg->param(1),$msg->nick,$msg->param(0),$msg->param(2))]; |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | sub S_INVITE { |
|---|
| 117 | my ($this,$msg,$sender) = @_; |
|---|
| 118 | [$msg->param(1), |
|---|
| 119 | sprintf 'Invited by %s: %s',$msg->nick,$msg->param(1)]; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | sub S_MODE { |
|---|
| 123 | my ($this,$msg,$sender) = @_; |
|---|
| 124 | [$msg->param(0), |
|---|
| 125 | sprintf('Mode by %s: %s %s', |
|---|
| 126 | $msg->nick, |
|---|
| 127 | $msg->param(0), |
|---|
| 128 | join(' ',@{$msg->params}[1 .. ($msg->n_params - 1)]))]; |
|---|
| 129 | } |
|---|
| 130 | |
|---|
| 131 | sub S_NICK { |
|---|
| 132 | my ($this,$msg,$sender) = @_; |
|---|
| 133 | my $network_name = $sender->network_name; |
|---|
| 134 | my $line = do { |
|---|
| 135 | sprintf( |
|---|
| 136 | do { |
|---|
| 137 | if ($msg->param(0) eq $sender->current_nick) { |
|---|
| 138 | 'My nick is changed (%s -> %s)'; |
|---|
| 139 | } |
|---|
| 140 | else { |
|---|
| 141 | '%s -> %s'; |
|---|
| 142 | } |
|---|
| 143 | }, |
|---|
| 144 | $msg->nick, |
|---|
| 145 | $msg->param(0)); |
|---|
| 146 | }; |
|---|
| 147 | my @result; |
|---|
| 148 | if( my $ch_short_list = $msg->remark('affected-channels') ){ |
|---|
| 149 | foreach my $ch_name (@$ch_short_list) { |
|---|
| 150 | push @result,[Multicast::attach($ch_name,$network_name), |
|---|
| 151 | $line]; |
|---|
| 152 | } |
|---|
| 153 | } |
|---|
| 154 | @result; |
|---|
| 155 | } |
|---|
| 156 | |
|---|
| 157 | { |
|---|
| 158 | no warnings 'once'; |
|---|
| 159 | *S_KILL = \&S_QUIT; |
|---|
| 160 | } |
|---|
| 161 | |
|---|
| 162 | sub S_QUIT { |
|---|
| 163 | my ($this,$msg,$sender) = @_; |
|---|
| 164 | my $network_name = $sender->network_name; |
|---|
| 165 | my @result; |
|---|
| 166 | if( my $ch_short_list = $msg->remark('affected-channels') ){ |
|---|
| 167 | foreach my $ch_name (@$ch_short_list) { |
|---|
| 168 | push @result,[Multicast::attach($ch_name,$network_name), |
|---|
| 169 | sprintf '! %s (%s)',$msg->nick,$msg->param(0)]; |
|---|
| 170 | } |
|---|
| 171 | } |
|---|
| 172 | @result; |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | sub S_TOPIC { |
|---|
| 176 | my ($this,$msg,$sender) = @_; |
|---|
| 177 | [$msg->param(0), |
|---|
| 178 | sprintf('Topic of channel %s by %s: %s', |
|---|
| 179 | $msg->param(0), |
|---|
| 180 | $msg->nick, |
|---|
| 181 | $msg->param(1))]; |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | { |
|---|
| 185 | no warnings 'once'; |
|---|
| 186 | *S_PRIVMSG = \&PRIVMSG_or_NOTICE; |
|---|
| 187 | *S_NOTICE = \&PRIVMSG_or_NOTICE; |
|---|
| 188 | *C_PRIVMSG = \&PRIVMSG_or_NOTICE; |
|---|
| 189 | *C_NOTICE = \&PRIVMSG_or_NOTICE; |
|---|
| 190 | } |
|---|
| 191 | |
|---|
| 192 | sub PRIVMSG_or_NOTICE |
|---|
| 193 | { |
|---|
| 194 | my ($this,$msg,$sender) = @_; |
|---|
| 195 | my $line = $this->_build_message($msg, $sender); |
|---|
| 196 | my $channel = $line->{is_priv} ? 'priv' : $line->{ch_long}; |
|---|
| 197 | [$channel, $line->{formatted}]; |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | # ----------------------------------------------------------------------------- |
|---|
| 201 | # $hashref = $obj->_build_message($msg, $sender). |
|---|
| 202 | # Log/Channel から拝借. |
|---|
| 203 | # ただ |
|---|
| 204 | # - distinguish_myself が省かれている. |
|---|
| 205 | # - PRIVでも相手の名前がchannel名として使われる. |
|---|
| 206 | # - 好きにformat出来るように解析した情報をHASHREFで返している. |
|---|
| 207 | # という点で変更されている. |
|---|
| 208 | # |
|---|
| 209 | sub _build_message |
|---|
| 210 | { |
|---|
| 211 | my ($this, $msg, $sender) = @_; |
|---|
| 212 | |
|---|
| 213 | my $raw_target = $msg->param(0); |
|---|
| 214 | my ($target,$netname,$_explicit) = Multicast::detatch( $raw_target ); |
|---|
| 215 | my $is_priv = Multicast::nick_p($target); |
|---|
| 216 | my $cmd = $msg->command; |
|---|
| 217 | |
|---|
| 218 | my $marker_id; |
|---|
| 219 | if( $sender->isa('IrcIO::Client') ) |
|---|
| 220 | { |
|---|
| 221 | $marker_id = 'myself'; |
|---|
| 222 | }elsif( $is_priv ) |
|---|
| 223 | { |
|---|
| 224 | $marker_id = 'priv'; |
|---|
| 225 | }else |
|---|
| 226 | { |
|---|
| 227 | $marker_id = 'channel'; |
|---|
| 228 | } |
|---|
| 229 | my $marker = $MARKER->{$marker_id}{$cmd}; |
|---|
| 230 | $marker or die "no marker for $marker_id/$cmd"; |
|---|
| 231 | |
|---|
| 232 | my ($speaker, $ch_short); |
|---|
| 233 | if( $sender->isa('IrcIO::Client') ) |
|---|
| 234 | { |
|---|
| 235 | # 自分の発言. |
|---|
| 236 | $speaker = RunLoop->shared_loop->network( $netname )->current_nick; |
|---|
| 237 | $ch_short = $target; |
|---|
| 238 | }else |
|---|
| 239 | { |
|---|
| 240 | # 相手の. |
|---|
| 241 | $speaker = $msg->nick || $sender->current_nick; |
|---|
| 242 | $ch_short = $is_priv ? $speaker : $target; |
|---|
| 243 | } |
|---|
| 244 | my $ch_long = Multicast::attach($ch_short, $netname); |
|---|
| 245 | |
|---|
| 246 | my $line = sprintf( |
|---|
| 247 | '%s%s:%s%s %s', |
|---|
| 248 | $marker->[0], |
|---|
| 249 | $ch_long, |
|---|
| 250 | $speaker, |
|---|
| 251 | $marker->[1], |
|---|
| 252 | $msg->param(1), |
|---|
| 253 | ); |
|---|
| 254 | |
|---|
| 255 | +{ |
|---|
| 256 | marker_id => $marker_id, # 'myself' / 'priv' / 'channel' |
|---|
| 257 | is_priv => $is_priv, |
|---|
| 258 | marker => $marker, # ['<', '>'], etc. |
|---|
| 259 | speaker => $speaker, |
|---|
| 260 | ch_long => $ch_long, |
|---|
| 261 | ch_short => $ch_short, |
|---|
| 262 | netname => $netname, |
|---|
| 263 | msg => $msg->param(1), |
|---|
| 264 | formatted => $line, |
|---|
| 265 | }; |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | 1; |
|---|