1# -----------------------------------------------------------------------------
2# $Id: Recent.pm 11365 2008-05-10 14:58:28Z topia $
3# -----------------------------------------------------------------------------
4package Log::Recent;
5use strict;
6use warnings;
7use base qw(Module);
8use Module::Use qw(Tools::DateConvert Log::Logger);
9use Tools::DateConvert;
10use Log::Logger;
11use Mask;
12
13sub new {
14    my $class = shift;
15    my $this = $class->SUPER::new(@_);
16    # チャンネル管理の手間を省くため、チャンネルのログはChannelInfoのremarksに保存する。
17    # privのログだけこのクラスで保持。
18    $this->{priv_log} = []; # 中身は単なる文字列
19    $this->{logger} =
20	Log::Logger->new(
21	    sub {
22		$this->log(@_);
23	    },
24	    $this,
25	    'S_PRIVMSG','C_PRIVMSG','S_NOTICE','C_NOTICE');
26    $this->{hook} = IrcIO::Client::Hook->new(
27	sub {
28	    my ($hook, $client, $ch_name, $network, $ch) = @_;
29	    # no-recent-logs オプションが指定されていれば何もしない
30	    return if defined $client->option('no-recent-logs');
31	    # ログはあるか?
32	    my $vec = $ch->remarks('recent-log');
33	    if (defined $vec) {
34		foreach my $elem (@$vec) {
35		    $client->send_message(
36			$this->construct_irc_message(
37			    Prefix => RunLoop->shared_loop->sysmsg_prefix(qw(channel log)),
38			    Command => 'NOTICE',
39			    Params => [$ch_name,$elem->[1]]));
40		}
41	    }
42	})->install('channel-info');
43    $this;
44}
45
46sub destruct {
47    my $this = shift;
48    $this->{hook} and $this->{hook}->uninstall;
49}
50
51sub message_arrived {
52    my ($this,$msg,$sender) = @_;
53    # Log::Recent/commandにマッチするか?
54    if (Mask::match(lc($this->config->command) || '*', lc($msg->command))) {
55	$this->{logger}->log($msg,$sender);
56    }
57    $msg;
58}
59
60sub client_attached {
61    my ($this,$client) = @_;
62    # no-recent-logs オプションが指定されていれば何もしない
63    return if defined $client->option('no-recent-logs');
64    # まずはpriv
65    my $local_nick = RunLoop->shared->current_nick;
66    foreach my $elem (@{$this->{priv_log}}) {
67	$client->send_message(
68	    $this->construct_irc_message(
69		Prefix => RunLoop->shared_loop->sysmsg_prefix(qw(priv log)),
70		Command => 'NOTICE',
71		Params => [$local_nick,$elem->[1]])); # $elem->[0]は常に'priv'
72    }
73
74    # 次に各チャンネル
75#    foreach my $network (values %{RunLoop->shared->networks}) {
76#	foreach my $ch (values %{$network->channels}) {
77#	    # ログはあるか?
78#	    my $vec = $ch->remarks('recent-log');
79#	    if (defined $vec) {
80#		my $ch_name;
81#		foreach my $elem (@$vec) {
82#		    $ch_name =
83#			RunLoop->shared->multi_server_mode_p ?
84#			    $elem->[0] : $ch->name;
85#		    $client->send_message(
86#			$this->construct_irc_message(
87#			    Prefix => RunLoop->shared_loop->sysmsg_prefix(qw(channel log)),
88#			    Command => 'NOTICE',
89#			    Params => [$ch_name,$elem->[1]]));
90#		}
91#	    }
92#	}
93#    }
94}
95
96*S_PRIVMSG = \&PRIVMSG_or_NOTICE;
97*S_NOTICE = \&PRIVMSG_or_NOTICE;
98*C_PRIVMSG = \&PRIVMSG_or_NOTICE;
99*C_NOTICE = \&PRIVMSG_or_NOTICE;
100sub PRIVMSG_or_NOTICE {
101    my ($this,$msg,$sender) = @_;
102    my $target = Multicast::detach($msg->param(0));
103    my $is_priv = Multicast::nick_p($target);
104    my $cmd = $msg->command;
105
106    my $line = do {
107	if ($is_priv) {
108	    if ($sender->isa('IrcIO::Client')) {
109		sprintf(
110		    $cmd eq 'PRIVMSG' ? '>%s< %s' : ')%s( %s',
111		    $msg->param(0),
112		    $msg->param(1));
113	    }
114	    else {
115		sprintf(
116		    $cmd eq 'PRIVMSG' ? '-%s- %s' : '=%s= %s',
117		    $msg->nick || $sender->current_nick,
118		    $msg->param(1));
119	    }
120	}
121	else {
122	    my $format = do {
123		if ($this->config->distinguish_myself && $sender->isa('IrcIO::Client')) {
124		    $cmd eq 'PRIVMSG' ? '>%s< %s' : ')%s( %s';
125		}
126		else {
127		    $cmd eq 'PRIVMSG' ? '<%s> %s' : '(%s) %s';
128		}
129	    };
130	    my $nick = do {
131		if ($sender->isa('IrcIO::Client')) {
132		    RunLoop->shared_loop->network(
133		      (Multicast::detatch($msg->param(0)))[1])
134			->current_nick;
135		}
136		else {
137		    $msg->nick || $sender->current_nick;
138		}
139	    };
140	    sprintf $format,$nick,$msg->param(1);
141	}
142    };
143
144    [$is_priv ? 'priv' : $msg->param(0),$line];
145}
146
147sub log {
148    my ($this,$ch_full,$log_line) = @_;
149    my $vec = do {
150	if ($ch_full eq 'priv') {
151	    # privは自分で保存
152	    $this->{priv_log};
153	}
154	else {
155	    # privでなければChannelInfoに'recent-log'として保存。
156	    my ($ch_short,$network_name) = Multicast::detach($ch_full);
157	    my $network = RunLoop->shared->network($network_name);
158	    if (!defined $network) {
159		RunLoop->shared->notify_warn("errorness network name: $network_name");
160		return;
161	    }
162	    my $ch = $network->channel($ch_short);
163	    if (!defined $ch) {
164		return;
165	    }
166	    my $log_vec = $ch->remarks('recent-log');
167	    if (!defined $log_vec) {
168		$log_vec = [];
169		$ch->remarks('recent-log',$log_vec);
170	    }
171	    $log_vec;
172	}
173    };
174
175    my $header = Tools::DateConvert::replace(
176	$this->config->header || '%H:%M'
177    );
178
179    # ログに追加
180    # 要素は[チャンネル名,ログ行]
181    push @$vec,[$ch_full,"$header $log_line"];
182
183    # 溢れた分を消す
184    if (@$vec > $this->config->line) {
185	splice @$vec,0,(@$vec - $this->config->line);
186    }
187}
188
1891;
190
191=pod
192info: クライアントを接続した時に、保存しておいた最近のメッセージを送る。
193default: off
194section: important
195
196# クライアントオプションの no-recent-logs が指定されていれば送信しません。
197
198# 各行のヘッダのフォーマット。省略されたら'%H:%M'。
199header: %H:%M:%S
200
201# ログをチャンネル毎に何行まで保存するか。省略されたら10。
202line: 15
203
204# PRIVMSGとNOTICEを記録する際に、自分の発言と他人の発言でフォーマットを変えるかどうか。1/0。デフォルトで1。
205distinguish-myself: 1
206
207# どのメッセージを保存するか。省略されたら保存可能な全てのメッセージを保存する。
208command: privmsg,notice,topic,join,part,quit,kill
209=cut
210