pisg.pl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. #!/usr/bin/perl -w
  2. use strict;
  3. use Getopt::Long;
  4. use FindBin;
  5. # pisg - Perl IRC Statistics Generator
  6. #
  7. # Copyright (C) 2001 <Morten 'LostStar' Brix Pedersen> - morten@wtf.dk
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  22. my ($debug);
  23. # Default values for pisg. Their meanings are explained in CONFIG-README.
  24. #
  25. # If you are a user of pisg, you shouldn't change it here, but instead on
  26. # commandline or in pisg.cfg
  27. my $conf = {
  28. channel => "#channel",
  29. logtype => "Logfile",
  30. logfile => "channel.log",
  31. format => "mIRC",
  32. network => "SomeIRCNet",
  33. outputfile => "index.html",
  34. maintainer => "MAINTAINER",
  35. pagehead => "none",
  36. configfile => "pisg.cfg",
  37. imagepath => "",
  38. logdir => "",
  39. lang => 'EN',
  40. langfile => 'lang.txt',
  41. prefix => "",
  42. modules_dir => $FindBin::Bin . "/modules", # Module search path
  43. # Colors / Layout
  44. bgcolor => "#dedeee",
  45. bgpic => '',
  46. text => "black",
  47. hbgcolor => "#666699",
  48. hcolor => "white",
  49. hicell => "#BABADD",
  50. hicell2 => "#CCCCCC",
  51. tdcolor => "black",
  52. tdtop => "#C8C8DD",
  53. link => "#0b407a",
  54. vlink => "#0b407a",
  55. hlink => "#0b407a",
  56. headline => "#000000",
  57. rankc => "#CCCCCC",
  58. pic_v_0 => "blue-v.png",
  59. pic_v_6 => "green-v.png",
  60. pic_v_12 => "yellow-v.png",
  61. pic_v_18 => "red-v.png",
  62. pic_h_0 => "blue-h.png",
  63. pic_h_6 => "green-h.png",
  64. pic_h_12 => "yellow-h.png",
  65. pic_h_18 => "red-h.png",
  66. # Stats settings
  67. show_linetime => 0,
  68. show_time => 1,
  69. show_words => 0,
  70. show_wpl => 0,
  71. show_cpl => 0,
  72. show_legend => 1,
  73. show_kickline => 1,
  74. show_actionline => 1,
  75. show_shoutline => 1,
  76. show_violentlines => 1,
  77. # Less important things
  78. minquote => 25,
  79. maxquote => 65,
  80. wordlength => 5,
  81. activenicks => 25,
  82. activenicks2 => 30,
  83. topichistory => 3,
  84. nicktracking => 0,
  85. timeoffset => "+0",
  86. # Misc settings
  87. foul => 'ass fuck bitch shit scheisse scheiße kacke arsch ficker ficken schlampe',
  88. violent => 'slaps beats smacks',
  89. ignorewords => '',
  90. tablewidth => 614,
  91. regexp_aliases => 0,
  92. # Developer stuff
  93. debug => 0,
  94. debugfile => "debug.log",
  95. version => "v0.23-cvs",
  96. };
  97. my ($chans, $users);
  98. my ($totallength, $oldtime, $actions, $normals, %T);
  99. sub load_modules
  100. {
  101. push(@INC, $conf->{modules_dir});
  102. require Pisg::Common;
  103. Pisg::Common->import();
  104. Pisg::Common::init_common($debug);
  105. }
  106. sub main
  107. {
  108. print "pisg $conf->{version} - Perl IRC Statistics Generator\n\n";
  109. get_cmdlineoptions();
  110. load_modules(); # Add modules, now that their location is known.
  111. init_config(); # Init config. (Aliases, ignores, other options etc.)
  112. init_debug()
  113. unless ($conf->{debugstarted}); # Init the debugging file
  114. get_language_templates(); # Get translations from lang.txt
  115. parse_channels(); # parse any channels in <channel> statements
  116. do_channel()
  117. unless ($conf->{chan_done}{$conf->{channel}});
  118. close_debug(); # Close the debugging file
  119. }
  120. sub do_channel
  121. {
  122. init_pisg(); # Init commandline arguments and other things
  123. init_words(); # Init words. (Foulwords etc)
  124. # Pick our stats generator.
  125. my $analyzer;
  126. eval <<_END;
  127. use Pisg::Parser::$conf->{logtype};
  128. \$analyzer = new Pisg::Parser::$conf->{logtype}(\$conf, \$debug);
  129. _END
  130. if ($@) {
  131. print STDERR "Could not load stats generator for '$conf->{logtype}': $@\n";
  132. return undef;
  133. }
  134. my $stats = $analyzer->analyze();
  135. my $generator;
  136. eval <<_END;
  137. use Pisg::HTMLGenerator;
  138. \$generator = new Pisg::HTMLGenerator(\$conf, \$debug, \$stats, \$users, \%T);
  139. _END
  140. if ($@) {
  141. print STDERR "Could not load stats html generator (Pisg::HTMLGenerator): $@\n";
  142. return undef;
  143. }
  144. # Create our HTML page if the logfile has any data.
  145. if (defined $stats and $stats->{totallines} > 0) {
  146. $generator->create_html();
  147. } elsif ($stats->{totallines} == 0) {
  148. print STDERR "No lines found in logfile.. skipping.\n";
  149. }
  150. $conf->{chan_done}{$conf->{channel}} = 1;
  151. }
  152. sub parse_channels
  153. {
  154. my %origconf = %{ $conf };
  155. foreach my $channel (keys %{ $chans }) {
  156. foreach (keys %{ $chans->{$channel} }) {
  157. $conf->{$_} = $chans->{$channel}{$_};
  158. }
  159. do_channel();
  160. $origconf{chan_done} = $conf->{chan_done};
  161. %{ $conf } = %origconf;
  162. }
  163. }
  164. sub init_pisg
  165. {
  166. # Reset all variables
  167. undef $totallength;
  168. my $timestamp = time();
  169. $conf->{start} = time();
  170. if ($conf->{timeoffset} =~ /\+(\d+)/) {
  171. # We must plus some hours to the time
  172. $timestamp += 3600 * $1; # 3600 seconds per hour
  173. } elsif ($conf->{timeoffset} =~ /-(\d+)/) {
  174. # We must remove some hours from the time
  175. $timestamp -= 3600 * $1; # 3600 seconds per hour
  176. }
  177. $conf->{timestamp} = $timestamp;
  178. # Add trailing slash when it's not there..
  179. $conf->{imagepath} =~ s/([^\/])$/$1\//;
  180. # Set some values
  181. $oldtime = "00";
  182. $actions = "0";
  183. $normals = "0";
  184. print "Using language template: $conf->{lang}\n\n" if ($conf->{lang} ne 'EN');
  185. print "Statistics for channel $conf->{channel} \@ $conf->{network} by $conf->{maintainer}\n\n";
  186. }
  187. sub init_config
  188. {
  189. if ((open(CONFIG, $conf->{configfile}) or open(CONFIG, $FindBin::Bin . "/$conf->{configfile}"))) {
  190. print "Using config file: $conf->{configfile}\n";
  191. my $lineno = 0;
  192. while (my $line = <CONFIG>)
  193. {
  194. $lineno++;
  195. next if ($line =~ /^#/);
  196. if ($line =~ /<user.*>/) {
  197. my $nick;
  198. if ($line =~ /nick="([^"]+)"/) {
  199. $nick = $1;
  200. add_alias($nick, $nick);
  201. } else {
  202. print STDERR "Warning: no nick specified in $conf->{configfile} on line $lineno\n";
  203. next;
  204. }
  205. if ($line =~ /alias="([^"]+)"/) {
  206. my @thisalias = split(/\s+/, lc($1));
  207. foreach (@thisalias) {
  208. if ($conf->{regexp_aliases} and /[\|\[\]\{\}\(\)\?\+\.\*\^\\]/) {
  209. add_aliaswild($nick, $_);
  210. } elsif (not $conf->{regexp_aliases} and s/\*/\.\*/g) {
  211. # quote it if it is a wildcard
  212. s/([\|\[\]\{\}\(\)\?\+\^\\])/\\$1/g;
  213. add_aliaswild($nick, $_);
  214. } else {
  215. add_alias($nick, $_);
  216. }
  217. }
  218. }
  219. if ($line =~ /pic="([^"]+)"/) {
  220. $users->{userpics}{$nick} = $1;
  221. }
  222. if ($line =~ /link="([^"]+)"/) {
  223. $users->{userlinks}{$nick} = $1;
  224. }
  225. if ($line =~ /ignore="Y"/i) {
  226. add_ignore($nick);
  227. }
  228. if ($line =~ /sex="([MmFf])"/i) {
  229. $users->{sex}{$nick} = lc($1);
  230. }
  231. } elsif ($line =~ /<set(.*)>/) {
  232. my $settings = $1;
  233. while ($settings =~ s/[ \t]([^=]+)=["']([^"']*)["']//) {
  234. my $var = lc($1); # Make the string lowercase
  235. unless (($conf->{$var} eq $2) || $conf->{cmdl}{$var}) {
  236. $conf->{$var} = $2;
  237. }
  238. $debug->("Conf: $var = $2");
  239. }
  240. } elsif ($line =~ /<channel=['"]([^'"]+)['"](.*)>/i) {
  241. my ($channel, $settings) = ($1, $2);
  242. $chans->{$channel}->{channel} = $channel;
  243. $conf->{chan_done}{$conf->{channel}} = 1; # don't parse channel in $conf->{channel} if a channel statement is present
  244. while ($settings =~ s/\s([^=]+)=["']([^"']*)["']//) {
  245. my $var = lc($1);
  246. $chans->{$channel}{$var} = $2;
  247. $debug->("Channel conf $channel: $var = $2");
  248. }
  249. while (<CONFIG>) {
  250. last if ($_ =~ /<\/*channel>/i);
  251. while ($_ =~ s/^\s*(\w+)\s*=\s*["']([^"']*)["']//) {
  252. my $var = lc($1);
  253. unless ($conf->{cmdl}{$var}) {
  254. $chans->{$channel}{$var} = $2;
  255. }
  256. $debug->("Conf $channel: $var = $2");
  257. }
  258. }
  259. }
  260. }
  261. close(CONFIG);
  262. }
  263. }
  264. sub init_words
  265. {
  266. $conf->{foul} =~ s/\s+/|/g;
  267. foreach (split /\s+/, $conf->{ignorewords}) {
  268. $conf->{ignoreword}{$_} = 1;
  269. }
  270. $conf->{violent} =~ s/\s+/|/g;
  271. }
  272. sub init_debug
  273. {
  274. $conf->{debugstarted} = 1;
  275. if ($conf->{debug}) {
  276. print "[ Debugging => $conf->{debugfile} ]\n";
  277. open(DEBUG,"> $conf->{debugfile}") or print STDERR "$0: Unable to open debug
  278. file($conf->{debugfile}): $!\n";
  279. $debug->("*** pisg debug file for $conf->{logfile}\n");
  280. if ($conf->{debugqueue}) {
  281. print DEBUG $conf->{debugqueue};
  282. delete $conf->{debugqueue};
  283. }
  284. } else {
  285. $debug = sub {};
  286. }
  287. }
  288. sub get_subst
  289. {
  290. my ($m,$f,$hash) = @_;
  291. if ($hash->{nick} && $users->{sex}{$hash->{nick}}) {
  292. if ($users->{sex}{$hash->{nick}} eq 'm') {
  293. return $m;
  294. } elsif ($users->{sex}{$hash->{nick}} eq 'f') {
  295. return $f;
  296. }
  297. }
  298. return "$m/$f";
  299. }
  300. $debug = sub {
  301. if ($conf->{debug} or not $conf->{debugstarted}) {
  302. my $debugline = $_[0] . "\n";
  303. if ($conf->{debugstarted}) {
  304. print DEBUG $debugline;
  305. } else {
  306. $conf->{debugqueue} .= $debugline;
  307. }
  308. }
  309. };
  310. sub close_debug
  311. {
  312. if ($conf->{debug}) {
  313. close(DEBUG) or print STDERR "$0: Cannot close debugfile($conf->{debugfile}): $!\n";
  314. }
  315. }
  316. sub get_cmdlineoptions
  317. {
  318. my $tmp;
  319. # Commandline options
  320. my ($moduledir, $channel, $logfile, $format, $network, $maintainer, $outputfile, $logdir, $prefix, $configfile, $help);
  321. my $usage = <<END_USAGE;
  322. Usage: pisg.pl [-ch channel] [-l logfile] [-o outputfile] [-ma
  323. maintainer] [-f format] [-n network] [-d logdir] [-mo moduledir] [-h]
  324. -ch --channel=xxx : Set channel name
  325. -l --logfile=xxx : Log file to parse
  326. -o --outfile=xxx : Name of html file to create
  327. -ma --maintainer=xxx : Channel/statistics maintainer
  328. -f --format=xxx : Logfile format [see FORMATS file]
  329. -n --network=xxx : IRC Network this channel is on.
  330. -d --dir=xxx : Analyze all files in this dir. Ignores logfile.
  331. -p --prefix=xxx : Analyse only files starting with xxx in dir.
  332. Only works with --dir
  333. -mo --moduledir=xxx : Directory containing pisg's modules.
  334. -co --configfile=xxx : Config file
  335. -h --help : Output this message and exit (-? also works).
  336. Example:
  337. \$ pisg.pl -n IRCnet -f xchat -o suid.html -ch \\#channel -l logfile.log
  338. All options may also be defined by editing the configuration file and
  339. calling pisg without arguments.
  340. END_USAGE
  341. #'
  342. if (GetOptions('channel=s' => \$channel,
  343. 'logfile=s' => \$logfile,
  344. 'format=s' => \$format,
  345. 'network=s' => \$network,
  346. 'maintainer=s' => \$maintainer,
  347. 'outfile=s' => \$outputfile,
  348. 'dir=s' => \$logdir,
  349. 'prefix=s' => \$prefix,
  350. 'ignorefile=s' => \$tmp,
  351. 'aliasfile=s' => \$tmp,
  352. 'moduledir=s' => \$moduledir,
  353. 'configfile=s' => \$configfile,
  354. 'help|?' => \$help
  355. ) == 0 or $help) {
  356. die($usage);
  357. }
  358. if ($tmp) {
  359. die("The aliasfile and ignorefile has been obsoleted by the new
  360. pisg.cfg, please use that instead [look in pisg.cfg]\n");
  361. }
  362. if ($channel) {
  363. $conf->{channel} = $channel;
  364. $conf->{cmdl}{channel} = 1;
  365. }
  366. if ($logfile) {
  367. $conf->{logfile} = $logfile;
  368. $conf->{cmdl}{logfile} = 1;
  369. }
  370. if ($format) {
  371. $conf->{format} = $format;
  372. $conf->{cmdl}{format} = 1;
  373. }
  374. if ($network) {
  375. $conf->{network} = $network;
  376. $conf->{cmdl}{network} = 1;
  377. }
  378. if ($maintainer) {
  379. $conf->{maintainer} = $maintainer;
  380. $conf->{cmdl}{maintainer} = 1;
  381. }
  382. if ($outputfile) {
  383. $conf->{outputfile} = $outputfile;
  384. $conf->{cmdl}{outputfile} = 1;
  385. }
  386. if ($logdir) {
  387. $conf->{logdir} = $logdir;
  388. $conf->{cmdl}{logdir} = 1;
  389. }
  390. if ($prefix) {
  391. $conf->{prefix} = $prefix;
  392. $conf->{cmdl}{prefix} = 1;
  393. }
  394. if ($moduledir) {
  395. $conf->{modules_dir} = $moduledir;
  396. $conf->{cmdl}{modules_dir} = 1;
  397. }
  398. if ($configfile) {
  399. $conf->{configfile} = $configfile;
  400. $conf->{cmdl}{configfile} = 1;
  401. }
  402. }
  403. sub get_language_templates
  404. {
  405. open(FILE, $conf->{langfile}) or open (FILE, $FindBin::Bin . "/$conf->{langfile}") or die("$0: Unable to open language file($conf->{langfile}): $!\n");
  406. while (my $line = <FILE>)
  407. {
  408. next if ($line =~ /^#/);
  409. if ($line =~ /<lang name=\"([^"]+)\">/) {
  410. # Found start tag, setting the current language
  411. my $current_lang = $1;
  412. while (<FILE>) {
  413. last if ($_ =~ /<\/lang>/i);
  414. # Get 'template = "Text"' in language file:
  415. if ($_ =~ /(\w+)\s+=\s+"(.*)"\s*$/) {
  416. $T{$current_lang}{$1} = $2;
  417. }
  418. }
  419. }
  420. }
  421. close(FILE);
  422. }
  423. &main(); # Run the script