1 package Onis::Data::Core;
5 Onis::Data::Core - User management
9 Store data to the internal structure, care about users, nicks and idents and
10 dispatch to plugins. The core of the data even..
18 use Onis::Config qw#get_config#;
19 use Onis::Users qw#host_to_username nick_to_username#;
20 use Onis::Data::Persistent qw#init#;
22 @Onis::Data::Core::EXPORT_OK = qw#all_nicks get_channel
24 ident_to_nick ident_to_name
26 get_total_lines nick_rename print_output
27 register_plugin store get_print_name#;
28 @Onis::Data::Core::ISA = ('Exporter');
30 our $DATA = init ('$DATA', 'hash');
39 our $LASTRUN_DAYS = 0;
41 our $UNSHARP = 'MEDIUM';
42 if (get_config ('unsharp'))
44 my $tmp = get_config ('unsharp');
48 if ($tmp eq 'NONE' or $tmp eq 'LIGHT'
56 print STDERR $/, __FILE__, ": ``$tmp'' is not a valid value for config option ``unsharp''.",
57 $/, __FILE__, ": Using standard value ``MEDIUM''.";
63 $DATA->{'idents_of_nick'} = {};
64 $DATA->{'channel'} = {};
65 $DATA->{'total_lines'} = 0;
68 if (defined ($DATA->{'lastrun'}))
70 my $last = $DATA->{'lastrun'};
73 my $diff = ($now - $last) % 86400;
77 $DATA->{'lastrun'} = $now;
78 $LASTRUN_DAYS = $diff;
83 $DATA->{'lastrun'} = time;
86 my $VERSION = '$Id: Core.pm,v 1.14 2004/10/31 15:00:32 octo Exp $';
87 print STDERR $/, __FILE__, ": $VERSION" if ($::DEBUG);
91 =head1 EXPORTED FUNCTIONS
95 =item I<@nicks> = B<all_nicks> ()
97 Returns an array of all seen nicks.
108 my @temp = keys (%{$DATA->{'idents_of_nick'}});
109 my $nicks_of_ident = {};
111 print STDERR $/, __FILE__, ': Looking at ', scalar (@temp), ' nicks.' if ($::DEBUG & 0x100);
116 my $this_ident = 'unidentified';
119 my $this_ident_is_user = 0;
121 my @idents = keys (%{$DATA->{'idents_of_nick'}{$this_nick}});
126 my $num = $DATA->{'idents_of_nick'}{$this_nick}{$ident};
128 my $ident_is_user = 1;
130 if ($ident =~ m/^[^@]+@.+$/)
137 $newnum = int ($num * (0.9**$LASTRUN_DAYS));
140 print STDERR $/, __FILE__, ": Deleting ident ``$ident'' because it's too old." if ($::DEBUG);
141 delete ($DATA->{'idents_of_nick'}{$this_nick}{$ident});
142 if (!keys %{$DATA->{'idents_of_nick'}{$this_nick}})
144 print STDERR $/, __FILE__, ": Deleting nick ``$this_nick'' because it's too old." if ($::DEBUG);
145 delete ($DATA->{'idents_of_nick'}{$this_nick});
148 elsif ($ident_is_user)
150 if (($num >= $this_max) or !$this_ident_is_user)
153 $this_ident = $ident;
154 $this_ident_is_user = 1;
157 elsif ($ident !~ m/\@unidentified$/)
159 if (($num >= $this_max) and !$this_ident_is_user)
162 $this_ident = $ident;
167 print $/, __FILE__, ": max_ident ($this_nick) = $this_ident" if ($::DEBUG & 0x100);
169 if ($this_ident ne 'unidentified')
171 if (!$this_ident_is_user and nick_to_username ($this_nick))
173 print STDERR $/, __FILE__, ": $this_nick!$this_ident -> " if ($::DEBUG & 0x100);
175 $this_ident = nick_to_username ($this_nick);
176 $this_ident_is_user = 1;
178 print STDERR $this_ident if ($::DEBUG & 0x100);
180 $nicks_of_ident->{$this_ident}{$this_nick} = $this_total;
182 elsif ($::DEBUG & 0x100)
184 print STDERR $/, __FILE__, ": Ignoring unidentified nick ``$this_nick''";
188 @temp = keys (%$nicks_of_ident);
190 print STDERR $/, __FILE__, ': Looking at ', scalar (@temp), ' idents.' if ($::DEBUG & 0x100);
197 my @other_nicks = ();
199 my @nicks = keys (%{$nicks_of_ident->{$this_ident}});
204 my $num = $nicks_of_ident->{$this_ident}{$nick};
206 if ($num > $this_max)
208 if ($this_nick) { push (@other_nicks, $this_nick); }
214 push (@other_nicks, $nick);
218 print STDERR $/, __FILE__, ": max_nick ($this_ident) = $this_nick" if ($::DEBUG & 0x100);
220 for (@other_nicks, $this_nick)
222 push (@ALLNICKS, $_);
223 $NICK_MAP{$_} = $this_nick;
224 $NICK2IDENT{$_} = $this_ident;
227 $IDENT2NICK{$this_ident} = $this_nick;
231 =item I<$channel> = B<get_channel> ()
233 Returns the name of the channel we're generating stats for.
240 if (get_config ('channel'))
242 $chan = get_config ('channel');
244 elsif (keys (%{$DATA->{'channel'}}))
248 $DATA->{'channel'}{$b} <=> $DATA->{'channel'}{$a}
249 } (keys (%{$DATA->{'channel'}}));
256 # Fix network-safe channel named (RFC 2811)
257 if ($chan =~ m/^![A-Z0-9]{5}.+/)
259 $chan =~ s/[A-Z0-9]{5}//;
265 =item I<$main> = B<get_main_nick> (I<$nick>)
267 Returns the main nick for I<$nick> or an empty string if the nick is unknown..
274 if (defined ($NICK_MAP{$nick}))
276 return ($NICK_MAP{$nick});
284 =item I<$ident> = B<nick_to_ident> (I<$nick>)
286 Returns the ident for this nick or an empty string if unknown.
293 if (defined ($NICK2IDENT{$nick}))
295 return ($NICK2IDENT{$nick});
303 =item I<$nick> = B<ident_to_nick> (I<$ident>)
305 Returns the nick for the given ident or an empty string if unknown.
313 if (!defined ($ident)
314 or (lc ($ident) eq 'ignore')
315 or (lc ($ident) eq 'unidentified'))
319 elsif (defined ($IDENT2NICK{$ident}))
321 return ($IDENT2NICK{$ident});
329 =item I<$name> = B<ident_to_name> (I<$ident>)
331 Returns the printable version of the name for the chatter identified by
332 I<$ident>. Returns an empty string if the ident is not known.
339 my $nick = ident_to_nick ($ident);
347 $name = get_print_name ($nick);
352 =item I<$name> = B<get_print_name> (I<$nick>)
354 Returns the printable version of the name for the nick I<$nick> or I<$nick> if
365 if (defined ($NICK2IDENT{$nick}))
367 $ident = $NICK2IDENT{$nick};
370 if (($ident !~ m/^[^@]+@.+$/) and $ident)
378 =item I<$lines> = B<get_total_lines> ()
380 Returns the total number of lines parsed so far.
386 return ($DATA->{'total_lines'});
389 =item B<nick_rename> (I<$old_nick>, I<$new_nick>)
391 Keeps track of a nick's hostname if the nick changes.
397 my $old_nick = shift;
398 my $new_nick = shift;
400 if (defined ($DATA->{'host_cache'}{$old_nick}))
402 my $host = $DATA->{'host_cache'}{$old_nick};
403 $DATA->{'host_cache'}{$new_nick} = $host;
405 if (!defined ($DATA->{'hosts_of_nick'}{$new_nick}{$host}))
407 $DATA->{'hosts_of_nick'}{$new_nick}{$host} = 1;
411 if (defined ($DATA->{'byident'}{"$old_nick\@unidentified"}))
413 # Other data may be overwritten, but I don't care here..
414 # This should be a extremely rare case..
415 $DATA->{'byident'}{"$new_nick\@unidentified"} = $DATA->{'byident'}{"$old_nick\@unidentified"};
416 delete ($DATA->{'byident'}{"$old_nick\@unidentified"});
420 =item B<print_output> ()
422 Print the output. Should be called only once..
428 if (!$DATA->{'total_lines'})
430 print STDERR <<'MESSAGE';
434 The most common reasons for this are:
435 - The logfile used was empty.
436 - The ``logtype'' setting did not match the logfile.
437 - The logfile did not include a date.
451 delete ($DATA->{'byname'});
454 =item I<$data> = B<register_plugin> (I<$type>, I<$sub_ref>)
456 Register a subroutine for the given type. Returns a reference to the internal
457 data object. This will change soon, don't use it anymore if possible.
467 if (ref ($sub_ref) ne "CODE")
469 print STDERR $/, __FILE__, ": Plugin tried to register a non-code reference. Ignoring it.";
473 if ($type eq 'OUTPUT')
475 push (@$OUTPUT, $sub_ref);
479 if (!defined ($REGISTER->{$type}))
481 $REGISTER->{$type} = [];
485 push (@{$REGISTER->{$type}}, $sub_ref);
487 print STDERR $/, __FILE__, ': ', scalar (caller ()), " registered for ``$type''." if ($::DEBUG & 0x800);
492 =item B<store> (I<$type>, I<$data>)
494 Passes I<$data> (a hashref) to all plugins which registered for I<$type>.
501 my $type = $data->{'type'};
505 if (!defined ($type))
507 print STDERR $/, __FILE__, ": Plugin data did not include a type. This line will be skipped." if ($::DEBUG & 0x20);
511 if (!defined ($data->{'nick'}))
513 print STDERR $/, __FILE__, ": Plugin data did not include a nick. This line will be skipped." if ($::DEBUG & 0x20);
517 $nick = $data->{'nick'};
519 if (defined ($data->{'host'}))
521 my $user = host_to_username ($nick . '!' . $data->{'host'});
525 $data->{'ident'} = $user;
526 $NICK2IDENT{$nick} = $user;
530 my $host = unsharp ($data->{'host'});
531 $data->{'host'} = $host;
532 $data->{'ident'} = $host;
533 $NICK2IDENT{$nick} = $host;
536 if (defined ($DATA->{'byident'}{"$nick\@unidentified"}))
538 my $ident = $data->{'ident'};
540 print STDERR $/, __FILE__, ": Merging ``$nick\@unidentified'' to ``$ident''" if ($::DEBUG & 0x100);
542 if (!defined ($DATA->{'byident'}{$ident}))
544 $DATA->{'byident'}{$ident} = {};
547 add_hash ($DATA->{'byident'}{$ident}, $DATA->{'byident'}{"$nick\@unidentified"});
548 delete ($DATA->{'byident'}{"$nick\@unidentified"});
551 elsif (defined ($NICK2IDENT{$nick}))
553 $data->{'ident'} = $NICK2IDENT{$nick};
557 my $user = nick_to_username ($nick);
561 $data->{'ident'} = $user;
562 $NICK2IDENT{$nick} = $user;
566 $data->{'ident'} = $nick . '@unidentified';
570 $ident = $data->{'ident'};
572 if ($::DEBUG & 0x0100)
574 print STDERR $/, __FILE__, ": id ($nick) = ", $data->{'ident'};
577 if (defined ($data->{'channel'}))
579 my $chan = lc ($data->{'channel'});
580 $DATA->{'channel'}{$chan}++;
583 if ($::DEBUG & 0x400)
585 my @keys = keys (%$data);
589 my $val = $data->{$key};
590 print STDERR $/, __FILE__, ': ';
591 printf STDERR ("%10s: %s", $key, $val);
595 if (lc ($ident) eq "ignore")
597 print STDERR $/, __FILE__, ': Ignoring line from ignored user.' if ($::DEBUG & 0x0100);
601 $DATA->{'idents_of_nick'}{$nick}{$ident}++;
602 $DATA->{'total_lines'}++;
604 if (defined ($REGISTER->{$type}))
606 for (@{$REGISTER->{$type}})
616 =item B<unsharp> (I<$ident>)
618 Takes an ident (i.e. a user-host-pair, e.g. I<user@host.domain.com> or
619 I<login@123.123.123.123>) and "unsharps it". The unsharp version is then
622 What unsharp exactly does is described in the F<README>.
628 my $user_host = shift;
637 print STDERR $/, __FILE__, ": Unsharp ``$user_host''" if ($::DEBUG & 0x100);
639 ($user, $host) = split (m/@/, $user_host, 2);
641 @parts = split (m/\./, $host);
642 $num_parts = scalar (@parts);
644 if (($UNSHARP ne 'NONE')
645 and ($user =~ m/^[\~\^\-\+\=](.+)$/))
650 if ($UNSHARP eq 'NONE')
652 return ($user . '@' . $host);
654 elsif ($host =~ m/^[\d\.]{7,15}$/)
656 if ($UNSHARP ne 'LIGHT')
663 for ($i = 0; $i < ($num_parts - 2); $i++)
665 if ($UNSHARP eq 'LIGHT')
667 if ($parts[$i] !~ s/\d+/*/g)
672 elsif ($UNSHARP eq 'MEDIUM')
674 if ($parts[$i] =~ m/\d/)
683 else # ($UNSHARP eq 'HARD')
690 $host = lc (join ('.', @parts));
691 $host =~ s/\*(\.\*)+/*/;
692 $retval = $user . '@' . $host;
694 print STDERR " -> ``$retval''" if ($::DEBUG & 0x100);
698 =item B<merge_idents> ()
700 Merges idents. Does magic, don't interfere ;)
706 my @idents = keys (%IDENT2NICK);
711 my $name = ident_to_name ($ident);
713 if (!defined ($DATA->{'byident'}{$ident}))
718 if (!defined ($DATA->{'byname'}{$name}))
720 $DATA->{'byname'}{$name} = {};
723 add_hash ($DATA->{'byname'}{$name}, $DATA->{'byident'}{$ident});
732 my @keys = keys (%$src);
737 my $val = $src->{$key};
739 if (!defined ($dst->{$key}))
748 print STDERR $/, __FILE__, ": ``$key'' = ``$val''" if ($::DEBUG);
752 $dst->{$key} += $val;
755 elsif (ref ($val) ne ref ($dst->{$key}))
757 print STDERR $/, __FILE__, ": Destination and source type do not match!" if ($::DEBUG);
759 elsif (ref ($val) eq "HASH")
761 add_hash ($dst->{$key}, $val);
763 elsif (ref ($val) eq "ARRAY")
772 print STDERR $/, __FILE__, ": ``", $key, '[', $i, "]'' = ``$j''" if ($::DEBUG);
776 $dst->{$key}->[$i] += $j;
783 my $type = ref ($val);
784 print STDERR $/, __FILE__, ": Reference type ``$type'' is not supported!", $/;
793 Florian octo Forster E<lt>octo at verplant.orgE<gt>