File Coverage

blib/lib/POE/Component/IRC/Plugin/Logger.pm
Criterion Covered Total %
statement 349 426 81.9
branch 46 84 54.7
condition 6 9 66.6
subroutine 60 83 72.2
pod 2 26 7.6
total 463 628 73.7


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::Logger;
2             our $AUTHORITY = 'cpan:HINRIK';
3             $POE::Component::IRC::Plugin::Logger::VERSION = '6.92';
4 6     6   5758 use strict;
  6         16  
  6         264  
5 6     6   37 use warnings FATAL => 'all';
  6         16  
  6         398  
6 6     6   40 use Carp;
  6         15  
  6         548  
7 6     6   81 use Encode::Guess;
  6         14  
  6         178  
8 6     6   980 use Fcntl qw(O_WRONLY O_APPEND O_CREAT);
  6         13  
  6         730  
9 6     6   52 use File::Glob ':glob';
  6         28  
  6         2439  
10 6     6   51 use File::Spec::Functions qw(catdir catfile rel2abs);
  6         15  
  6         682  
11 6     6   141 use IO::Handle;
  6         13  
  6         344  
12 6     6   56 use IRC::Utils qw(lc_irc parse_user strip_color strip_formatting decode_irc);
  6         13  
  6         675  
13 6     6   46 use POE::Component::IRC::Plugin qw( :ALL );
  6         14  
  6         745  
14 6     6   3361 use POE::Component::IRC::Plugin::BotTraffic;
  6         16  
  6         287  
15 6     6   48 use POSIX qw(strftime);
  6         19  
  6         150  
16              
17             sub new {
18 5     5 1 9442 my ($package) = shift;
19 5 50       38 croak "$package requires an even number of arguments" if @_ & 1;
20 5         25 my %self = @_;
21              
22 5 50 66     42 if (!defined $self{Path} && ref $self{Log_sub} ne 'CODE') {
23 0         0 die "$package requires a Path";
24             }
25 5         55 return bless \%self, $package;
26             }
27              
28             sub PCI_register {
29 5     5 0 1416 my ($self, $irc) = @_;
30              
31 5 50       61 if (!$irc->isa('POE::Component::IRC::State')) {
32 0         0 die __PACKAGE__ . ' requires PoCo::IRC::State or a subclass thereof';
33             }
34              
35 5 50       13 if ( !grep { $_->isa('POE::Component::IRC::Plugin::BotTraffic') } values %{ $irc->plugin_list() } ) {
  15         391  
  5         53  
36 5         52 $irc->plugin_add('BotTraffic', POE::Component::IRC::Plugin::BotTraffic->new());
37             }
38              
39 5 50       820 if ($self->{Restricted}) {
40 0         0 $self->{dir_perm} = oct 700;
41 0         0 $self->{file_perm} = oct 600;
42             }
43             else {
44 5         19 $self->{dir_perm} = oct 755;
45 5         25 $self->{file_perm} = oct 644;
46              
47             }
48              
49 5 100       184 $self->{Path} = bsd_glob($self->{Path}) if ref $self->{Log_sub} ne 'CODE';
50 5 50 66     149 if (defined $self->{Path} && ! -d $self->{Path}) {
51             mkdir $self->{Path}, $self->{dir_perm}
52 0 0       0 or die 'Cannot create directory ' . $self->{Path} . ": $!; aborted";
53 0         0 $self->{Path} = rel2abs($self->{Path});
54             }
55              
56 5         18 $self->{irc} = $irc;
57 5         16 $self->{logging} = { };
58 5 50       46 $self->{Private} = 1 if !defined $self->{Private};
59 5 50       40 $self->{Public} = 1 if !defined $self->{Public};
60 5 50       26 $self->{DCC} = 1 if !defined $self->{DCC};
61 5 50       87 $self->{Format} = $self->default_format() if !defined $self->{Format};
62              
63 5         74 $irc->plugin_register($self, 'SERVER', qw(001 332 333 chan_mode
64             ctcp_action bot_action bot_msg bot_public bot_notice join kick msg
65             nick part public notice quit topic dcc_start dcc_chat dcc_done));
66 5         662 $irc->plugin_register($self, 'USER', 'dcc_chat');
67 5         168 return 1;
68             }
69              
70             sub PCI_unregister {
71 5     5 0 1443 return 1;
72             }
73              
74             sub S_001 {
75 4     4 0 563 my ($self, $irc) = splice @_, 0, 2;
76 4         18 $self->{logging} = { };
77 4         14 return PCI_EAT_NONE;
78             }
79              
80             sub S_332 {
81 0     0 0 0 my ($self, $irc) = splice @_, 0, 2;
82 0         0 my $chan = decode_irc(${ $_[2] }->[0]);
  0         0  
83 0         0 my $topic = $self->_normalize(${ $_[2] }->[1]);
  0         0  
84              
85             # only log this if we were just joining the channel
86 0 0       0 $self->_log_entry($chan, topic_is => $chan, $topic) if !$irc->channel_list($chan);
87 0         0 return PCI_EAT_NONE;
88             }
89              
90             sub S_333 {
91 0     0 0 0 my ($self, $irc) = splice @_, 0, 2;
92 0         0 my ($chan, $user, $time) = @{ ${ $_[2] } };
  0         0  
  0         0  
93 0         0 $chan = decode_irc($chan);
94              
95             # only log this if we were just joining the channel
96 0 0       0 $self->_log_entry($chan, topic_set_by => $chan, $user, $time) if !$irc->channel_list($chan);
97 0         0 return PCI_EAT_NONE;
98             }
99              
100             sub S_chan_mode {
101 12     12 0 622 my ($self, $irc) = splice @_, 0, 2;
102 12         26 pop @_;
103 12         25 my $nick = parse_user(${ $_[0] });
  12         56  
104 12         170 my $chan = decode_irc(${ $_[1] });
  12         48  
105 12         1910 my $mode = ${ $_[2] };
  12         28  
106 12 100       43 my $arg = defined $_[3] ? ${ $_[3] } : '';
  6         12  
107              
108 12         54 $self->_log_entry($chan, $mode => $nick, $arg);
109 12         81 return PCI_EAT_NONE;
110             }
111              
112             sub S_ctcp_action {
113 1     1 0 52 my ($self, $irc) = splice @_, 0, 2;
114 1         2 my $sender = parse_user(${ $_[0] });
  1         5  
115 1         14 my $recipients = ${ $_[1] };
  1         3  
116 1         3 my $msg = $self->_normalize(${ $_[2] });
  1         4  
117              
118 1         3 for my $recipient (@{ $recipients }) {
  1         3  
119 1 50       16 if ($recipient eq $irc->nick_name()) {
120 1         5 $self->_log_entry($sender, action => $sender, $msg);
121             }
122             else {
123 0         0 $recipient = decode_irc($recipient);
124 0         0 $self->_log_entry($recipient, action => $sender, $msg);
125             }
126             }
127 1         6 return PCI_EAT_NONE;
128             }
129              
130             sub S_notice {
131 2     2 0 101 my ($self, $irc) = splice @_, 0, 2;
132 2         4 my $sender = parse_user(${ $_[0] });
  2         11  
133 2         28 my $targets = ${ $_[1] };
  2         7  
134 2         4 my $msg = $self->_normalize(${ $_[2] });
  2         20  
135              
136 2         7 for my $target (@{ $targets }) {
  2         7  
137 2 100       23 if ($target eq $irc->nick_name()) {
138 1         4 $self->_log_entry($sender, notice => $sender, $msg);
139             }
140             else {
141 1         4 $target = decode_irc($target);
142 1         130 $self->_log_entry($target, notice => $sender, $msg);
143             }
144             }
145 2         11 return PCI_EAT_NONE;
146             }
147              
148              
149             sub S_bot_action {
150 1     1 0 50 my ($self, $irc) = splice @_, 0, 2;
151 1         2 my $recipients = ${ $_[0] };
  1         3  
152 1         2 my $msg = $self->_normalize(${ $_[1] });
  1         4  
153              
154 1         3 for my $recipient (@{ $recipients }) {
  1         4  
155 1         3 $recipient = decode_irc($recipient);
156 1         113 $self->_log_entry($recipient, action => $irc->nick_name(), $msg);
157             }
158 1         6 return PCI_EAT_NONE;
159             }
160              
161             sub S_bot_msg {
162 1     1 0 56 my ($self, $irc) = splice @_, 0, 2;
163 1         2 my $recipients = ${ $_[0] };
  1         3  
164 1         2 my $msg = $self->_normalize(${ $_[1] });
  1         4  
165              
166 1         2 for my $recipient (@{ $recipients }) {
  1         4  
167 1         4 $self->_log_entry($recipient, privmsg => $irc->nick_name(), $msg);
168             }
169 1         7 return PCI_EAT_NONE;
170             }
171              
172             sub S_bot_public {
173 0     0 0 0 my ($self, $irc) = splice @_, 0, 2;
174 0         0 my $channels = ${ $_[0] };
  0         0  
175 0         0 my $msg = $self->_normalize(${ $_[1] });
  0         0  
176              
177 0         0 for my $chan (@{ $channels }) {
  0         0  
178 0         0 $chan = decode_irc($chan);
179 0         0 $self->_log_entry($chan, privmsg => $irc->nick_name(), $msg);
180             }
181 0         0 return PCI_EAT_NONE;
182             }
183              
184             sub S_bot_notice {
185 1     1 0 50 my ($self, $irc) = splice @_, 0, 2;
186 1         2 my $targets = ${ $_[0] };
  1         3  
187 1         3 my $msg = $self->_normalize(${ $_[1] });
  1         4  
188              
189 1         3 for my $target (@{ $targets }) {
  1         3  
190 1         3 $target = decode_irc($target);
191 1         133 $self->_log_entry($target, notice => $irc->nick_name(), $msg);
192             }
193 1         6 return PCI_EAT_NONE;
194             }
195              
196             sub S_join {
197 4     4 0 238 my ($self, $irc) = splice @_, 0, 2;
198 4         10 my ($joiner, $user, $host) = parse_user(${ $_[0] });
  4         28  
199 4         82 my $chan = decode_irc(${ $_[1] });
  4         23  
200              
201 4         957 $self->_log_entry($chan, join => $joiner, "$user\@$host", $chan);
202 4         23 return PCI_EAT_NONE;
203             }
204              
205             sub S_kick {
206 1     1 0 64 my ($self, $irc) = splice @_, 0, 2;
207 1         3 my $kicker = parse_user(${ $_[0] });
  1         8  
208 1         18 my $chan = decode_irc(${ $_[1] });
  1         6  
209 1         209 my $victim = ${ $_[2] };
  1         3  
210 1         3 my $msg = $self->_normalize(${ $_[3] });
  1         7  
211              
212 1         7 $self->_log_entry($chan, kick => $kicker, $victim, $chan, $msg);
213 1         7 return PCI_EAT_NONE;
214             }
215              
216             sub S_msg {
217 1     1 0 88 my ($self, $irc) = splice @_, 0, 2;
218 1         4 my $sender = parse_user(${ $_[0] });
  1         9  
219 1         27 my $msg = $self->_normalize(${ $_[2] });
  1         8  
220              
221 1         9 $self->_log_entry($sender, privmsg => $sender, $msg);
222 1         9 return PCI_EAT_NONE;
223             }
224              
225             sub S_nick {
226 1     1 0 50 my ($self, $irc) = splice @_, 0, 2;
227 1         4 my $old_nick = parse_user(${ $_[0] });
  1         6  
228 1         13 my $new_nick = ${ $_[1] };
  1         3  
229 1         2 my $channels = ${ $_[2] };
  1         3  
230              
231 1         2 for my $chan (@{ $channels }) {
  1         3  
232 1         4 $chan = decode_irc($chan);
233 1         130 $self->_log_entry($chan, nick_change => $old_nick, $new_nick);
234             }
235 1         6 return PCI_EAT_NONE;
236             }
237              
238             sub S_part {
239 1     1 0 51 my ($self, $irc) = splice @_, 0, 2;
240 1         3 my ($parter, $user, $host) = parse_user(${ $_[0] });
  1         5  
241 1         15 my $chan = decode_irc(${ $_[1] });
  1         4  
242 1 50       150 my $msg = ref $_[2] eq 'SCALAR' ? ${ $_[2] } : '';
  1         4  
243 1         5 $msg = $self->_normalize($msg);
244              
245 1         8 $self->_log_entry($chan, part => $parter, "$user\@$host", $chan, $msg);
246 1         6 return PCI_EAT_NONE;
247             }
248              
249             sub S_public {
250 1     1 0 55 my ($self, $irc) = splice @_, 0, 2;
251 1         2 my $sender = parse_user(${ $_[0] });
  1         8  
252 1         16 my $channels = ${ $_[1] };
  1         2  
253 1         3 my $msg = $self->_normalize(${ $_[2] });
  1         6  
254              
255 1         3 for my $chan (@{ $channels }) {
  1         3  
256 1         3 $chan = decode_irc($chan);
257 1         115 $self->_log_entry($chan, privmsg => $sender, $msg);
258             }
259 1         6 return PCI_EAT_NONE;
260             }
261              
262             sub S_quit {
263 1     1 0 65 my ($self, $irc) = splice @_, 0, 2;
264 1         2 my ($quitter, $user, $host) = parse_user(${ $_[0] });
  1         7  
265 1         20 my $msg = $self->_normalize(${ $_[1] });
  1         6  
266 1         4 my $channels = ${ $_[2] };
  1         4  
267              
268 1         2 for my $chan (@{ $channels }) {
  1         3  
269 1         4 $chan = decode_irc($chan);
270 1         120 $self->_log_entry($chan, quit => $quitter, "$user\@$host", $msg);
271             }
272 1         7 return PCI_EAT_NONE;
273             }
274              
275             sub S_topic {
276 1     1 0 49 my ($self, $irc) = splice @_, 0, 2;
277 1         2 my $changer = parse_user(${ $_[0] });
  1         6  
278 1         13 my $chan = decode_irc(${ $_[1] });
  1         5  
279 1         126 my $new_topic = $self->_normalize(${ $_[2] });
  1         7  
280              
281 1         5 $self->_log_entry($chan, topic_change => $changer, $new_topic);
282 1         5 return PCI_EAT_NONE;
283             }
284              
285             sub S_dcc_start {
286 1     1 0 63 my ($self, $irc) = splice @_, 0, 2;
287 1         2 my $nick = ${ $_[1] };
  1         4  
288 1         3 my $type = ${ $_[2] };
  1         2  
289 1         3 my $port = ${ $_[3] };
  1         2  
290 1         2 my $addr = ${ $_[6] };
  1         3  
291              
292 1 50       5 return PCI_EAT_NONE if $type ne 'CHAT';
293 1         9 $self->_log_entry("=$nick", dcc_start => $nick, "$addr:$port");
294 1         6 return PCI_EAT_NONE;
295             }
296              
297             sub S_dcc_chat {
298 2     2 0 98 my ($self, $irc) = splice @_, 0, 2;
299 2         4 my $nick = ${ $_[1] };
  2         5  
300 2         5 my $msg = $self->_normalize(${ $_[3] });
  2         10  
301              
302 2 100       19 if (my ($action) = $msg =~ /\001ACTION (.*?)\001/) {
303 1         5 $self->_log_entry("=$nick", action => $nick, $action);
304             }
305             else {
306 1         7 $self->_log_entry("=$nick", privmsg => $nick, $msg);
307             }
308 2         12 return PCI_EAT_NONE;
309             }
310              
311             sub U_dcc_chat {
312 2     2 0 98 my ($self, $irc) = splice @_, 0, 2;
313 2         4 pop @_;
314 2         5 my ($id, @lines) = @_;
315 2         8 $_ = $$_ for @lines;
316 2         9 my $me = $irc->nick_name();
317              
318 2         4 my ($dcc) = grep { $_->isa('POE::Component::IRC::Plugin::DCC') } values %{ $irc->plugin_list() };
  10         114  
  2         9  
319 2         11 my $info = $dcc->dcc_info($$id);
320 2         5 my $nick = $info->{nick};
321              
322 2         4 for my $msg (@lines) {
323 2         112 $msg = $self->_normalize($msg);
324 2 100       19 if (my ($action) = $msg =~ /\001ACTION (.*?)\001/) {
325 1         5 $self->_log_entry("=$nick", action => $me, $action);
326             }
327             else {
328 1         5 $self->_log_entry("=$nick", privmsg => $me, $msg);
329             }
330             }
331 2         13 return PCI_EAT_NONE;
332             }
333              
334             sub S_dcc_done {
335 1     1 0 50 my ($self, $irc) = splice @_, 0, 2;
336 1         2 my $nick = ${ $_[1] };
  1         4  
337 1         3 my $type = ${ $_[2] };
  1         2  
338 1         3 my $port = ${ $_[3] };
  1         2  
339 1         2 my $addr = ${ $_[7] };
  1         3  
340              
341 1 50       4 return PCI_EAT_NONE if $type ne 'CHAT';
342 1         6 $self->_log_entry("=$nick", dcc_done => $nick, "$addr:$port");
343 1         6 return PCI_EAT_NONE;
344             }
345              
346             sub _log_entry {
347 35     35   177 my ($self, $context, $type, @args) = @_;
348 35         2577 my ($date, $time) = split / /, (strftime '%Y-%m-%d %H:%M:%S ', localtime);
349 35         344 $context = lc_irc $context, $self->{irc}->isupport('CASEMAPPING');
350 35 50       632 my $chantypes = join('', @{ $self->{irc}->isupport('CHANTYPES') || ['#', '&']});
  35         109  
351              
352 35 100       341 if ($context =~ /^[$chantypes]/) {
    100          
353 23 50       87 return if !$self->{Public};
354             }
355             elsif ($context =~ /^=/) {
356 6 50       21 return if !$self->{DCC};
357             }
358             else {
359 6 50       21 return if !$self->{Private};
360             }
361              
362 35 50 66     146 return if $type eq 'notice' && !$self->{Notices};
363              
364 35 100       112 if (ref $self->{Log_sub} eq 'CODE') {
365 3         19 $self->{Log_sub}->($context, $type, @args);
366 3         6257 return;
367             }
368              
369 32 50       112 return if !defined $self->{Format}->{$type};
370              
371             # slash is problematic in a filename, replace it with underscore
372 32         104 $context =~ s!/!_!g;
373              
374 32         56 my $log_file;
375 32 50       85 if ($self->{Sort_by_date}) {
376 0         0 my $log_dir = catdir($self->{Path}, $context);
377 0 0       0 if (! -d $log_dir) {
378             mkdir $log_dir, $self->{dir_perm}
379 0 0       0 or die "Couldn't create directory $log_dir: $!; aborted";
380             }
381 0         0 $log_file = catfile($self->{Path}, $context, "$date.log");
382             }
383             else {
384 32         267 $log_file = catfile($self->{Path}, "$context.log");
385             }
386              
387 32         151 $log_file = $self->_open_log($log_file);
388              
389 32 100       141 if (!$self->{logging}->{$context}) {
390 3         226 print $log_file "***\n*** LOGGING BEGINS\n***\n";
391 3         26 $self->{logging}->{$context} = 1;
392             }
393 32         154 my $line = "$time " . $self->{Format}->{$type}->(@args);
394 32 50       140 $line = "$date $line" if !$self->{Sort_by_date};
395 32         1386 print $log_file $line, "\n";
396 32         693 return;
397             }
398              
399             sub _open_log {
400 32     32   77 my ($self, $file_name) = @_;
401             sysopen(my $log, $file_name, O_WRONLY|O_APPEND|O_CREAT, $self->{file_perm})
402 32 50       2173 or die "Couldn't open or create file '$file_name': $!; aborted";
403 32     3   746 binmode($log, ':encoding(utf8)');
  3         42  
  3         9  
  3         30  
404 32         6160 $log->autoflush(1);
405 32         1496 return $log;
406             }
407              
408             sub _normalize {
409 16     16   47 my ($self, $line) = @_;
410 16         57 $line = decode_irc($line);
411 16 50       2570 $line = strip_color($line) if $self->{Strip_color};
412 16 50       78 $line = strip_formatting($line) if $self->{Strip_formatting};
413 16         44 return $line;
414             }
415              
416             sub default_format {
417             return {
418 1     1   6 '+b' => sub { my ($nick, $mask) = @_; "--- $nick sets ban on $mask" },
  1         6  
419 1     1   5 '-b' => sub { my ($nick, $mask) = @_; "--- $nick removes ban on $mask" },
  1         7  
420 0     0   0 '+e' => sub { my ($nick, $mask) = @_; "--- $nick sets exempt on $mask" },
  0         0  
421 0     0   0 '-e' => sub { my ($nick, $mask) = @_; "--- $nick removes exempt on $mask" },
  0         0  
422 0     0   0 '+I' => sub { my ($nick, $mask) = @_; "--- $nick sets invite on $mask" },
  0         0  
423 0     0   0 '-I' => sub { my ($nick, $mask) = @_; "--- $nick removes invite on $mask" },
  0         0  
424 0     0   0 '+h' => sub { my ($nick, $subject) = @_; "--- $nick gives channel half-operator status to $subject" },
  0         0  
425 0     0   0 '-h' => sub { my ($nick, $subject) = @_; "--- $nick removes channel half-operator status from $subject" },
  0         0  
426 1     1   4 '+o' => sub { my ($nick, $subject) = @_; "--- $nick gives channel operator status to $subject" },
  1         7  
427 0     0   0 '-o' => sub { my ($nick, $subject) = @_; "--- $nick removes channel operator status from $subject" },
  0         0  
428 0     0   0 '+v' => sub { my ($nick, $subject) = @_; "--- $nick gives voice to $subject" },
  0         0  
429 0     0   0 '-v' => sub { my ($nick, $subject) = @_; "--- $nick removes voice from $subject" },
  0         0  
430 1     1   5 '+k' => sub { my ($nick, $key) = @_; "--- $nick sets channel keyword to $key" },
  1         6  
431 1     1   4 '-k' => sub { my ($nick) = @_; "--- $nick removes channel keyword" },
  1         4  
432 1     1   6 '+l' => sub { my ($nick, $limit) = @_; "--- $nick sets channel user limit to $limit" },
  1         7  
433 1     1   5 '-l' => sub { my ($nick) = @_; "--- $nick removes channel user limit" },
  1         5  
434 0     0   0 '+i' => sub { my ($nick) = @_; "--- $nick enables invite-only channel status" },
  0         0  
435 0     0   0 '-i' => sub { my ($nick) = @_; "--- $nick disables invite-only channel status" },
  0         0  
436 1     1   3 '+m' => sub { my ($nick) = @_; "--- $nick enables channel moderation" },
  1         6  
437 0     0   0 '-m' => sub { my ($nick) = @_; "--- $nick disables channel moderation" },
  0         0  
438 0     0   0 '+n' => sub { my ($nick) = @_; "--- $nick disables external messages" },
  0         0  
439 0     0   0 '-n' => sub { my ($nick) = @_; "--- $nick enables external messages" },
  0         0  
440 0     0   0 '+p' => sub { my ($nick) = @_; "--- $nick enables private channel status" },
  0         0  
441 0     0   0 '-p' => sub { my ($nick) = @_; "--- $nick disables private channel status" },
  0         0  
442 1     1   4 '+s' => sub { my ($nick) = @_; "--- $nick enables secret channel status" },
  1         6  
443 0     0   0 '-s' => sub { my ($nick) = @_; "--- $nick disables secret channel status" },
  0         0  
444 0     0   0 '+t' => sub { my ($nick) = @_; "--- $nick enables topic protection" },
  0         0  
445 1     1   4 '-t' => sub { my ($nick) = @_; "--- $nick disables topic protection" },
  1         6  
446 1     1   5 nick_change => sub { my ($old_nick, $new_nick) = @_; "--- $old_nick is now known as $new_nick" },
  1         5  
447 0     0   0 topic_is => sub { my ($chan, $topic) = @_; "--- Topic for $chan is: $topic" },
  0         0  
448 1     1   5 topic_change => sub { my ($nick, $topic) = @_; "--- $nick changes the topic to: $topic" },
  1         7  
449 5     5   20 privmsg => sub { my ($nick, $msg) = @_; "<$nick> $msg" },
  5         27  
450 3     3   9 notice => sub { my ($nick, $msg) = @_; ">$nick< $msg" },
  3         14  
451 4     4   13 action => sub { my ($nick, $action) = @_; "* $nick $action" },
  4         20  
452 1     1   4 dcc_start => sub { my ($nick, $address) = @_; "--> Opened DCC chat connection with $nick ($address)" },
  1         7  
453 1     1   4 dcc_done => sub { my ($nick, $address) = @_; "<-- Closed DCC chat connection with $nick ($address)" },
  1         6  
454 3     3   10 join => sub { my ($nick, $userhost, $chan) = @_; "--> $nick ($userhost) joins $chan" },
  3         18  
455             part => sub {
456 1     1   4 my ($nick, $userhost, $chan, $msg) = @_;
457 1         7 my $line = "<-- $nick ($userhost) leaves $chan";
458 1 50       7 $line .= " ($msg)" if $msg ne '';
459 1         16 return $line;
460             },
461             quit => sub {
462 1     1   4 my ($nick, $userhost, $msg) = @_;
463 1         7 my $line = "<-- $nick ($userhost) quits";
464 1 50       7 $line .= " ($msg)" if $msg ne '';
465 1         5 return $line;
466             },
467             kick => sub {
468 1     1   5 my ($kicker, $victim, $chan, $msg) = @_;
469 1         7 my $line = "<-- $kicker kicks $victim from $chan";
470 1 50       8 $line .= " ($msg)" if $msg ne '';
471 1         4 return $line;
472             },
473             topic_set_by => sub {
474 0     0   0 my ($chan, $user, $time) = @_;
475 0         0 my $date = localtime $time;
476 0         0 return "--- Topic for $chan was set by $user at $date";
477             },
478             }
479 5     5 1 834 }
480              
481             1;
482              
483             =encoding utf8
484              
485             =head1 NAME
486              
487             POE::Component::IRC::Plugin::Logger - A PoCo-IRC plugin which
488             logs public, private, and DCC chat messages to disk
489              
490             =head1 SYNOPSIS
491              
492             use POE::Component::IRC::Plugin::Logger;
493              
494             $irc->plugin_add('Logger', POE::Component::IRC::Plugin::Logger->new(
495             Path => '/home/me/irclogs',
496             DCC => 0,
497             Private => 0,
498             Public => 1,
499             ));
500              
501             =head1 DESCRIPTION
502              
503             POE::Component::IRC::Plugin::Logger is a L
504             plugin. It logs messages and CTCP ACTIONs to either F<#some_channel.log> or
505             F in the supplied path. In the case of DCC chats, a '=' is
506             prepended to the nickname (like in irssi).
507              
508             The plugin tries to detect UTF-8 encoding of every message or else falls back
509             to CP1252, like irssi (and, supposedly, mIRC) does by default. Resulting log
510             files will be UTF-8 encoded. The default log format is similar to xchat's,
511             except that it's sane and parsable.
512              
513             This plugin requires the IRC component to be L
514             or a subclass thereof. It also requires a L
515             to be in the plugin pipeline. It will be added automatically if it is not
516             present.
517              
518             =head1 METHODS
519              
520             =head2 C
521              
522             Arguments:
523              
524             B<'Path'>, the place where you want the logs saved.
525              
526             B<'Private'>, whether or not to log private messages. Defaults to 1.
527              
528             B<'Public'>, whether or not to log public messages. Defaults to 1.
529              
530             B<'DCC'>, whether or not to log DCC chats. Defaults to 1.
531              
532             B<'Notices'>, whether or not to log NOTICEs. Defaults to 0.
533              
534             B<'Sort_by_date'>, whether or not to split log files by date, i.e.
535             F<#channel/YYYY-MM-DD.log> instead of F<#channel.log>. If enabled, the date
536             will be omitted from the timestamp. Defaults to 0.
537              
538             B<'Strip_color'>, whether or not to strip all color codes from messages. Defaults
539             to 0.
540              
541             B<'Strip_formatting'>, whether or not to strip all formatting codes from messages.
542             Defaults to 0.
543              
544             B<'Restricted'>, set this to 1 if you want all directories/files to be created
545             without read permissions for other users (i.e. 700 for dirs and 600 for files).
546             Defaults to 1.
547              
548             B<'Format'>, a hash reference representing the log format, if you want to define
549             your own. See the source for details.
550              
551             B<'Log_sub'>, a subroutine reference which can be used to override the file
552             logging. Use this if you want to store logs in a database instead, for
553             example. It will be called with 3 arguments: the context (a channel name or
554             nickname), a type (e.g. 'privmsg' or '+b', and any arguments to that type.
555             You can make use L to create logs that match the default
556             log format. B You must take care of handling date/time and stripping
557             colors/formatting codes yourself.
558              
559             Returns a plugin object suitable for feeding to
560             L's C method.
561              
562             =head2 C
563              
564             Returns a hash reference of type/subroutine pairs, for formatting logs
565             according to the default log format.
566              
567             =head1 AUTHOR
568              
569             Hinrik Ern SigurEsson, hinrik.sig@gmail.com
570              
571             =cut