#!/usr/bin/perl ########################################################################## # onis 0.8.2 2005-06-07 # #---=============--------------------------------------------------------# # Language: Perl # # Purpose: Generating statistics # # Input: IRC-Logfiles # # Output: One HTML file # # Version: 0.8.2 (unstable) # # License: GPL # # Homepage: http://verplant.org/onis/ # # Authors: Florian octo Forster # # Contributions are listed in THANKS # ########################################################################## BEGIN { if ($0 =~ m#^(.*)[/\\]#) { chdir ($1); } unshift (@INC, 'lib'); # 0x0010 Language (make not-translated lines red/yellow) # 0x0020 Parser (dropped lines) # 0x0040 Parser (time information) # 0x0100 Data::Core (host unsharp) # 0x0200 Data::Persistent # 0x0400 Data::Core (dump incoming data to stderr) # 0x0800 Data::Core (initializing) # 0x1000 Onis::Users $::DEBUG = 0x0000; } use strict; use warnings; use vars qw/$VERSION $REVISION/; use Onis::Config qw/get_config parse_argv read_config/; use File::Basename qw/dirname/; use Fcntl qw/:flock/; =head1 NAME onis - onis not irs stats =head1 SYNOPSIS B [I] I... =head1 DESCRIPTION onis is a script that converts IRC logfiles into an HTML statistics page. It provides information about daily channel usage, user activity, and channel trivia. It provides a configurable customization and supports Dancer, dircproxy, eggdrop, irssi, mIRC, and XChat logs. Persistent data (history files) and automatic log purging make onis applicable for a large number of logfiles. It also features a powerful translation infrastructure. =cut $VERSION = ''; $REVISION = '$LastChangedRevision$'; if (!$VERSION) { $VERSION = $REVISION; $VERSION =~ s/^\D*(\d+).*/r$1/; } print STDERR $/, __FILE__, ': $Id$' if ($::DEBUG); our $FileInfo; our $PurgeLogs = 0; parse_argv (@ARGV); read_config (get_config ('config') ? get_config ('config') : 'onis.conf'); read_config (scalar get_config ('theme')) if (get_config ('theme')); my $output = get_config ('output'); if (!$output) { $output = "reports/onis.html"; } foreach ('Core', get_config ('plugin')) { my $module = ucfirst (lc ($_)); require "Onis/Plugins/$module.pm"; } if (!get_config ('input')) { print STDERR < [logfile logfile ..] Options: --config Specify alternate config file --output Defines the file to write the HTML to. --overwrite Overwrites files without prompting. --channel Defines the channel's name. --logtype Defines the logfile's type. See 'config' for a complete list. --user Define's the generator's name. For a full list of all options please read the onis(1) manpage. EOF exit (1); } if (-e $output) { my $overwrite = 0; if (get_config ('overwrite')) { my $tmp = lc (get_config ('overwrite')); if ($tmp eq 'true' or $tmp eq 'yes' or $tmp eq 'on') { $overwrite = 1; } } if (!$overwrite) { print STDERR <; exit (1) if ($answer =~ m/n/i); } } my $logtype = 'Eggdrop'; if (get_config ('logtype')) { $logtype = ucfirst (lc (get_config ('logtype'))); } require "Onis/Parser/$logtype.pm"; require Onis::Parser::Persistent; require Onis::Data::Persistent; import Onis::Parser (qw(parse last_date)); import Onis::Parser::Persistent (qw(newfile)); import Onis::Data::Persistent (); $FileInfo = Onis::Data::Persistent->new ('FileInfo', 'inode', qw(mtime)); if (get_config ('purge_logs')) { my $temp = lc (get_config ('purge_logs')); if (($temp eq 'truncate') or ($temp eq 'shorten')) { $PurgeLogs = 1; } elsif (($temp eq 'delete') or ($temp eq 'remove') or ($temp eq 'del')) { $PurgeLogs = 2; } } for (get_config ('input')) { my $file = $_; my $logfile; my $status = 4; my $position = 0; my $mtime; my $size; my $inode; ($inode, $size, $mtime) = (stat ($file))[1,7,9]; print STDERR $/, $/, __FILE__, " --- New File ``$file'' ---" if ($::DEBUG & 0x200); if (!defined ($mtime)) { print STDERR $/, __FILE__, ": Unable to stat file ``$file''"; next; } else { my ($old_mtime) = $FileInfo->get ($inode); print STDERR $/, __FILE__, ": ``$file'': " if ($::DEBUG & 0x200); if (defined ($old_mtime)) { if ($old_mtime == $mtime) { print STDERR "File did not change. Skipping." if ($::DEBUG & 0x200); next; } elsif ($old_mtime < $mtime) { print STDERR "File changed. Reading it again." if ($::DEBUG & 0x200); } else { print STDERR "File ``$file'' is older than expected. There might be a problem!"; } } else { print STDERR "File appears to be new. Reading it." if ($::DEBUG & 0x200); } $FileInfo->put ($inode, $mtime); } # truncate if ($PurgeLogs == 1) { unless (open ($logfile, '+< ' . $file)) { print STDERR $/, __FILE__, ": Unable to open file ``$file'': $!"; next; } } else { unless (open ($logfile, '< ' . $file)) { print STDERR $/, __FILE__, ": Unable to open file ``$file'': $!"; next; } } if ($PurgeLogs) { unless (flock ($logfile, LOCK_EX)) { print STDERR $/, __FILE__, ": Unable to get an exclusive lock for file ``$file'': $!"; close ($logfile); next; } } else { unless (flock ($logfile, LOCK_SH)) { print STDERR $/, __FILE__, ": Unable to get a shared lock for file ``$file'': $!"; close ($logfile); next; } } newfile ($inode); while (<$logfile>) { s/\n|\r//g; $status = parse ($_); # 0 == rewind file # 1 == line parsed # 2 == unable to parse # 3 == line old # 4 == don't have date if ($status == 0) { print STDERR $/, __FILE__, ": Rewinding file ``$file''" if ($::DEBUG & 0x200); seek ($logfile, 0, 0); $position = 0; } elsif (($status == 1) or ($status == 2) or ($status == 3)) { $position = tell ($logfile); } elsif ($status == 4) { # void } else { print STDERR $/, __FILE__, ": Parser returned unknown status code: ``$status''"; } } if ($PurgeLogs and (($status == 1) or ($status == 2) or ($status == 3))) { if (($PurgeLogs > 1) #and (($position + 1) >= $size) ) { # delete file print STDERR $/, __FILE__, ": Deleting empty file ``$file''" if ($::DEBUG & 0x200); close ($logfile); if (-w $file) { unless (unlink ($file)) { print STDERR $/, __FILE__, ": Unable to delete empty file ``$file'': $!"; } delete ($FileInfo->{$inode}); } else { print STDERR $/, __FILE__, ": Won't delete ``$file''. Set it to writeable first!"; } } else { seek ($logfile, 0, 0); if (truncate ($logfile, 0)) { print $logfile &last_date (); print STDERR $/, __FILE__, ": Truncated ``$file''" if ($::DEBUG & 0x200); } else { print STDERR $/, __FILE__, ": Couldn't truncate file ``$file'': $!"; } close ($logfile); } } else { close ($logfile); } } require Onis::Data::Core; require Onis::Html; import Onis::Data::Core qw#print_output#; import Onis::Html qw#open_file close_file#; if (open_file ($output)) { print_output (); close_file (); } else { # Fail and make noise! ;) print STDERR <: I; Load the config from this file. B<(command line only)> =item B: I; Sets the file from which to read the user configuration. =item B: I; Sets the language file/translation to use. =item B: I; Sets the plugins to load. The plugin B will always be loaded. =item B: I; Read and parse this file(s). B<(config file only)> =item B: I; Sets the parser to use for parsing the input file. =item B: I; Write the generated output to this file. =item B: I; Sets wether or not to overwrite the output-file if it exists. =item B: "I" | "I" | "I"; Sets wether logs should be truncated or even removes after they have been parsed. =item B: I; Sets the user that created the page. Defaults to the environment variable B or "onis", if it is not set. =item B: I; Sets the name of the channel being parsed. Normally this is auto-detected. =item B: "I" | "I" | "I" | "I"; Sets how to do unsharping. What each setting actually does is described in the readme and in L. =back =head2 Appearance =over 4 =item B: I; Theme file to load. =item B: I; Sets the stylesheet to use. This is included in the HTML-file as-is, so you have to take care of absolute/relative paths yourself.. =item B: I; Wether or not to print the color codes (introduced by mIRC, used by idiots and ignored by the rest) in the generated HTML-file. Of course this defaults to not print the codes.. =item B: I; Sets if user-images should be displayed. =item B: I; Sets the default image to use if no user-defined image is available. =item B: "I" | "I" | "I" | "I"; =item B: "I" | "I" | "I" | "I"; =item B: "I" | "I" | "I" | "I"; Sets if and how lines, words and/or characters should be displayed. =item B: "I" | "I" | "I"; Sets wether to sort by lines, words or characters written. =item B: I; Wether or not to display a fixed width bar that shows when a user is most active. =item B: I, I, I, I; =item B: I, I, I, I; Sets the images to use for horizontal and vertical bars. This should be used in the theme-file. =item B: I; Sets the encoding to include in the HTML-file. If you don't know what this is, don't change it.. =item B: I; Wether or not this is a public page. Public pages may be linked on the onis homepage at some point in the fututre.. =back =head2 Storage / Persistency =over 4 =item B: I; Sets the storage-module to use. =item B: I; Sets the directory to store persistency information under. =item B: I; Sets the file to write persistency data to, if applicable by the storage-module. =back =head2 Plugins =over 4 =item B: I; Substring containing only word-characters needs to be this long to be considered a word. =item B: I; Sets the number of "most referenced nicks", "most used words" and the like to be displayed. This option will be removed in the future. =item B: I; =item B: I; The number of lines in the big and the small table. While in the big table one line is dedicated for one person the small table displays six persons per line. =item B: I; Sets how many quotes are to be cached for each nick. At the end of the run one of the quotes in the cache will be chosen at random and displayed. =item B: I; =item B: I; Sets the minimum and maximum length of a quote. Too short quotes may be not very typical for a person, too long quotes may clutter the layout. =item B: I; =item B: I; Number of conversations partners to include in the output (or in the conversations section of the userdetails plugin). =item B: I; Sets how many lines without interruption are considered a soliloquy. =item B: I; =item B: I; Sets the number of days shown in the longterm-plugin (or the longter-section of the userdetails-plugin). =item B: I; The Words-Plugin will ignore words with less than this characters. =item B: I; The number of nicks to print userdetails for. =back =head1 AUTHOR Florian Forster Eocto at verplant.orgE =cut