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