Просмотр исходного кода

First cvs checkin of pisg, this is 0.16pre5

Morten Brix Pedersen 25 лет назад
Сommit
9ecb73b686
10 измененных файлов с 2787 добавлено и 0 удалено
  1. 340 0
      COPYING
  2. 43 0
      CREDITS
  3. 172 0
      Changelog
  4. 39 0
      FORMATS
  5. 29 0
      Makefile
  6. 78 0
      README
  7. BIN
      gfx/pipe-blue.png
  8. BIN
      gfx/pipe-purple.png
  9. 2063 0
      pisg.pl
  10. 23 0
      users.cfg

+ 340 - 0
COPYING

@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.

+ 43 - 0
CREDITS

@@ -0,0 +1,43 @@
+CREDITS goes to...
+--
+
+ * Stefan aka Flyghost <flyghost@ghosthouse.cx>
+   | Code contribution and fixes
+ * P. Gnodde aka Shogun <peter@pcswebdesign.nl>
+   | Lots of feature enhancements and code cleanups, thanks!
+ * Steve Tibold aka T0mcat <t0mcat@chat.lu>
+   | Tons of ideas
+ * Markus Geiger <mail@markus-geiger.net>
+   | Alias/nick replacement
+ * Joakim Sandström <joose@nalle.puh.org>
+   | Ideas and bugfixes
+ * Ryan Ryttie <kesu@kesuki.dyndns.org>
+   | xchat logfiles request/help
+ * Martin Poulsen <xact@mail.dk>
+   | Suggestions and moral support
+ * Vetinary <iranitev@gmx.net>
+   | Lots of feature additions and code cleanups, thanks!
+ * Laocius <irish@rook.lug.usf.edu>
+   | The features 'most used words' and 'most referenced nicks'
+ * Luca (eni) <adsl.help@dada.it>
+   | Reported a very small, but stupid bug with the xchat fileformat :)
+ * kiss dess <duvet@mail.com>
+   | Found a few minor bugs. And added replace_links()
+ * Sam Bingner <sam@dhs.org>
+   | Added mIRC color stripping, eggdrop log format and nick/alias tracking.
+ * S. William Schulz <sws@astrum.com>
+   | Better commandline arguments, --help and fixed indenting problems with
+     xchat logs, also fixed a critical thing with --format
+ * Tim Hunter <tim@quic.net>
+   | 'shortest lines', 'most fouls', 'pageheader' and more. 
+ * Winfried (Doomshammer) <doomy@doomshammer.yi.org>
+   | grufti log format help, German translation and a ton of other things.
+ * Kim Ingemann (Tuxi) <tuxi@tuxi.dk>
+   | Danish translation
+ * Molator <molator@ifrance.com>
+   | French translation
+ * Jamie Cheetham <jamie@softham.co.uk>
+   | Eggdrop 'last messaged repeated x times' fix
+
+I probably forgot a lot of people here, _PLEASE_ notify me if I left you
+out.. because I have a bad habit of forgetting to maintain this list :)

+ 172 - 0
Changelog

@@ -0,0 +1,172 @@
+pisg (0.16) - 
+
+   * Danish translation (thanks to Kim Ingemann aka Tuxi)
+   * Fixed some unitialized values which came up in some situations
+   * Eggdrop log had 'last message repeated x times' - pisg didn't understood
+     this before, not it does! :) (thanks to Jamie Cheetham).
+   * French translation (thanks Molator)
+   * Also use the timeoffset in 'most active times'
+   * Started to distribute various scripts with pisg:
+     - 'dropegg.pl' - takes a bunch of eggdrop logs and converts them into
+       mIRC format (Emil Mikulic <darkmoon7@optushome.com.au>)
+     - 'addalias.pl' - webinterface for allowing users to add their own stuff
+       to the users.cfg file. (Doomshammer <doomshammer@doomshammer.yi.org>)
+     - 'crontab' - small file explaining how to use pisg with crontab
+     
+
+pisg (0.15) - Fri, July 20th 2001
+
+   * Fixed bug where grufti-logfiles wouldn't ignore oppers.
+   * Fixed pisg dying when there is no action and normallines in logfile
+   * Also strip mIRC bold and underline codes
+   * Nick tracking code optimized
+   * Eggdrop logs were logging server mode changes.. fixed.
+   * The 'active times' bars should now be more logical and always have a max
+     height of 100px
+   * Added new users.cfg file which obsoletes aliasfile and ignorefile
+   * New feature: ability to add user pictures to stats
+   * New feature: ability to add user links to stats (homepages, e-mail)
+   * Made a big code change so it now supports somekind of template system so
+     pisg can support more than one language
+   * German language support added (thanks Winfried aka Doomshammer)
+   * Now shows the time when the topic was changed in 'Latest topics'.
+   * Thousands of small things which I forgot.
+   * Removed the documentation for unnamed arguments on the commandline..
+     people should start using the named arguments (--logfile, -f etc.)
+
+pisg (0.14) - Mon, July 16th 2001
+
+   * 'Most referenced nicks' and 'most used words' now ignores the nicks in
+     ignore.txt
+   * 'Most used words' now ignores nicks
+   * Fixed critical bug; --format wasn't working. (thanks S. William Schulz)
+
+pisg (0.13) - Sun, July 15th 2001 
+
+   * New feature: Now supports 'grufti' (bot) logfiles.
+   * New feature: Now supports 'bxlog' (logging script for bitchx) logfiles
+   * Improved some debugging
+   * Cleaned up a lot of code, so it's structured better - less global
+     variables and everything is now in subs, also found some variables which
+     weren't used.. so it should be more correct and more efficient now
+   * Fix up some speed in the kicking stats... it was doing the same grep twice
+   * Fixed a stupid bug which caused pisg to die if the alias.txt wasn't
+     there.
+   * Really fix typo, sherif -> sheriff
+   * Added a FORMATS file explaining the supported fileformats, and updated
+     the README accordingly
+   * Added a new feature where one can specify a log directory, and then pisg
+     will run through all the files in the directory and combine them into one
+     big html file.
+   * -d on commandline now specifies logdir instead of debugging (removed)
+   * Also added --aliasfile and --ignorefile as commandline options.
+
+pisg (0.12) - Thu, July 12th 2001 
+
+   * Strip mIRC color codes (Sam Bingner)
+   * New feature: Support eggdrop logformats (Sam Bingner)
+   * Track nicks/aliases dynamically [as an option] (Sam Bingner)
+   * Added better commandline arguments (like --channel=#blah) and pisg now
+     has a cool usage --help option. (thanks S. William Schulz)
+   * xchat logformats which are indented should now be supported
+   * New feature: 'most fouls' (bad language)
+   * New feature: 'shortest lines' (as opposite to longest lines)
+   * Added $timeoffset so people can get times in their own time zones. 
+   * It's now possible to define a pageheader file which can be included on
+     the HTML page, with $pageheader
+   * Changed 'no topics' text 
+
+pisg (0.11) - Fri, June 29th 2001
+   * Before http:// didn't get counted as a smiley, now "all" protocols
+     doesn't get counted as smilies (ftp://, gohper://).
+   * Fixed typo, sherif -> sheriff
+   * Changed the appearence of 'most ref nicks/words', email me if you don't
+     like it.. :)
+   * Also add some fade stuff to the 'most active nicks'
+   * Topic history variable wasn't used, fixed.
+   * Replace & with &amp;
+   * Replace URLs and e-mail addys in 'random quote' and 'last topic' to HTML
+     hrefs
+   * Some other small changes which I forgot
+   
+pisg (0.10) - Fri, June 22th 2001
+   * The 'Im going on vacation'-release. Fixes very small things.
+   * Small bug in counting kicker/kicked person when using xchat - fixed.
+   * Fixed a bug where some lines didn't catch up the alias for a nick
+   * Join counting was broken.. fixed.
+   * Use 'px' to define font sizes instead of 'pt' in CSS.
+   * Fix some colors in the page which couldn't get set by variables in the
+     top
+   * Other small changes
+
+pisg (0.09) - Tue, June 5th 2001
+   * Some small improvements in the count of sadfaces/smilies
+   * New features:
+     - 'most used words'
+     - 'most referenced nick'
+   * Rewrote a LOT of the format dependent code, now it's very easy adding
+     new IRC log formats
+   * Fixed random bugs here and there
+   * The usual small, nice code enhancements.
+
+pisg (0.08) - Sat, April 14th 2001
+
+   * Documentation updates (remember to up the version number :))
+   * A lot of nice changes from Vetinary <iranitev@gmx.net>:
+     - 'op/deop stats'
+     - 'join stats'
+     - 'action counting'
+     - 'counting of :(, :-('
+   * use strict;
+
+pisg (0.07) - Sun, March 11th 2001
+
+   * New feature: Now supports xchat logfiles <:)
+   * Should render pages 100% HTML 4.01/CSS2 compatible now
+   * Some minor code cleanups (use strict; soon)
+
+pisg (0.06) - Wed, February 21th 2001
+
+   * Even more workarounds for small logfiles
+   * More customization options
+   * Now shows processtime
+   * You can now specify nicks to ignore in the logfile (bots etc)
+   * Now supports commandline arguments (./pisg.pl chan logfile outputfile)
+   * Some speedup improvements
+   * A bit more HTML 4 compliant :)
+
+pisg (0.05) - Thu, February 15th 2001
+
+    * New features:
+      - 'Runners-up to Big numbers'
+      - 'Latest topics'
+    * More fixes for small logfiles
+    * Enhanced 'Most active times' 
+    * Speedup improvements
+    * Added CREDITS file to the nice people who have contributed :)
+
+pisg (0.04) - Fri, January 26th 2001
+
+    * New features:
+      - 'the person that smiled the most'
+      - 'can now connect nicks together through alias file'
+      - 'person that wrote the most monologues (5 or more lines in a row)'
+    * Fixes for small logfiles
+    * Restrutured code
+    * More customization through variables in the top of the script
+
+pisg (0.03) - Thu, January 11rd 2001
+    
+    * Code cleanups
+    * New features:
+      - 'the op who kicked most'
+      - 'the one who got kicked the most'
+      - 'longest lines'
+    * Highlight most active hour
+    * New images, 'pipe-blue.png' and 'pipe-purple.png', removed 'pipe3v.gif'
+    * Updated README
+    * Added Changes file
+
+pisg (0.02) - Wed, January 3rd 2001
+    
+    * Initial release.

+ 39 - 0
FORMATS

@@ -0,0 +1,39 @@
+FORMATS - supported IRC logfile formats by pisg
+------------------
+
+pisg supports a variety of logfile formats, these are:
+
+* mIRC:
+  - A client for Windows (www.mirc.com)
+  
+  - If you want to create mIRC logfiles with your eggdrop, you can get some TCL
+    which does it all for you (also logrotating and much more).
+
+    MEL (mIRCStats Eggdrop Logger) can be downloaded from the following URL:
+
+    http://mel.angelbears.org/
+    
+  - In order to use this, format must be set to 'mIRC'.
+
+* X-Chat:
+  -  A client for Linux (www.xchat.org)
+    
+  -  In order to use this, format must be set to 'xchat'.
+
+* Eggdrop:
+  - A cool and advanced IRC bot for Linux (www.eggheads.org)
+
+  - In order to use this, format must be set to 'eggdrop'.
+     
+* Grufti:
+  - A bot which I'm not really familiar with (www.grufti.com)
+  - The author has actually made something like pisg called 'gruftistats',
+    but someone mailed me and told me that it hadn't been maintained for
+    over a year, and it had some problems..
+
+  - In order to use this, format must be set to 'grufti'
+
+* bxlog:
+  - A logging script for BitchX (console client - see scripts.bitchx.com)
+   
+  - In order to use this, format must be set to 'bxlog'

+ 29 - 0
Makefile

@@ -0,0 +1,29 @@
+# Simple Makefile to make new releases of pisg (newreleases dir must exist)
+
+VERSION = 0.16
+
+DIRNAME = pisg-$(VERSION)
+
+TARFILE = pisg-$(VERSION).tar.gz
+ZIPFILE = pisg-$(VERSION).zip
+
+pisg:
+	mkdir $(DIRNAME)
+	cp pisg.pl $(DIRNAME)
+	cp CREDITS $(DIRNAME)
+	cp gfx/pipe-purple.png $(DIRNAME)
+	cp gfx/pipe-blue.png $(DIRNAME)
+	cp COPYING $(DIRNAME)
+	cp users.cfg $(DIRNAME)
+	cp README $(DIRNAME)
+	cp FORMATS $(DIRNAME)
+	cp Changelog $(DIRNAME)
+	cp -r scripts $(DIRNAME)
+	tar zcfv newrelease/pisg-$(VERSION).tar.gz $(DIRNAME)
+	zip -r pisg $(DIRNAME)
+	mv pisg.zip newrelease/$(ZIPFILE)
+	mv $(DIRNAME) newrelease
+clean:
+	rm -r newrelease/$(DIRNAME)
+	rm newrelease/$(TARFILE)
+	rm newrelease/$(ZIPFILE)

+ 78 - 0
README

@@ -0,0 +1,78 @@
+README, pisg - by me and others (see CREDITS file)
+------------------
+pisg - Perl IRC Statistics Generator, is a Perl script which takes IRC
+logfiles and makes nice looking stats of them, which can be amusing to show
+for the users of your channel.
+
+The supported logfile formats is explained in the FORMATS file included with
+this distribution.
+
+If you don't have access to any of the clients/bots or you use some other
+IRC client/bot which isn't supported, then it's fairly easy to add support
+for a new IRC log format. Take a look at the source code or send me an
+e-mail with the logfile format.
+
+SETTING UP PISG
+---------------
+It's quite simple to set up pisg. You have 2 choices:
+
+ * Set settings from commandline (try pisg.pl --help)
+ * Set settings from inside the script
+
+The most important this is the name of the logfile, the maintainers name,
+and then the format of your logfile.
+
+You cannot set everything up on the commandline, only the important things.
+
+The 'users.cfg' file control a lot of things, like connecting nicks
+together, ignoring nicks in the stats, adding user pictures etc. There is
+more documentation in the users.cfg file.
+
+NOTE: The stats will look best with a logfile which is a least a day-long.
+Some stats (like smilies, exclamation marks etc) doesn't get counted before
+a special amount of time.
+
+Also, pisg supports multiple languages so you're text will be in your own
+language; English, German, Danish and French is supported at the moment and
+can be set with the $lang variable in the script.
+
+You're welcome to translate pisg to your own language, it's not very hard,
+look in the bottom of pisg.pl!
+
+RUNNING PISG
+------------
+If you have setup everything inside the script, then you just need to run
+it. If you're on a Linux/Unix system, these steps should do the work:
+
+    $ chmod +x pisg.pl
+    $ ./pisg.pl
+
+If you have more than one channel, then you can also tell it to pisg on the
+commandline, the syntax is explained when doing:
+
+    $ ./pisg.pl --help
+
+For example the following would work:
+
+    $ ./pisg.pl -c \#channel -l logfile.log -f mIRC -o index.html
+    
+Try it out.
+
+Setting settings on commandline, will override the relevant settings in the
+script.
+
+CONTACT INFORMATION
+-------------------
+If you have any problems with setting up or running this script, please
+don't hesitate to contact me. I can either be via e-mail (morten@wtf.dk) or
+via ICQ (6681780).
+
+The pisg homepage is located at http://www.wtf.dk/hp/index.php?page=pisg.
+
+If you use this script, and have a permanent URL for the location of your
+generated stats, I would be delighted if you would send the url to me.  Just
+to see how many people uses it, and so I can link to it from my pisg page.
+
+Have fun :)
+
+ - Morten 'LostStar' Brix Pedersen <morten@wtf.dk>

BIN
gfx/pipe-blue.png


BIN
gfx/pipe-purple.png


+ 2063 - 0
pisg.pl

@@ -0,0 +1,2063 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Long;
+
+# pisg - Perl IRC Statistics Generator
+#
+# Copyright (C) 2001  <Morten 'LostStar' Brix Pedersen> - morten@wtf.dk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+my ($channel, $logfile, $format, $network, $outputfile, $maintainer,
+$pagehead, $usersfile, $imagepath, $logdir, $lang, $bgcolor, $text, $hbgcolor,
+$hcolor, $hicell, $hicell2, $tdcolor, $tdtop, $link, $vlink, $hlink, $headline,
+$rankc, $minquote, $maxquote, $activenicks, $activenicks2, $topichistory,
+$nicktracking, $timeoffset, $version, $debug, $debugfile);
+
+# Values that _MUST_ be set below (unless you pass them on commandline)
+$channel = "#channel";		# The name of your channel.
+$logfile = "channel.log";	# The exact filename of the logfile
+$format = "mIRC";		# logfile format. see FORMATS file
+$network = "SomeIRCNet";	# Network the channels is using.
+$outputfile = "index.html";	# The name of the html file to be generated
+$maintainer = "MAINTAINER";	# The maintainer or bot which makes the logfile
+$pagehead = "none";		# Some 'page header' file which you want to
+				# include in top of the stats
+
+$usersfile = "users.cfg";	# Path to users config file (aliases, ignores,
+				# pics and more, see users.cfg for examples)
+
+$imagepath = "";		# If your user pictures is located
+				# some special directory, set the path here.
+
+$logdir = "";			# If you specify a path to a dir here, then
+				# pisg will take that dir, and parse ALL
+				# logfiles in it, and create 1 HTML file
+				# from it
+
+$lang = 'EN';			# Language to use, EN | DE | DK | FR
+
+# Here you can set the colors for your stats page..
+$bgcolor = "#dedeee";		# Background color of the page
+$text = "black";		# Normal text color
+$hbgcolor = "#666699";		# Background color in headlines
+$hcolor = "white";		# Text color in headline
+$hicell = "#BABADD";		# Background color in highlighted cells
+$hicell2 = "#CCCCCC";		# Background color in highlighted cells
+$tdcolor = "black";		# Color of text in tables
+$tdtop = "#C8C8DD";		# Top color in some tables.
+$link = "#0b407a";		# Color of links
+$vlink = "#0b407a";		# Color of visited links
+$hlink = "#0b407a";		# Color of hovered links
+$headline = "#000000";		# Border color of headlines
+$rankc = "#CCCCCC";             # Colors of 'ranks' (1,2,3,4)
+
+# Other things that you might set, but not everyone cares about them
+$minquote = "25";		# Minimal value of letters for a random quote
+$maxquote = "65";		# Maximum value of letters for a random quote
+$activenicks = "25";		# Number of nicks to show in the 'top 25'
+$activenicks2 = "30";		# Nicks to show in 'these didnt make it...'
+$topichistory = "3";		# How many topics to show in 'latest topics'
+$nicktracking = 0;		# Track nickchanges and create aliases (can
+				# be slow, so it's disabled by default)
+
+$timeoffset = "+0";		# A time offset on the stats page - if your
+				# country has a different timezone than the
+				# machine where the stats are being
+				# generated, then for example do +1
+				# to add 1 hour to the time
+
+# You shouldn't care about anything below this point
+$debug = 0;			# 0 = Debugging off, 1 = Debugging on
+$debugfile = "debug.log";	# Path to debug file(must be set if $debug == 1)
+$version = "v0.16pre5";
+
+my ($lines, $kicked, $gotkicked, $smile, $longlines, $time, $timestamp, %alias,
+$normalline, $actionline, $thirdline, @ignore, $line, $processtime, @topics,
+%monologue, %kicked, %gotkick, %line, %length, %qpercent, %lpercent, %sadface,
+%smile, $nicks, %longlines, %mono, %times, %question, %loud, $totallength,
+%gaveop, %tookop, %joins, %actions, %sayings, %wordcount, %lastused,
+$colorcode, $boldcode, $underlinecode, %gotban, %setban, %foul, $days,
+$oldtime, $lastline, $actions, $normals, %userpics, %userlinks, %T,
+$repeated, $lastnormal);
+
+sub main
+{
+    init_pisg();        # Init commandline arguments and other things
+    init_lineformats(); # Attempt to set line formats in compliance with user specification (--format)
+
+    init_users_config();        # Init users config. (Aliases, ignores etc.)
+    init_debug();       # Init the debugging file
+
+    if ($logdir) {
+        parse_dir();            # Run through all logfiles in dir
+    } else {
+        parse_file($logfile);   # Run through the whole logfile
+    }
+
+    create_html();      # Create the HTML
+                        # (look here if you want to remove some of the
+                        # stats which you don't care about)
+
+    close_debug();      # Close the debugging file
+
+    print "\nFile was parsed succesfully in $processtime on $time.\n";
+}
+
+sub init_pisg
+{
+    print "pisg $version - Perl IRC Statistics Generator\n\n";
+
+    get_cmdlineoptions();
+
+    $timestamp = time;
+
+    if ($timeoffset =~ /\+(\d)/) {
+        # We must plus some hours to the time
+        $timestamp += 3600 * $1; # 3600 seconds per hour
+
+    } elsif ($timeoffset =~ /-(\d)/) {
+        # We must remove some hours from the time
+        $timestamp -= 3600 * $1; # 3600 seconds per hour
+    }
+
+    # Set useful values.
+    $days = 1;
+    $oldtime = "00";
+    $lastline = "";
+    $actions = "0";
+    $normals = "0";
+    $colorcode = chr(3);
+    $boldcode = chr(2);
+    $underlinecode = chr(31);
+    $time = localtime($timestamp);
+    $repeated = 0;
+    $lastnormal = "";
+
+    print "Statistics for channel $channel \@ $network by $maintainer\n\n";
+    print "Using language template: $lang\n\n" if ($lang ne 'EN');
+
+}
+
+sub init_lineformats {
+
+    # These are the regular expressions which matches the lines in the logfile,
+    # and looks different if it's xchat, mIRC or whatever.
+    # If you want to add support for a new format - you first have to add the
+    # regex here, and then you also have to modify the parse subroutines called
+    # 'parse_normalline()', 'parse_actionline()' and 'parse_thirdline()'
+
+    if ($format eq 'xchat') {
+        $normalline = '^(\d+):\d+:\d+ <([^>]+)>\s+(.*)';
+        $actionline = '^(\d+):\d+:\d+ \*\s+(\S+) (.*)';
+        $thirdline = '^(\d+):(\d+):\d+ .--\s+(\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (.*)';
+    } elsif ($format eq 'mIRC') {
+        $normalline = '^\[(\d+):\d+\] <([^>]+)> (.*)';
+        $actionline = '^\[(\d+):\d+\] \* (\S+) (.*)';
+        $thirdline = '^\[(\d+):(\d+)\] \*\*\* (\S+) (\S+) (\S+) (\S+) (\S+)(.*)';
+    } elsif ($format eq 'eggdrop') {
+        $normalline = '^\[(\d+):\d+\] <([^>]+)> (.*)';
+        $actionline = '^\[(\d+):\d+\] Action: (\S+) (.*)';
+        $thirdline = '^\[(\d+):(\d+)\] (\S+) (\S+) (\S+) (\S+)(.*)';
+    } elsif ($format eq 'bxlog') {
+        $normalline = '^\[\d+ \w+\/(\d+):\d+\] <([^>]+)> (.*)';
+        $actionline = '^\[\d+ \w+\/(\d+):\d+\] \* (\S+) (.*)';
+        $thirdline = '^\[\d+ \w+\/(\d+):(\d+)\] \S (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) (.*)';
+    } elsif ($format eq 'grufti') {
+        $normalline = '^\[(\d+):\d+\] <([^>]+)> (.*)';
+        $actionline = '^\[(\d+):\d+\] \* (\S+) (.*)';
+        $thirdline = '^\[(\d+):(\d+)\] (\S+) (\S+) (\S+) (\S+) (\S+) (\S+)(.*)';
+    } else {
+        die("Logfile format not supported, check \$format setting.\n");
+    }
+
+}
+
+sub init_users_config
+{
+
+    if (open(USERS, $usersfile)) {
+
+        my $lineno = 0;
+        while (<USERS>)
+        {
+            $lineno++;
+            my $line = $_;
+            next if /^#/;
+
+            if ($line =~ /<user.*>/) {
+                my $nick;
+
+                if ($line =~ /nick="([^"]+)"/) {
+                    $nick = $1;
+                } else {
+                    print STDERR "Warning: no nick specified in $usersfile on line $lineno\n";
+                    next;
+                }
+
+                if ($line =~ /alias="([^"]+)"/) {
+                    my @thisalias = split(/\s+/, $1);
+                    push(@{ $alias{$nick} }, @thisalias);
+                }
+
+                if ($line =~ /pic="([^"]+)"/) {
+                    $userpics{$nick} = $1;
+                }
+
+                if ($line =~ /link="([^"]+)"/) {
+                    $userlinks{$nick} = $1;
+                }
+
+                if ($line =~ /ignore="Y"/i) {
+                    push(@ignore, $nick);
+                }
+
+            }
+
+        }
+
+        close(USERS);
+    }
+
+}
+
+sub init_debug
+{
+    if ($debug) {
+        print "[ Debugging => $debugfile ]\n";
+        open(DEBUG,"> $debugfile") or print STDERR "$0: Unable to open debug
+        file($debugfile): $!\n";
+        debug("*** pisg debug file for $logfile\n");
+
+    }
+}
+
+sub parse_dir
+{
+    print "Going into $logdir and parsing all files there...\n\n";
+    my $files = `ls $logdir`;
+
+    my @filesarray = split(/\n/, $files);
+
+    # Add trailing slash when it's not there..
+    if (substr($logdir, -1) ne '/') {
+        $logdir =~ s/(.*)/$1\//;
+    }
+
+    foreach my $file (@filesarray) {
+        $file = $logdir . $file;
+        parse_file($file);
+    }
+
+}
+
+sub parse_file
+{
+    my $file = shift;
+
+    # This parses the file..
+    print "Analyzing log($file) in '$format' format...\n";
+
+    open (LOGFILE, $file) or die("$0: Unable to open logfile($file): $!\n");
+
+    while($line = <LOGFILE>) {
+        $lines++; # Increment number of lines.
+
+        # Strip mIRC color codes
+        $line =~ s/$colorcode\d{1,2},\d{1,2}//g;
+        $line =~ s/$colorcode\d{0,2}//g;
+        # Strip mIRC bold and underline codes
+        $line =~ s/[$boldcode$underlinecode]//g;
+
+        my $hashref;
+
+        # Match normal lines.
+        if ($hashref = parse_normalline($line)) {
+
+            my ($hour, $nick, $saying, $i);
+
+            for ($i = 0; $i <= $repeated; $i++) {
+
+                if ($i > 0) {
+                    $hashref = parse_normalline($lastnormal);
+                    $lines++; #Increment number of lines for repeated lines
+                }
+
+
+                $hour = $hashref->{hour};
+                $nick = find_alias($hashref->{nick});
+                $saying = $hashref->{saying};
+
+                # Timestamp collecting
+                $times{$hour}++;
+
+                unless (grep /^\Q$nick\E$/i, @ignore) {
+                    $normals++;
+                    $line{$nick}++;
+
+                    # Count up monologues
+
+                    if ($lastline eq $nick) {
+                        $mono{$nick}++;
+
+                        if ($mono{$nick} == "5") {
+                            $monologue{$nick}++;
+                            $mono{$nick} = 0;
+                        }
+                    } else {
+                        $mono{$nick} = 0;
+                    }
+
+                    $lastline = $nick;
+
+                    my $l = length($saying);
+
+                    if ($l > $minquote && $l < $maxquote) {
+                        $saying = htmlentities($saying);
+
+                        # Creates $hash{nick}[n] - a hash of an array.
+                        push (@{ $sayings{$nick} }, $saying);
+                        $longlines{$nick}++;
+                    }
+
+                    $question{$nick}++
+                        if ($saying =~ /\?/);
+
+                    $loud{$nick}++
+                        if ($saying =~ /!/);
+
+                    $foul{$nick}++
+                        if ($saying =~ /ass|fuck|bitch|shit|scheisse|scheiße|kacke|arsch|ficker|ficken|schlampe/);
+
+                    # Who smiles the most?
+                    # A regex matching al lot of smilies
+
+                    $smile{$nick}++
+                        if ($saying =~ /[8;:=][ ^-o]?[)pPD}\]>]/);
+                    $sadface{$nick}++
+                        if ($saying =~ /[8;:=][ ^-]?[\(\[\\\/{]/);
+
+                    # Don't count http:// as a :/ face
+                    $sadface{$nick}--
+                        if ($saying =~ /\w+:\/\//);
+
+                    foreach my $word (split(/[\s,!?.:;)(]+/, $saying)) {
+                        # remove uninteresting words
+                        next unless (length($word) > 5);
+                        # ignore contractions
+                        next if ($word =~ m/'..?$/);
+
+                        $wordcount{$word}++ unless (grep /^\Q$word\E$/i, @ignore);
+                        $lastused{$word} = $nick;
+                    }
+
+
+                    $length{$nick} += $l;
+                    $totallength += $l;
+                }
+            }
+            $lastnormal = $line;
+            $repeated = 0;
+        }
+
+        # Match action lines.
+        elsif ($hashref = parse_actionline($line)) {
+
+            my ($hour, $nick, $saying);
+
+            $hour = $hashref->{hour};
+            $nick = find_alias($hashref->{nick});
+            $saying = $hashref->{saying};
+
+            # Timestamp collecting
+            $times{$hour}++;
+
+            unless (grep /^\Q$nick\E$/i, @ignore) {
+                $actions++;
+                $line{$nick}++;
+
+                my $len = length($saying);
+                $length{$nick} += $len;
+                $totallength += $len;
+            }
+        }
+
+        # Match *** lines.
+        elsif ($hashref = parse_thirdline($line)) {
+
+            my ($hour, $min, $nick, $kicker, $newtopic, $newmode, $newjoin, $newnick);
+
+            $hour = $hashref->{hour};
+            $min = $hashref->{min};
+            $nick = find_alias($hashref->{nick});
+            $kicker = $hashref->{kicker};
+            $newtopic = $hashref->{newtopic};
+            $newmode = $hashref->{newmode};
+            $newjoin = $hashref->{newjoin};
+            $newnick = $hashref->{newnick};
+
+            # Timestamp collecting
+            $times{$hour}++;
+
+            unless (grep /^\Q$nick\E$/i, @ignore) {
+
+                if (defined($kicker)) {
+                    unless (grep /^\Q$kicker\E$/i, @ignore) {
+                        $gotkick{$nick}++;
+                        $kicked{$kicker}++;
+                    }
+                } elsif (defined($newtopic)) {
+                    my $tcount = @topics;
+
+                    $topics[$tcount]{topic} = htmlentities($newtopic);
+                    $topics[$tcount]{nick} = $nick;
+                    $topics[$tcount]{hour} = $hour;
+                    $topics[$tcount]{min} = $min;
+
+                    # Strip off the quotes (')
+                    $topics[$tcount]{topic} =~ s/^\'(.*)\'$/$1/;
+                } elsif (defined($newmode)) {
+                    my @opchange = opchanges($newmode);
+                    unless (exists $gaveop{$nick}) { $gaveop{$nick} = 0 }
+                    $gaveop{$nick} += $opchange[0] if $opchange[0];
+                    $tookop{$nick} += $opchange[1] if $opchange[1];
+                } elsif (defined($newjoin)) {
+                    $joins{$nick}++;
+                } elsif (defined($newnick) && ($nicktracking == 1)) {
+                    if (find_alias($newnick) eq $newnick) {
+                        if (defined($alias{$nick}) && !defined($alias{$newnick})) {
+                            push (@{$alias{$nick}}, $newnick);
+                        } elsif (defined($alias{$newnick}) && !defined($alias{$nick})) {
+                            push (@{$alias{$newnick}}, $nick);
+                        } elsif ($nick =~ /Guest/) {
+                            push (@{$alias{$newnick}}, ($newnick, $nick));
+                        } else {
+                            push (@{$alias{$nick}}, ($nick, $newnick));
+                        }
+                    }
+                }
+
+            }
+
+            if ($hour < $oldtime) { $days++ }
+            $oldtime = $hour;
+
+        }
+    }
+
+    close(LOGFILE);
+
+    my ($sec,$min,$hour) = gmtime(time - $^T);
+    $processtime = sprintf("%02d hours, %02d minutes and %02d seconds", $hour,  $min, $sec);
+
+    $nicks = scalar keys %line;
+
+    print "Finished analyzing log, $days days total.\n";
+}
+
+
+# Here is the 3 parse sub routines, their function is to return a hash with
+# the elements in the line. This is where we add the format dependent stuff.
+sub parse_normalline
+{
+    # Parse a normal line - returns a hash with 'hour', 'nick' and 'saying'
+    my $line = shift;
+    my %hash;
+
+    if ($line =~ /$normalline/) {
+        debug("[$lines] Normal: $1 $2 $3");
+
+        if (($format eq 'mIRC') || ($format eq 'xchat') || ($format eq
+        'eggdrop') || ($format eq 'bxlog') || ($format eq 'grufti')) {
+
+            $hash{hour} = $1;
+            $hash{nick} = $2;
+            $hash{saying} = $3;
+
+        } else {
+            die("Format not supported?!\n");
+        }
+
+        return \%hash;
+
+    } else {
+        return;
+    }
+}
+
+sub parse_actionline
+{
+    # Parse an action line - returns a hash with 'hour', 'nick' and 'saying'
+    my $line = shift;
+    my %hash;
+
+    if ($line =~ /$actionline/) {
+        debug("[$lines] Action: $1 $2 $3");
+
+        if (($format eq 'mIRC') || ($format eq 'xchat') || ($format eq
+        'eggdrop') || $format eq 'bxlog' || ($format eq 'grufti')) {
+
+            $hash{hour} = $1;
+            $hash{nick} = $2;
+            $hash{saying} = $3;
+
+        } else {
+            die("Format not supported?!\n");
+        }
+
+        return \%hash;
+
+    } else {
+        return;
+    }
+}
+
+sub parse_thirdline
+{
+    # Parses the 'third' line - (the third line is everything else, like
+    # topic changes, mode changes, kicks, etc.)
+    # parse_thirdline() have to return a hash with the following keys, for
+    # every format:
+    #   hour            - the hour we're in (for timestamp loggin)
+    #   min             - the minute we're in (for timestamp loggin)
+    #   nick            - the nick
+    #   kicker          - the nick which were kicked (if any)
+    #   newtopic        - the new topic (if any)
+    #   newmode         - a deop or an op, must be '+o' or '-o'
+    #   newjoin         - a new nick which has joined the channel
+    #   newnick         - a person has changed nick and this is the new nick
+    my $line = shift;
+    my %hash;
+
+    if ($line =~ /$thirdline/) {
+        if ($7) {
+          debug("[$lines] ***: $1 $2 $3 $4 $5 $6 $7");
+        } else {
+          debug("[$lines] ***: $1 $2 $3 $4 $5 $6");
+        }
+
+        if ($format eq 'mIRC') {
+            $hash{hour} = $1;
+            $hash{min} = $2;
+            $hash{nick} = $3;
+
+            if (($4.$5) eq 'waskicked') {
+                $hash{kicker} = $7;
+
+            } elsif (($4.$5) eq 'changestopic') {
+                $hash{newtopic} = "$7 $8";
+
+            } elsif (($4.$5) eq 'setsmode:') {
+                $hash{newmode} = $6;
+
+            } elsif (($4.$5) eq 'hasjoined') {
+                $hash{newjoin} = $3;
+
+            } elsif (($4.$5) eq 'nowknown') {
+                $hash{newnick} = $8;
+            }
+
+
+        } elsif ($format eq 'xchat') {
+            $hash{hour} = $1;
+            $hash{min} = $2;
+            $hash{nick} = $3;
+
+            if (($4.$5) eq 'haskicked') {
+                $hash{kicker} = $3;
+                $hash{nick} = $6;
+
+            } elsif (($4.$5) eq 'haschanged') {
+                $hash{newtopic} = $9;
+
+            } elsif (($4.$5) eq 'giveschannel') {
+                $hash{newmode} = '+o';
+
+            } elsif (($4.$5) eq 'removeschannel') {
+                $hash{newmode} = '-o';
+
+            } elsif (($5.$6) eq 'hasjoined') {
+                $hash{newjoin} = $1;
+
+            } elsif (($5.$6) eq 'nowknown') {
+                $hash{newnick} = $8;
+            }
+
+        } elsif ($format eq 'eggdrop') {
+            $hash{hour} = $1;
+            $hash{min} = $2;
+            $hash{nick} = $3;
+
+            if (($4.$5) eq 'kickedfrom') {
+                $7 =~ /^ by ([\S]+):.*/;
+                $hash{kicker} = $1;
+
+            } elsif ($3 eq 'Topic') {
+                $7 =~ /^ by ([\S]+)![\S]+: (.*)/;
+                $hash{nick} = $1;
+                $hash{newtopic} = $2;
+
+            } elsif (($4.$5) eq 'modechange') {
+                $hash{newmode} = $6;
+                $7 =~ /^ .+ by ([\S]+)!.*/;
+                $hash{nick} = $1;
+                $hash{newmode} =~ s/^\'//;
+
+            } elsif ($5 eq 'joined') {
+                $hash{newjoin} = $3;
+
+            } elsif (($3.$4) eq 'Nickchange:') {
+                $hash{nick} = $5;
+                $7 =~ /([\S]+)/;
+                $hash{newnick} = $1;
+
+            } elsif (($3.$4.$5) eq 'Lastmessagerepeated') {
+                $repeated = $6;
+            }
+
+        } elsif ($format eq 'bxlog') {
+            $hash{hour} = $1;
+            $hash{min} = $2;
+            $hash{nick} = $3;
+
+            if ($5 eq 'kicked') {
+                $hash{kicker} = $9;
+                $hash{kicker} =~ s/!.*$//;
+
+            } elsif ($3 eq 'Topic') {
+                $hash{nick} = substr($5, 0, -1);
+                $hash{newtopic} = "$6 $7 $8 $9 $10";
+
+            } elsif ($5 eq '[+o') {
+                $hash{newmode} = '+o';
+                $hash{newmode} = substr($6, 0, -1);
+
+            } elsif ($5 eq '[-o') {
+                $hash{newmode} = '-o';
+                $hash{newmode} = substr($6, 0, -1);
+
+            } elsif (($4.$5) eq 'hasjoined') {
+                $hash{newjoin} = $1;
+
+            } elsif (($4.$5) eq 'isknown') {
+                $hash{newnick} = $6;
+            }
+
+            $hash{nick} =~ s/!.*$//;
+
+        } elsif ($format eq 'grufti') {
+            $hash{hour} = $1;
+            $hash{min} = $2;
+            $hash{nick} = $3;
+
+            if ($5 eq 'kicked') {
+                $hash{kicker} = $3;
+                $hash{nick} = $6;
+
+            } elsif (($4.$5) eq 'haschanged') {
+                $hash{newtopic} = $9;
+
+            } elsif (($4.$5) eq 'modechange') {
+                $hash{newmode} = substr($6, 1);
+                $hash{nick} = substr($9, 1);
+
+            } elsif ($5 eq 'joined') {
+                $hash{newjoin} = $1;
+
+            } elsif (($3.$4) eq 'Nickchange') {
+                $hash{nick} = $7;
+                $hash{newnick} = $9;
+            }
+
+        } else {
+            die("Format not supported?!\n");
+        }
+
+        return \%hash;
+
+    } else {
+        return;
+    }
+}
+
+
+sub opchanges
+{
+    my @modes = split(//, $_[0]);
+    my ($mode,$plus)        = ("",0);
+    my ($gaveop,$tookop)    = (0,0);
+
+    foreach (@modes) {
+        if    (/^\+$/) { $plus  = 1; next; }
+        elsif (/^-$/)  { $plus  = 0; next; }
+        else  { next unless /^o$/ }
+
+        if ($plus) { $gaveop++ } else { $tookop++ }
+    }
+
+    my @out = ($gaveop,$tookop);
+    return @out;
+}
+
+
+sub htmlentities
+{
+    my $str = shift;
+
+    $str =~ s/\&/\&amp;/g;
+    $str =~ s/\</\&lt;/g;
+    $str =~ s/\>/\&gt;/g;
+
+    return $str;
+}
+
+sub html
+{
+    my $html = shift;
+    print OUTPUT $html . "\n";
+}
+
+sub template_text
+{
+    # This function is for the homemade template system. It receives a name
+    # of a template and a hash with the fields in the template to update to
+    # its corresponding value
+    my $template = shift;
+    my %hash = @_;
+
+    my $text;
+
+    if (!$T{$lang}{$template}) {
+        # Fall back to English if the language template doesn't exist
+        $text = $T{EN}{$template};
+    }
+
+    $text = $T{$lang}{$template};
+
+    if (!$text) {
+        die("No such template $template\n");
+    }
+
+    foreach my $key (sort keys %hash) {
+        $text =~ s/\[:$key\]/$hash{$key}/;
+        $text =~ s/ü/&uuml;/g;
+        $text =~ s/ö/&ouml;/g;
+        $text =~ s/ä/&auml;/g;
+        $text =~ s/ß/&szlig/g;
+    }
+
+    return $text;
+
+}
+
+
+sub replace_links
+{
+    # Sub to replace urls and e-mail addys to links
+    my $str = shift;
+    my $nick = shift;
+
+    if ($nick) {
+        $str =~ s/(http|https|ftp|telnet|news)(:\/\/[-a-zA-Z0-9_]+.[-a-zA-Z0-9.,_~=:;&@%?#\/+]+)/<a href="$1$2" target="_blank" title="Open in new window: $1$2">$nick<\/a>/g;
+        $str =~ s/([-a-zA-Z0-9._]+@[-a-zA-Z0-9_]+.[-a-zA-Z0-9._]+)/<a href="mailto:$1" title="Mail to $nick">$nick<\/a>/g;
+    } else {
+        $str =~ s/(http|https|ftp|telnet|news)(:\/\/[-a-zA-Z0-9_]+.[-a-zA-Z0-9.,_~=:;&@%?#\/+]+)/<a href="$1$2" target="_blank" title="Open in new window: $1$2">$1$2<\/a>/g;
+        $str =~ s/([-a-zA-Z0-9._]+@[-a-zA-Z0-9_]+.[-a-zA-Z0-9._]+)/<a href="mailto:$1" title="Mail to $1">$1<\/a>/g;
+    }
+
+
+    return $str;
+
+}
+
+sub debug
+{
+    if ($debug) {
+        my $debugline = $_[0] . "\n";
+        print DEBUG $debugline;
+    }
+}
+
+sub find_alias
+{
+    my $nick = shift;
+
+    foreach (keys %alias) {
+        return $_ if (grep /^\Q$nick\E$/i, @{$alias{$_}});
+    }
+
+    return $nick;
+}
+
+sub create_html
+{
+
+    # This is where all subroutines get executed, you can actually design
+    # your own layout here, the lines should be self-explainable
+
+    print "Now generating HTML($outputfile)...\n";
+
+    open (OUTPUT, "> $outputfile") or die("$0: Unable to open outputfile($outputfile): $!\n");
+
+    htmlheader();
+    pageheader();
+    activetimes();
+    activenicks();
+
+    headline(template_text('bignumtopic'));
+    html("<table width=\"614\">\n"); # Needed for sections
+    questions();
+    loudpeople();
+    mostsmiles();
+    mostsad();
+    longlines();
+    shortlines();
+    html("</table>"); # Needed for sections
+
+    mostusedword();
+
+    mostreferenced();
+
+    headline(template_text('othernumtopic'));
+    html("<table width=\"614\">\n"); # Needed for sections
+    gotkicks();
+    mostkicks();
+    mostop();
+    mostmonologues();
+    mostjoins();
+    mostfoul();
+    html("</table>"); # Needed for sections
+
+    headline(template_text('latesttopic'));
+    html("<table width=\"614\">\n"); # Needed for sections
+    lasttopics();
+    html("</table>"); # Needed for sections
+
+    my %hash = ( lines => $lines );
+    html(template_text('totallines', %hash) . "<br><br>");
+
+    htmlfooter();
+
+    close(OUTPUT);
+
+}
+
+
+sub activetimes
+{
+    # The most actives times on the channel
+
+    my (%output, $tbgcolor);
+
+    &headline(template_text('activetimestopic'));
+
+    my @toptime = sort { $times{$b} <=> $times{$a} } keys %times;
+
+    my $highest_value = $times{$toptime[0]};
+
+    my @now = localtime($timestamp);
+
+    my $image = "pipe-blue.png";
+
+    for my $hour (sort keys %times) {
+        debug("Time: $hour => ". $times{$hour});
+
+        if ($toptime[0] == $hour) {
+            $image = "pipe-purple.png";
+        } 
+
+        my $size = ($times{$hour} / $highest_value) * 100;
+        my $percent = ($times{$hour} / $lines) * 100;
+        $percent =~ s/(\.\d)\d+/$1/;
+
+        print STDERR "before: $hour\n";
+        if ($timeoffset =~ /\+(\d)/) {
+            # We must plus some hours to the time
+            $hour += $1;
+            $hour = $hour % 24;
+            print STDERR "hour: $hour\n";
+            if ($hour < 10) { $hour = "0" . $hour; }
+
+        } elsif ($timeoffset =~ /-(\d)/) {
+            # We must remove some hours from the time
+            $hour -= $1;
+            $hour = $hour % 24;
+            if ($hour < 10) { $hour = "0" . $hour; }
+        }
+
+        $output{$hour} = "<td align=\"center\" valign=\"bottom\" class=\"asmall\">$percent%<br><img src=\"$image\" width=\"15\" height=\"$size\" alt=\"$percent\"></td>\n";
+    }
+
+    html("<table border=\"0\" width=\"614\"><tr>\n");
+
+    for ($b = 0; $b < 24; $b++) {
+        if ($b < 10) { $a = "0" . $b; } else { $a = $b; }
+
+        if (!defined($output{$a}) || $output{$a} eq "") {
+            html("<td align=\"center\" valign=\"bottom\" class=\"asmall\">0%</td>");
+        } else {
+            html($output{$a});
+        }
+    }
+
+    html("</tr><tr>");
+
+    for ($b = 0; $b < 24; $b++) {
+        if ($now[2] == $b) { $tbgcolor = "\#AAAAAA"; } else { $tbgcolor = "\#CCCCCC"; }
+        html("<td bgcolor=\"$tbgcolor\" align=\"center\" class=\"small\">$b</td>");
+}
+
+    html("</tr></table>");
+}
+
+sub activenicks
+{
+    # The most active nicks (those who wrote most lines)
+
+    my $randomline;
+
+    &headline(template_text('activenickstopic'));
+
+    html("<table border=\"0\" width=\"614\"><tr>");
+    html("<td>&nbsp;</td><td bgcolor=\"$tdtop\"><b>" . template_text('nick') . "</b></td><td bgcolor=\"$tdtop\"><b>" . template_text('numberlines') ."</b></td><td bgcolor=\"$tdtop\"><b>". template_text('randquote') ."</b></td>");
+    if (%userpics) {
+        html("<td bgcolor=\"$tdtop\"><b>" . template_text('userpic') ."</b></td>");
+    }
+
+    html("</tr>");
+
+    my @active = sort { $line{$b} <=> $line{$a} } keys %line;
+
+    if ($activenicks > $nicks) {
+        $activenicks = $nicks;
+        print "Note: There was less nicks in the logfile than your specificied there to be in most active nicks...\n";
+    }
+
+    my ($nick, $visiblenick);
+    my $i = 1;
+    for (my $c = 0; $c < $activenicks; $c++) {
+        $nick = $active[$c];
+        $visiblenick = $active[$c];
+
+        if (!$longlines{$nick}) {
+            $randomline = "";
+        } else {
+            my $rand = rand($longlines{$nick});
+            $randomline = $sayings{$nick}[$rand];
+        }
+
+
+        # Convert URLs and e-mail addys to links
+        $randomline = replace_links($randomline);
+
+        # Add a link to the nick if there is any
+        if ($userlinks{$nick}) {
+            $visiblenick = replace_links($userlinks{$nick}, $nick);
+        }
+
+        my $h = $hicell;
+        $h =~ s/^#//;
+        $h = hex $h;
+        my $h2 = $hicell2;
+        $h2 =~ s/^#//;
+        $h2 = hex $h2;
+        my $f_b = $h & 0xff;
+        my $f_g = ($h & 0xff00) >> 8;
+        my $f_r = ($h & 0xff0000) >> 16;
+        my $t_b = $h2 & 0xff;
+        my $t_g = ($h2 & 0xff00) >> 8;
+        my $t_r = ($h2 & 0xff0000) >> 16;
+        my $col_b  = sprintf "%0.2x", abs int(((($t_b - $f_b) / $activenicks) * +$c) + $f_b);
+        my $col_g  = sprintf "%0.2x", abs int(((($t_g - $f_g) / $activenicks) * +$c) + $f_g);
+        my $col_r  = sprintf "%0.2x", abs int(((($t_r - $f_r) / $activenicks) * +$c) + $f_r);
+
+
+        html("<tr><td bgcolor=\"$rankc\" align=\"left\">");
+        my $line = $line{$nick};
+        html("$i</td><td bgcolor=\"#$col_r$col_g$col_b\">$visiblenick</td><td bgcolor=\"#$col_r$col_g$col_b\">$line</td><td bgcolor=\"#$col_r$col_g$col_b\">");
+        html("\"$randomline\"</td>");
+
+        if ($userpics{$nick}) {
+            html("<td bgcolor=\"#$col_r$col_g$col_b\" align=\"center\"><img valign=\"middle\" src=\"$imagepath$userpics{$nick}\"></td>");
+        }
+
+        html("</tr>");
+        $i++;
+    }
+
+    html("</table><br>");
+
+    # ALMOST AS ACTIVE NICKS
+
+    @active = sort { $line{$b} <=> $line{$a} } keys %line;
+
+    my $nickstoshow = $activenicks + $activenicks2;
+
+    unless ($nickstoshow > $nicks) {
+
+        html("<br><b><i>" . template_text('nottop') . "</i></b><table><tr>");
+        for (my $c = $activenicks; $c < $nickstoshow; $c++) {
+            unless ($c % 5) { unless ($c == $activenicks) { html("</tr><tr>"); } }
+            html("<td bgcolor=\"$rankc\" class=\"small\">");
+            my $nick = $active[$c];
+            my $lines = $line{$nick};
+            html("$nick ($lines)</td>");
+        }
+
+        html("</table>");
+    }
+
+
+}
+
+sub mostusedword
+{
+    # Lao the infamous word usage statistics
+    my %usages;
+
+    foreach my $word (sort keys %wordcount) {
+        next if exists $line{$word};
+        $usages{$word} = $wordcount{$word};
+    }
+
+
+    my @popular = sort { $usages{$b} <=> $usages{$a} } keys %usages;
+
+    if (@popular) {
+        &headline(template_text('mostwordstopic'));
+
+        html("<table border=\"0\" width=\"614\"><tr>");
+        html("<td>&nbsp;</td><td bgcolor=\"$tdtop\"><b>" . template_text('word') . "</b></td>");
+        html("<td bgcolor=\"$tdtop\"><b>" . template_text('numberuses') . "</b></td>");
+        html("<td bgcolor=\"$tdtop\"><b>" . template_text('lastused') . "</b></td>");
+
+
+        for(my $i = 0; $i < 10; $i++) {
+            last unless $i < $#popular;
+            my $a = $i + 1;
+            my $popular = $popular[$i];
+            my $wordcount = $wordcount{$popular[$i]};
+            my $lastused = $lastused{$popular[$i]};
+            html("<tr><td bgcolor=\"$rankc\"><b>$a</b>");
+            html("<td bgcolor=\"$hicell\">$popular</td>");
+            html("<td bgcolor=\"$hicell\">$wordcount</td>");
+            html("<td bgcolor=\"$hicell\">$lastused</td>");
+            html("</tr>");
+       }
+
+       html("</table>");
+   }
+
+}
+
+
+sub mostreferenced
+{
+    my %usages;
+
+    foreach my $word (sort keys %wordcount) {
+        next unless exists $line{$word};
+        $usages{$word} = $wordcount{$word};
+    }
+
+    my @popular = sort { $usages{$b} <=> $usages{$a} } keys %usages;
+
+    if (@popular) {
+
+        &headline(template_text('referencetopic'));
+
+        html("<table border=\"0\" width=\"614\"><tr>");
+        html("<td>&nbsp;</td><td bgcolor=\"$tdtop\"><b>" . template_text('nick') . "</b></td>");
+        html("<td bgcolor=\"$tdtop\"><b>" . template_text('numberuses') . "</b></td>");
+        html("<td bgcolor=\"$tdtop\"><b>" . template_text('lastused') . "</b></td>");
+
+       for(my $i = 0; $i < 5; $i++) {
+           last unless $i < $#popular;
+           my $a = $i + 1;
+           my $popular = $popular[$i];
+           my $wordcount = $wordcount{$popular[$i]};
+           my $lastused = $lastused{$popular[$i]};
+           html("<tr><td bgcolor=\"$rankc\"><b>$a</b>");
+           html("<td bgcolor=\"$hicell\">$popular</td>");
+           html("<td bgcolor=\"$hicell\">$wordcount</td>");
+           html("<td bgcolor=\"$hicell\">$lastused</td>");
+           html("</tr>");
+       }
+   html("</table>");
+   }
+
+}
+
+sub questions
+{
+    # Persons who asked the most questions
+
+    foreach my $nick (sort keys %question) {
+        if ($line{$nick} > 100) {
+            $qpercent{$nick} = ($question{$nick} / $line{$nick}) * 100;
+            $qpercent{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @question = sort { $qpercent{$b} <=> $qpercent{$a} } keys %qpercent;
+
+    if (@question) {
+        my %hash = (
+            nick => $question[0],
+            per => $qpercent{$question[0]}
+        );
+
+        my $text = template_text('question1', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text");
+        if (@question >= 2) {
+            my %hash = (
+                nick => $question[1],
+                per => $qpercent{$question[1]}
+            );
+
+            my $text = template_text('question2', %hash);
+            html("<br><span class=\"small\">$text</span>");
+        }
+        html("</td></tr>");
+
+    } else {
+        html("<tr><td bgcolor=\"$hicell\">" . template_text('question3') . "</td></tr>");
+    }
+}
+
+sub loudpeople
+{
+    # The ones who speak LOUDLY!
+
+    foreach my $nick (sort keys %loud) {
+        if ($line{$nick} > 100) {
+            $lpercent{$nick} = ($loud{$nick} / $line{$nick}) * 100;
+            $lpercent{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @loud = sort { $lpercent{$b} <=> $lpercent{$a} } keys %lpercent;
+
+    if (@loud) {
+        my %hash = (
+            nick => $loud[0],
+            per => $lpercent{$loud[0]}
+        );
+
+        my $text = template_text('loud1', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text");
+        if (@loud >= 2) {
+            my %hash = (
+                nick => $loud[1],
+                per => $lpercent{$loud[1]}
+            );
+
+            my $text = template_text('loud2', %hash);
+            html("<br><span class=\"small\">$text</span>");
+        }
+        html("</td></tr>");
+
+    } else {
+        my $text = template_text('loud3');
+        html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+    }
+
+}
+
+sub gotkicks
+{
+    # The persons who got kicked the most
+
+    my @gotkick = sort { $gotkick{$b} <=> $gotkick{$a} } keys %gotkick;
+
+    if (@gotkick) {
+        my %hash = (
+            nick => $gotkick[0],
+            kicks => $gotkick{$gotkick[0]}
+        );
+
+        my $text = template_text('gotkick1', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text");
+        if (@gotkick >= 2) {
+            my %hash = (
+                nick => $gotkick[1],
+                kicks => $gotkick{$gotkick[1]}
+            );
+
+            my $text = template_text('gotkick2', %hash);
+            html("<br><span class=\"small\">$text</span>");
+        }
+        html("</td></tr>");
+    }
+}
+
+sub mostjoins
+{
+
+    my @joins = sort { $joins{$b} <=> $joins{$a} } keys %joins;
+
+    if (@joins) {
+        my %hash = (
+            nick => $joins[0],
+            joins => $joins{$joins[0]}
+        );
+
+        my $text = template_text('joins', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+    }
+}
+
+
+sub mostkicks
+{
+     # The person who got kicked the most
+
+     my @kicked = sort { $kicked{$b} <=> $kicked{$a} } keys %kicked;
+
+     if (@kicked) {
+         my %hash = (
+             nick => $kicked[0],
+             kicked => $kicked{$kicked[0]}
+         );
+
+         my $text = template_text('kick1', %hash);
+         html("<tr><td bgcolor=\"$hicell\">$text");
+
+         if (@kicked >= 2) {
+         my %hash = (
+             oldnick => $kicked[0],
+             nick => $kicked[1],
+             kicked => $kicked{$kicked[1]}
+         );
+
+         my $text = template_text('kick2', %hash);
+             html("<br><span class=\"small\">$text</span>");
+         }
+         html("</td></tr>");
+     } else {
+         my $text = template_text('kick3');
+         html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+     }
+
+}
+
+sub mostmonologues
+{
+    # The person who had the most monologues (speaking to himself)
+
+     my @monologue = sort { $monologue{$b} <=> $monologue{$a} } keys %monologue;
+
+     if (@monologue) {
+         my %hash = (
+             nick => $monologue[0],
+             monos => $monologue{$monologue[0]}
+         );
+
+         my $text = template_text('mono1', %hash);
+
+         html("<tr><td bgcolor=\"$hicell\">$text");
+         if (@monologue >= 2) {
+             my %hash = (
+                 nick => $monologue[1],
+                 monos => $monologue{$monologue[1]}
+             );
+
+             my $text = template_text('mono2', %hash);
+             html("<br><span class=\"small\">$text</span>");
+         }
+         html("</td></tr>");
+     }
+}
+
+sub longlines
+{
+
+    my %len;
+
+    # The person(s) who wrote the longest lines
+
+    foreach my $nick (sort keys %length) {
+        if ($line{$nick} > 100) {
+            $len{$nick} = $length{$nick} / $line{$nick};
+            $len{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @len = sort { $len{$b} <=> $len{$a} } keys %len;
+
+    my $all_lines = $normals + $actions;
+
+    my $totalaverage;
+
+    if ($all_lines > 0) {
+        $totalaverage = $totallength / $all_lines;
+        $totalaverage =~ s/(\.\d)\d+/$1/;
+    }
+
+    if (@len) {
+        my %hash = (
+            nick => $len[0],
+            letters => $len{$len[0]}
+        );
+
+        my $text = template_text('long1', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text<br>");
+
+        if (@len >= 2) {
+            %hash = (
+                channel => $channel,
+                avg => $totalaverage
+            );
+
+            $text = template_text('long2', %hash);
+            html("<span class=\"small\">$text</span></td></tr>");
+        }
+    }
+}
+
+sub shortlines
+{
+    # This sub should be combined with the longlines sub at some point.. it
+    # does basically the same thing.
+
+    my %len;
+
+    # The person(s) who wrote the shortest lines
+
+    foreach my $nick (sort keys %length) {
+        if ($line{$nick} > 5) {
+            $len{$nick} = $length{$nick} / $line{$nick};
+            $len{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @len = sort { $len{$a} <=> $len{$b} } keys %len;
+
+    if (@len) {
+        my %hash = (
+            nick => $len[0],
+            letters => $len{$len[0]}
+        );
+
+        my $text = template_text('short1', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text<br>");
+
+        if (@len >= 2) {
+            %hash = (
+                nick => $len[1],
+                letters => $len{$len[1]}
+            );
+
+            $text = template_text('short2', %hash);
+            html("<span class=\"small\">$text</span></td></tr>");
+        }
+    }
+
+}
+
+sub mostfoul
+{
+    my %spercent;
+
+    foreach my $nick (sort keys %foul) {
+        if ($line{$nick} > 15) {
+            $spercent{$nick} = ($foul{$nick} / $line{$nick}) * 100;
+            $spercent{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @foul = sort { $spercent{$b} <=> $spercent{$a} } keys %spercent;
+
+
+    if (@foul) {
+
+        my %hash = (
+            nick => $foul[0],
+            per => $spercent{$foul[0]}
+        );
+
+        my $text = template_text('foul1', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text");
+
+        if (@foul >= 2) {
+            my %hash = (
+                nick => $foul[1],
+                per => $spercent{$foul[1]}
+            );
+
+            my $text = template_text('foul2', %hash);
+            html("<br><span class=\"small\">$text</span>");
+        }
+
+       html("</td></tr>");
+    } else {
+        my %hash = (
+            channel => $channel
+        );
+
+        my $text = template_text('foul3', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+    }
+}
+
+
+sub mostsad
+{
+    my %spercent;
+
+    foreach my $nick (sort keys %sadface) {
+        if ($line{$nick} > 100) {
+            $spercent{$nick} = ($sadface{$nick} / $line{$nick}) * 100;
+            $spercent{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @sadface = sort { $spercent{$b} <=> $spercent{$a} } keys %spercent;
+
+
+    if (@sadface) {
+        my %hash = (
+            nick => $sadface[0],
+            per => $spercent{$sadface[0]}
+        );
+
+        my $text = template_text('sad1', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text");
+
+        if (@sadface >= 2) {
+            my %hash = (
+                nick => $sadface[1],
+                per => $spercent{$sadface[1]}
+            );
+
+            my $text = template_text('sad2', %hash);
+
+            html("<br><span class=\"small\">$text</span>");
+        }
+        html("</td></tr>");
+    } else {
+        my %hash = (
+            channel => $channel
+        );
+        my $text = template_text('sad3', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+    }
+}
+
+
+sub mostop
+{
+    my @ops = sort { $gaveop{$b} <=> $gaveop{$a} } keys %gaveop;
+    my @deops = sort { $tookop{$b} <=> $tookop{$a} } keys %tookop;
+
+    if (@ops) {
+        my %hash = (
+            nick => $ops[0],
+            ops => $gaveop{$ops[0]}
+        );
+
+        my $text = template_text('mostop1', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text");
+
+        if (@ops >= 2) {
+            my %hash = (
+                nick => $ops[1],
+                ops => $gaveop{$ops[1]}
+            );
+
+            my $text = template_text('mostop2', %hash);
+            html("<br><span class=\"small\">$text</span>");
+        }
+        html("</td></tr>");
+    } else {
+        my %hash = ( channel => $channel );
+        my $text = template_text('mostop3', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+    }
+    if (@deops) {
+        my %hash = (
+            channel => $channel,
+            nick => $deops[0],
+            deops => $tookop{$deops[0]}
+        );
+        my $text = template_text('mostdeop1', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text");
+
+        if (@deops >= 2) {
+            my %hash = (
+                nick => $deops[1],
+                deops => $tookop{$deops[1]}
+            );
+            my $text = template_text('mostdeop2', %hash);
+
+            html("<br><span class=\"small\">$text</span>");
+        }
+            html("</td></tr>");
+    } else {
+        my %hash = ( channel => $channel );
+        my $text = template_text('mostdeop3', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text");
+    }
+}
+
+sub mostsmiles
+{
+    # The person(s) who smiled the most :-)
+
+    my %spercent;
+
+    foreach my $nick (sort keys %smile) {
+        if ($line{$nick} > 100) {
+            $spercent{$nick} = ($smile{$nick} / $line{$nick}) * 100;
+            $spercent{$nick} =~ s/(\.\d)\d+/$1/;
+        }
+    }
+
+    my @smiles = sort { $spercent{$b} <=> $spercent{$a} } keys %spercent;
+
+
+    if (@smiles) {
+        my %hash = (
+            nick => $smiles[0],
+            per => $spercent{$smiles[0]}
+        );
+
+        my $text = template_text('smiles1', %hash);
+
+        html("<tr><td bgcolor=\"$hicell\">$text");
+        if (@smiles >= 2) {
+            my %hash = (
+                nick => $smiles[1],
+                per => $spercent{$smiles[1]}
+            );
+
+            my $text = template_text('smiles2', %hash);
+            html("<br><span class=\"small\">$text</span>");
+        }
+        html("</td></tr>");
+
+    } else {
+        my %hash = (
+            channel => $channel
+        );
+
+        my $text = template_text('smiles3', %hash);
+        html("<tr><td bgcolor=\"$hicell\">$text</td></tr>");
+    }
+}
+
+sub lasttopics
+{
+    debug("Total number of topics: ". scalar @topics);
+
+    if (@topics) {
+        my $ltopic = @topics - 1;
+        my $tlimit = 0;
+
+        $topichistory -= 1;
+
+
+        if ($ltopic > $topichistory) { $tlimit = $ltopic - $topichistory; }
+
+        for (my $i = $ltopic; $i >= $tlimit; $i--) {
+            $topics[$i]{"topic"} = replace_links($topics[$i]{"topic"});
+            my $topic = $topics[$i]{topic};
+            my $nick = $topics[$i]{nick};
+            my $hour = $topics[$i]{hour};
+            my $min = $topics[$i]{min};
+            html("<tr><td bgcolor=\"$hicell\"><i>$topic</i></td>");
+            html("<td bgcolor=\"$hicell\">By <b>$nick</b> at <b>$hour:$min</b></td></tr>");
+        }
+       } else {
+            html("<tr><td bgcolor=\"$hicell\">" . template_text('notopic') ."</td></tr>");
+       }
+}
+
+
+# Some HTML subs
+sub htmlheader
+{
+print OUTPUT <<HTML;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>$channel @ $network channel statistics</title>
+<style type="text/css">
+a { text-decoration: none }
+a:link { color: $link; }
+a:visited { color: $vlink; }
+a:hover { text-decoration: underline; color: $hlink }
+
+body {
+    background-color: $bgcolor;
+    font-family: verdana, arial, sans-serif;
+    font-size: 13px;
+    color: $text;
+}
+
+td {
+    font-family: verdana, arial, sans-serif;
+    font-size: 13px;
+    color: $tdcolor;
+}
+
+.title {
+    font-family: tahoma, arial, sans-serif;
+    font-size: 16px;
+    font-weight: bold;
+}
+
+.headline { color: $hcolor; }
+.small { font-family: verdana, arial, sans-serif; font-size: 10px; }
+.asmall { font-family: arial narrow, sans-serif; font-size: 10px }
+</style></head>
+<body>
+<div align="center">
+HTML
+my %hash = (
+    channel => $channel,
+    network => $network,
+    maintainer => $maintainer,
+    time => $time,
+    days => $days,
+    nicks => $nicks,
+    channel => $channel
+);
+print OUTPUT "<span class=\"title\">" . template_text('pagetitle1', %hash) . "</span><br>";
+print OUTPUT "<br>" . template_text('pagetitle2', %hash) . "<br>";
+print OUTPUT template_text('pagetitle3', %hash) . "<br><br>";
+
+}
+
+sub htmlfooter
+{
+print OUTPUT <<HTML;
+<span class="small">
+Stats generated by <a href="http://www.wtf.dk/hp/index.php?page=pisg" title="Go to the pisg homepage">pisg</a> $version<br>
+pisg by <a href="http://www.wtf.dk/hp/" title="Go to the authors homepage">Morten "LostStar" Brix Pedersen</a> and others<br>
+Stats generated in $processtime
+</span>
+</div>
+</body>
+</html>
+HTML
+}
+
+sub headline
+{
+    my ($title) = @_;
+print OUTPUT <<HTML;
+   <br>
+   <table width="610" cellpadding="1" cellspacing="0" border="0">
+    <tr>
+     <td bgcolor="$headline">
+      <table width="100%" cellpadding="2" cellspacing="0" border="0" align="center">
+       <tr>
+        <td bgcolor="$hbgcolor" class="text10">
+         <div align="center" class="headline"><b>$title</b></div>
+        </td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </table>
+HTML
+}
+
+sub pageheader
+{
+    if ($pagehead ne 'none') {
+        open(PAGEHEAD, $pagehead) or die("$0: Unable to open $pagehead for reading: $!\n");
+        while (<PAGEHEAD>) {
+            html($_);
+        }
+    }
+}
+
+sub close_debug
+{
+    if ($debug) {
+        close(DEBUG) or print STDERR "$0: Cannot close debugfile($debugfile): $!\n";
+    }
+}
+
+sub get_cmdlineoptions
+{
+    my $tmp;
+    # Commandline options
+    my $help;
+
+my $usage = <<END_USAGE;
+Usage: pisg.pl [-c channel] [-l logfile] [-o outputfile] [-m
+maintainer]  [-f format] [-n network] [-d logdir] [-a aliasfile]
+[-i ignorefile] [-h]
+
+-c --channel=xxx       : Set channel name
+-l --logfile=xxx       : Log file to parse
+-o --outfile=xxx       : Name of html file to create
+-m --maintainer=xxx    : Channel/statistics maintainer
+-f --format=xxx        : Logfile format [see FORMATS file]
+-n --network=xxx       : IRC Network this channel is on.
+-d --dir=xxx           : Analyze all files in this dir. Ignores logfile.
+-u --usersfile=xxx     : Users config file
+-h --help              : Output this message and exit (-? also works).
+
+Example:
+
+ \$ pisg.pl -n IRCnet -f xchat -o suid.html -c \\#channel -l logfile.log
+
+As always, all options may also be defined by editing the source and calling
+pisg without arguments.
+
+END_USAGE
+
+    if (GetOptions('channel=s'    => \$channel,
+                   'logfile=s'    => \$logfile,
+                   'format=s'     => \$format,
+                   'network=s'    => \$network,
+                   'maintainer=s' => \$maintainer,
+                   'outfile=s'    => \$outputfile,
+                   'dir=s'        => \$logdir,
+                   'ignorefile=s' => \$tmp,
+                   'aliasfile=s'  => \$tmp,
+                   'usersfile=s'  => \$usersfile,
+                   'help|?'       => \$help
+               ) == 0 or $help) {
+                   die($usage);
+               }
+
+    if (@ARGV) {
+        if ($ARGV[0]) { $channel = $ARGV[0]; }
+        if ($ARGV[1]) { $logfile = $ARGV[1]; }
+        if ($ARGV[2]) { $outputfile = $ARGV[2]; }
+        if ($ARGV[3]) { $maintainer = $ARGV[3]; }
+    }
+
+    if ($tmp) {
+        die("The aliasfile and ignorefile has been obsoleted by the new
+        userscfg, please use that instead [look in users.cfg]\n");
+    }
+
+}
+
+### English
+$T{EN}{mostop1} = "<b>[:nick]</b> donated [:ops] ops in the channel...";
+$T{EN}{mostop2} = "<b>[:nick]</b> was also very polite: [:ops] from her/him";
+$T{EN}{mostop3} = "Strange, no op was given on [:channel]!";
+
+$T{EN}{mostdeop1} = "<b>[:nick]</b> is the channel sheriff with [:deops] deops...";
+$T{EN}{mostdeop2} = "<b>[:nick]</b> deoped [:deops] users";
+$T{EN}{mostdeop3} = "Wow, no op was taken on [:channel]!";
+
+$T{EN}{question1} = "<b>[:nick]</b> is either stupid or just making many questions... [:per]% lines contained a question!";
+$T{EN}{question2} = "<b>[:nick]</b> didn't know that much either, [:per]% of his lines were questions";
+$T{EN}{question3} = "Nobody asked questions here, just geniuses at this channel?";
+
+$T{EN}{loud1} = "Loudest one was <b>[:nick]</b> who yelled [:per]% of the time!";
+$T{EN}{loud2} = "Another <i>old yeller</i> was <b>[:nick]</b> who shouted [:per]% of the time!";
+$T{EN}{loud3} = "Nobody raised an exclamation mark, wow.";
+
+$T{EN}{gotkick1} = "<b>[:nick]</b> wasn't very popular, got kicked [:kicks] times!";
+$T{EN}{gotkick2} = "<b>[:nick]</b> seemed to be hated too, [:kicks] kicks were received";
+
+$T{EN}{joins} = "<b>[:nick]</b> couldn't decide to stay or to go, [:joins] joins during this reporting period!";
+
+$T{EN}{kick1} = "<b>[:nick]</b> is insane or just a fair op, kicked a total of [:kicked] people!";
+$T{EN}{kick2} = "[:oldnick]'s faithfull follower, <b>[:nick]</b>, kicked about [:kicked] people";
+$T{EN}{kick3} = "Nice oppers here, no one got kicked!";
+
+$T{EN}{mono1} = "<b>[:nick]</b> is talking to himself a lot, wrote over 5 lines in a row [:monos] times!";
+$T{EN}{mono2} = "Another lonely one was <b>[:nick]</b>, who managed to hit [:monos] times";
+
+$T{EN}{long1} = "<b>[:nick]</b> wrote longest lines, average of [:letters] per line...";
+$T{EN}{long2} = "Channel average on [:channel] was [:avg] letters per line";
+
+$T{EN}{short1} = "<b>[:nick]</b> wrote shortest lines, average of [:letters] per line...";
+$T{EN}{short2} = "[:nick] was tight-lipped, too, averaging [:letters]";
+
+$T{EN}{foul1} = "<b>[:nick]</b> has quite a potty mouth, [:per]% lines contained foul language";
+$T{EN}{foul2} = "<b>[:nick]</b> also makes sailors blush, [:per]% of the time";
+$T{EN}{foul3} = "Nobody is foul-mouthed at [:channel]! Get out much?";
+
+$T{EN}{smiles1} = "<b>[:nick]</b> brings happiness to the world, [:per]% lines contained smiling faces :)";
+$T{EN}{smiles2} = "<b>[:nick]</b> isn't a sad person either, who smiled [:per]% of the time";
+$T{EN}{smiles3} = "Nobody smiles at [:channel]! Cheer up guys and girls.";
+
+$T{EN}{sad1} = "<b>[:nick]</b> seems to be sad at the moment, [:per]% lines contained sad faces :(";
+$T{EN}{sad2} = "<b>[:nick]</b> is also a sad person, who cried [:per]% of the time";
+$T{EN}{sad3} = "Nobody is sad at [:channel]! What a happy channel :-)";
+
+$T{EN}{notopic} = "A topic was never set on this channel";
+
+## Topics
+
+$T{EN}{bignumtopic} = "Big numbers";
+$T{EN}{othernumtopic} = "Other interesting numbers";
+$T{EN}{latesttopic} = "Latest Topics";
+$T{EN}{activetimestopic} = "Most active times";
+$T{EN}{activenickstopic} = "Most active nicks";
+$T{EN}{mostwordstopic} = "Most used words";
+$T{EN}{referencetopic} = "Most referenced nick";
+
+## Other text
+
+$T{EN}{totallines} = "Total number of lines: [:lines]";
+$T{EN}{nick} = "Nick";
+$T{EN}{numberlines} = "Number of lines";
+$T{EN}{randquote} = "Random quote";
+$T{EN}{userpic} = "Userpic";
+$T{EN}{nottop} = "These didn't make it to the top:";
+$T{EN}{word} = "Word";
+$T{EN}{numberuses} = "Number of Uses";
+$T{EN}{lastused} = "Last Used by";
+$T{EN}{pagetitle1} = "[:channel] @ [:network] stats by [:maintainer]";
+$T{EN}{pagetitle2} = "Statistics generated on [:time]";
+$T{EN}{pagetitle3} = "During this [:days]\-days reporting period, a total number of <b>[:nicks]</b> different nicks were represented on [:channel].";
+
+### German
+
+$T{DE}{mostop1} = "<b>[:nick]</b> vergab [:ops] ops im Channel...";
+$T{DE}{mostop2} = "<b>[:nick]</b> war auch sehr zuvorkommend: [:ops] von ihm/ihr";
+$T{DE}{mostop3} = "Komisch, kein op wurde in [:channel] vergeben!";
+
+$T{DE}{mostdeop1} = "<b>[:nick]</b> ist die Channel-Polizei. Er/Sie hat [:deops] Usern das @ wieder weggenommen...";
+$T{DE}{mostdeop2} = "<b>[:nick]</b> nahm [:deops] Usern das @ wieder weg.";
+$T{DE}{mostdeop3} = "Wow, niemand wurde deopped in [:channel]!";
+
+$T{DE}{question1} = "<b>[:nick]</b> hat wohl in der Schule nicht gut aufgepasst... [:per]% seiner Zeilen enthielten eine Frage!";
+$T{DE}{question2} = "<b>[:nick]</b> weiss wohl auch nicht viel, [:per]% seiner Zeilen waren Fragen";
+$T{DE}{question3} = "Niemand hat hier was gefragt, sollte das etwa ein Channel voller Genies sein? ;)";
+
+$T{DE}{loud1} = "Am lautesten war <b>[:nick]</b> der [:per]% der Zeit geschrieen hat!";
+$T{DE}{loud2} = "Ein anderer <i>Schreihals</i> war <b>[:nick]</b> der/die [:per]% der Zeit rumgeschrieen hat!";
+$T{DE}{loud3} = "Niemand hat ein Ausrufungszeichen benutzt, wow... Zurückhaltende User im Channel ;)";
+
+$T{DE}{gotkick1} = "<b>[:nick]</b> war wohl der Channelclown, er/sie wurde [:kicks] mal gekickt!";
+$T{DE}{gotkick2} = "<b>[:nick]</b> konnte sich scheinbar auch nicht benehmen, [:kicks] kicks für ihn/sie";
+
+$T{DE}{joins} = "<b>[:nick]</b> konnte sich nicht entscheiden im Channel zu bleiben oder zu gehen, [:joins] joins während des Statistik-Zeitraums!";
+
+$T{DE}{kick1} = "Ist <b>[:nick]</b> jetzt einfach nur ein fairer Op oder macht ihm/ihr das Spass? Er/Sie kickte [:kicked] User!";
+$T{DE}{kick2} = "[:oldnick]'s würdiger Nachfolger, <b>[:nick]</b>, er/sie kickte [:kicked] User";
+$T{DE}{kick3} = "Nette Opper hier, niemand wurde gekickt!";
+
+$T{DE}{mono1} = "<b>[:nick]</b> spricht viel mit sich selbst, er/sie schrieb über 5 Zeilen in einer Reihe und das [:monos] mal!";
+$T{DE}{mono2} = "Ein anderes einsames Herz ist <b>[:nick]</b>, der/die [:monos] mal mit sich selbst redete";
+
+$T{DE}{long1} = "<b>[:nick]</b> ist die Labertasche im Channel Er/Sie schrieb die längste Zeile mit durchschnittlich [:letters] Buchstaben pro Zeile...";
+$T{DE}{long2} = "Channel-Durchschnitt in [:channel] war [:avg] Buchstaben pro Zeile";
+
+$T{DE}{short1} = "<b>[:nick]</b> ist nicht sehr Mitteilungsbedürftig. Er/Sie schrieb die kürzeste Zeile mit durchschnittlich [:letters] Buchstaben pro Zeile...";
+$T{DE}{short2} = "[:nick] war auch sehr kurzfassend, durchschnittlich [:letters] Buchstaben pro Zeile";
+
+$T{DE}{foul1} = "<b>[:nick]</b> hat in seiner/ihrer Jugend keine Erziehung genossen, [:per]% seiner/ihrer S&auml;tze enthielten Schimpfworte!";
+$T{DE}{foul2} = "<b>[:nick]</b> war [:per]% der Zeit unartig (Ob da der Weihnachtsman noch kommt?)";
+$T{DE}{foul3} = "Alle gut erzogen in [:channel], niemand hat Schimpfworte benutzt!";
+
+$T{DE}{smiles1} = "<b>[:nick]</b> ist ein fröhlicher Mensch, [:per]% seiner/ihrer Zeilen enthielt ein fröhliches Smily :)";
+$T{DE}{smiles2} = "<b>[:nick]</b> scheint auch glücklich zu sein. Er/Sie \"smilte\" [:per]% der Zeit";
+$T{DE}{smiles3} = "Niemand \"smilte\" in [:channel]! Los Leute legt mal wieder ein Lächeln auf :)";
+
+$T{DE}{sad1} = "<b>[:nick]</b> scheint im Moment nicht gut drauf zu sein, [:per]% seiner/ihrer Zeilen enthielten ein trauriges Smily :(";
+$T{DE}{sad2} = "<b>[:nick]</b> ist auch eine traurige Person, die [:per]% der Zeit geweint hat";
+$T{DE}{sad3} = "Niemand ist traurig in [:channel]! Was für ein fröhlicher Channel :)";
+
+$T{DE}{notopic} = "In diesem Channel wurde kein Topic gesetzt";
+
+## Topics
+
+$T{DE}{bignumtopic} = "Die Zahlen sprechen für sich :)";
+$T{DE}{othernumtopic} = "Andere interessante Zahlen";
+$T{DE}{latesttopic} = "Letzte Topics";
+$T{DE}{activetimestopic} = "Wann war am meisten los?";
+$T{DE}{activenickstopic} = "Wer quatscht am meisten?";
+$T{DE}{mostwordstopic} = "Am meisten benutzte Wörter";
+$T{DE}{referencetopic} = "Begehrte Nicks :)";
+
+## Other text
+
+$T{DE}{totallines} = "Gesamtanzahl der Zeilen: [:lines]";
+$T{DE}{nick} = "Nick";
+$T{DE}{numberlines} = "Anzahl der Zeilen";
+$T{DE}{randquote} = "Zufalls quote";
+$T{DE}{userpic} = "Userbild";
+$T{DE}{nottop} = "Sie haben es nicht an die Spitze geschafft:";
+$T{DE}{word} = "Wort";
+$T{DE}{numberuses} = "Wie oft benutzt";
+$T{DE}{lastused} = "Zuletzt benutzt von";
+$T{DE}{pagetitle1} = "[:channel] @ [:network] stats erstellt von [:maintainer]";
+$T{DE}{pagetitle2} = "Statistik erstellt am [:time]";
+$T{DE}{pagetitle3} = "Während des Statistikzeitraums von [:days] Tage(n) wurden <b>[:nicks]</b> verschiedene Nicks in [:channel] gezählt.";
+
+### Danish
+$T{DK}{mostop1} = "<b>[:nick]</b> gav [:ops] op status i kanalen...";
+$T{DK}{mostop2} = "<b>[:nick]</b> var også venlig at gi': [:ops] fra sig";
+$T{DK}{mostop3} = "Underligt, ingen har fået op på [:channel] endnu!";
+
+$T{DK}{mostdeop1} = "<b>[:nick]</b> holder orden på [:channel] med [:deops] deops...";
+$T{DK}{mostdeop2} = "<b>[:nick]</b> deoppede [:deops] brugere";
+$T{DK}{mostdeop3} = "Hmm, der var ingen som fik op på [:channel]!";
+
+$T{DK}{question1} = "<b>[:nick]</b> må være rimelig dum eller efterligner bare Spørge-Jørgen... [:per]% af hans linjer var spørgsmål!";
+$T{DK}{question2} = "<b>[:nick]</b> var heller ingen Einstein, [:per]% af hans linjer var også spørgsmål";
+$T{DK}{question3} = "Ingen spørgsmål på denne kanal. De må sørme være kloge!";
+
+$T{DK}{loud1} = "Den mest larmende var <b>[:nick]</b>, som råbte [:per]% af tiden!";
+$T{DK}{loud2} = "En anden skrigehals var <b>[:nick]</b>, som råbte [:per]% af tiden!";
+$T{DK}{loud3} = "Der er ingen som råber. Leger de stilleleg?";
+
+$T{DK}{gotkick1} = "<b>[:nick]</b> er ret upopulær, blev sparket ud [:kicks] gange!";
+$T{DK}{gotkick2} = "<b>[:nick]</b> er heller ikke nogen Elvis, [:kicks] gange fik han sparket";
+
+$T{DK}{joins} = "<b>[:nick]</b> er lidt forvirret. Han har været inde og ude af kanalen [:joins] gange!";
+
+$T{DK}{kick1} = "<b>[:nick]</b> er magtsygt eller ved hvordan han/hun skal kontrollere sine fjender. Han har sparket [:kicked] folk ud af kanalen!";
+$T{DK}{kick2} = "[:oldnick] har en efterfølger. <b>[:nick]</b> sparkede [:kicked] folk ud af kanalen";
+$T{DK}{kick3} = "Venlige operatører må man sige. Ingen har fået sparket!";
+
+$T{DK}{mono1} = "<b>[:nick]</b> snakker meget med sig selv. Han har skrevet over 5 linjer på en gang, [:monos] gange!";
+$T{DK}{mono2} = "En anden ensom person var <b>[:nick]</b>, som snakkede til sig selv [:monos] gange";
+
+$T{DK}{long1} = "<b>[:nick]</b> skriver meget lange sætninger. Gennemsnittet er [:letters] bogstaver pr. linje...";
+$T{DK}{long2} = "Gennemsnittet på [:channel] er [:avg] bogstaver pr. linje";
+
+$T{DK}{short1} = "<b>[:nick]</b> skrev de korteste linjer. Gennemsnittet er [:letters] bogstaver pr. linje...";
+$T{DK}{short2} = "[:nick] skriver også korte linjer, med et gennesnit på [:letters]";
+
+$T{DK}{foul1} = "<b>[:nick]</b> er lidt stor i munden, [:per]% af linjerne indholte uhøfligt sprog";
+$T{DK}{foul2} = "<b>[:nick]</b> burde også vaske munden med sæbe med at det han fyrer af, [:per]% af tiden";
+$T{DK}{foul3} = "Ingen rapkæftede på [:channel]!";
+
+$T{DK}{smiles1} = "<b>[:nick]</b> er altid glad, [:per]% af linjerne havde glade ansigter :)";
+$T{DK}{smiles2} = "<b>[:nick]</b> er heller ikke en trist person, som smilede [:per]% af tiden";
+$T{DK}{smiles3} = "Ingen smiler på [:channel]! Op med humøret drenge og piger.";
+
+$T{DK}{sad1} = "<b>[:nick]</b> er meget ked af det for tiden, [:per]% af linjerne havde sørgelige ansigter :(";
+$T{DK}{sad2} = "<b>[:nick]</b> er også en trist person, som græd [:per]% af tiden";
+$T{DK}{sad3} = "Ingen er kede af det på [:channel]! Sikke en glad kanal :-)";
+
+$T{DK}{notopic} = "A topic was never set on this channel";
+
+## Topics
+
+$T{DK}{bignumtopic} = "Store numre";
+$T{DK}{othernumtopic} = "Andre interessante numre";
+$T{DK}{latesttopic} = "Sidst nyeste topics";
+$T{DK}{activetimestopic} = "Mest aktive tider";
+$T{DK}{activenickstopic} = "Mest aktive nicks";
+$T{DK}{mostwordstopic} = "Mest brugte ord";
+$T{DK}{referencetopic} = "Mest tiltalte nicks";
+
+## Other text
+
+$T{DK}{totallines} = "Hele antal linjer: [:lines]";
+$T{DK}{nick} = "Nick";
+$T{DK}{numberlines} = "Antal linjer";
+$T{DK}{randquote} = "Tilfældig sætning";
+$T{DK}{userpic} = "Brugerbillede";
+$T{DK}{nottop} = "Disse nåede ikke til tops:";
+$T{DK}{word} = "Ord";
+$T{DK}{numberuses} = "Antal forbrug";
+$T{DK}{lastused} = "Sidst brugt af";
+$T{DK}{pagetitle1} = "[:channel] @ [:network] statistikker af [:maintainer]";
+$T{DK}{pagetitle2} = "Statistikker lavet d. [:time]";
+$T{DK}{pagetitle3} = "Igennem denne [:days]\-dages periode, har der været <b>[:nicks]</b> forskellige nicks på [:channel].";
+
+### French
+
+# Not exactly the translation but something fun
+# Time translation needs more update
+
+$T{FR}{mostop1} = "<b>[:nick]</b> a donné [:ops] ops sur le channel...";
+$T{FR}{mostop2} = "<b>[:nick]</b> est aussi très poli : [:ops] ops de sa part";
+$T{FR}{mostop3} = "Etrange, aucun op n'a été donné sur [:channel]!";
+
+$T{FR}{mostdeop1} = "<b>[:nick]</b> est le shérif avec [:deops] deops...";
+$T{FR}{mostdeop2} = "<b>[:nick]</b> a deopé [:deops] utilisateurs";
+$T{FR}{mostdeop3} = "Wow, aucun op n'a été retiré sur [:channel]!";
+
+$T{FR}{question1} = "<b>[:nick]</b> est soit stupide soit trop curieux... [:per]% de ses lignes contiennent une question!";
+$T{FR}{question2} = "<b>[:nick]</b> n'en connait pas davantage, [:per]% de ses lignes étaient des questions";
+$T{FR}{question3} = "Personne ne pose de question ici, tous des génies sur ce channel?";
+
+$T{FR}{loud1} = "Le plus bruyant est <b>[:nick]</b> qui gueule [:per]% du temps!";
+$T{FR}{loud2} = "Un autre <i>vieux raleur</i> est <b>[:nick]</b> qui braille [:per]% du temps!";
+$T{FR}{loud3} = "Personne ne s'exclame ici, wow.";
+
+$T{FR}{gotkick1} = "<b>[:nick]</b> n'est pas trés populaire, kické [:kicks] fois!";
+$T{FR}{gotkick2} = "<b>[:nick]</b> n'a pas d'ami non plus, [:kicks] kicks reçus";
+
+$T{FR}{joins} = "<b>[:nick]</b> ne sait pas s'il doit rester ou partir, [:joins] visites durant cette période!";
+
+$T{FR}{kick1} = "<b>[:nick]</b> est malade ou alors aime bien jouer, son kick a sévi [:kicked] fois!";
+$T{FR}{kick2} = "Un disciple de [:oldnick], <b>[:nick]</b>, a kické [:kicked] personnes";
+$T{FR}{kick3} = "Les ops sont sympas ici, personne n'a été kické!";
+
+$T{FR}{mono1} = "<b>[:nick]</b> se parle à lui-meme trés souvent, il a écrit plus de 5 lignes d'un coup à [:monos] reprises!";
+$T{FR}{mono2} = "Un autre incompris est <b>[:nick]</b>, qui a tenté de le dépasser à [:monos] reprises";
+
+$T{FR}{long1} = "<b>[:nick]</b> a écrit les lignes les plus longues, avec en moyenne [:letters] lettres par ligne...";
+$T{FR}{long2} = "La moyenne sur [:channel] est de [:avg] lettres par ligne";
+
+$T{FR}{short1} = "<b>[:nick]</b> a écrit les lignes les plus courtes, avec en moyenne [:letters] lettres par ligne...";
+$T{FR}{short2} = "[:nick] n'a pas grand chose a dire non plus, en moyenne [:letters]";
+
+$T{FR}{foul1} = "<b>[:nick]</b> a du mal a s'exprimer, [:per]% de ses lignes contiennent des mots incompréhensibles";
+$T{FR}{foul2} = "<b>[:nick]</b> parle aussi comme un chartier, [:per]% du temps";
+$T{FR}{foul3} = "Personne n'a de problème pour parler sur [:channel]! Pendant combien de temps encore?";
+
+$T{FR}{smiles1} = "<b>[:nick]</b> apporte un peu de gaiété dans le monde, [:per]% de ses lignes contiennent un smiley :)";
+$T{FR}{smiles2} = "<b>[:nick]</b> n'est pas triste non plus, il sourit [:per]% du temps";
+$T{FR}{smiles3} = "Personne ne sourit sur [:channel]! Allez faites un effort quoi!";
+
+$T{FR}{sad1} = "<b>[:nick]</b> semble un peu triste en ce moment, [:per]% de ses lignes contiennent un smiley :(";
+$T{FR}{sad2} = "<b>[:nick]</b> est aussi bien triste, il pleure [:per]% du temps";
+$T{FR}{sad3} = "Personne n'est triste sur [:channel]! Quel channel merveilleux :-)";
+
+$T{FR}{notopic} = "Il n'y a jamais eu de topic sur ce channel";
+
+## Topics
+
+$T{FR}{bignumtopic} = "Les gros chiffres";
+$T{FR}{othernumtopic} = "D'autres chiffres intéressants";
+$T{FR}{latesttopic} = "Les derniers topics";
+$T{FR}{activetimestopic} = "Les périodes d'intense activité";
+$T{FR}{activenickstopic} = "Les nicks les plus actifs";
+$T{FR}{mostwordstopic} = "Les mots les plus utilisés";
+$T{FR}{referencetopic} = "Les mots les plus utilisés dans la conversation";
+
+## Other text
+
+$T{FR}{totallines} = "Nombre total de lignes: [:lines]";
+$T{FR}{nick} = "Nick";
+$T{FR}{numberlines} = "Nombre de lignes";
+$T{FR}{randquote} = "Citation typique";
+$T{FR}{userpic} = "Image";
+$T{FR}{nottop} = "Ne sont pas dans le classement";
+$T{FR}{word} = "Mot";
+$T{FR}{numberuses} = "Nombre d'occurences";
+$T{FR}{lastused} = "Dernière utilisation par";
+$T{FR}{pagetitle1} = "[:channel] @ [:network] stats par [:maintainer]";
+$T{FR}{pagetitle2} = "Statistiques générées le [:time]";
+$T{FR}{pagetitle3} = "Durant cette periode de [:days] jours, <b>[:nicks]</b> nicks différents sont apparus sur [:channel].";
+
+&main();        # Run the script

+ 23 - 0
users.cfg

@@ -0,0 +1,23 @@
+# Users config file for pisg
+# This file is used for a couple of things:
+# - Setting aliases for nicks
+# - Setting nicks to be ignored
+# - Setting user pictures
+# - Setting links to users homepages/e-mails
+#
+# The format is quite simple.
+#
+# To add aliases to a user:
+# <user nick="Joe" alias="Joe^away Joe^work">
+#
+# To add a picture to a user:
+# <user nick="Joe" pic="joe.jpg">
+#
+# To ignore a nick:
+# <user nick="nameofbot" ignore="y">
+#
+# You don't have to make it over many lines, you could just as easily do:
+# <user nick="Joe" alias="Joe^away Joe^work" pic="joe.jpg" link="http://www.joe.com">
+#
+# Have fun.. :)
+