Browse Source

Commit some pending stuff.

Morten Brix Pedersen 22 years ago
parent
commit
c8a2b61de4
9 changed files with 315 additions and 67 deletions
  1. 3 1
      docs/CREDITS
  2. 8 1
      docs/Changelog
  3. 14 0
      docs/FORMATS
  4. 95 6
      docs/pisg-doc.sgml
  5. 10 10
      lang.txt
  6. 16 1
      modules/Pisg.pm
  7. 16 1
      modules/Pisg/Common.pm
  8. 49 47
      modules/Pisg/HTMLGenerator.pm
  9. 104 0
      modules/Pisg/Parser/Format/Vision.pm

+ 3 - 1
docs/CREDITS

@@ -39,7 +39,7 @@ And all the other contributors:
      --silent switch
  * Tim Hunter <tim@quic.net>
    | 'shortest lines', 'most fouls', 'pageheader' and more. 
- * Kim Ingemann (Tuxi) <tuxi@tuxi.dk>
+ * Kim Ingemann (KimI) <mail@pingvinland.dk>
    | Danish translation
  * Molator <molator@ifrance.com>
    | French translation
@@ -99,6 +99,8 @@ And all the other contributors:
    | oer logfile support
  * "Travis Roy" <travis@scootz.net>
    | perlbot logfile support
+ * Christoph Berg <cb@df7cb.de>
+   | NFiles and UserPics options, wildcards for random user pictures
 
 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 :)

+ 8 - 1
docs/Changelog

@@ -4,7 +4,14 @@ pisg (x.xx)
    * Added a DailyActivity option, which shows the activity in the channel of
      the last N days. Disabled by default. (thanks, sektor).
    * Fixed wrong example documentation for PicWidth and PicHeight (thanks,
-   Dennis Prebendorf).
+     Dennis Prebendorf).
+   * (Previously undocumented) setting UserPics allows to turn on/off user
+     pictures per channel and to place more than one picture per row to avoid
+     blowing up the table's height. (Christoph Berg)
+   * User pictures and DefaultPic may now contain * and ? to choose a random
+     picture from ImageGlobPath. (Christoph Berg)
+   * Made all picture heights integer. (Christoph Berg)
+   * Add support for Vision logs (thanks, Vegard Wærp)
 
 pisg (0.50) - Mon Nov, 17th 2003
    * Added NFiles option, which can be used for LogDirs to only parse the last

+ 14 - 0
docs/FORMATS

@@ -458,3 +458,17 @@ time"
  [2:51] <Lawy> [BruJo] wenas
  [2:51] <Danaita> lawyyyy
 
+* Vision
+  - An irc client for BeOS ( http://vision.sourceforge.net and http://
+www.bebits.com/app/2623 )
+                                                                                
+ - Timestamping has to be enabled in the preferences
+                                                                                
+ - The messages Vision displays for joins/parts/topic changes etc, is
+fully user configurable,
+   this parses the default messages. If you have changed them in the
+preferences you
+   have to edit the parser to reflect the changes.
+                                                                                
+ - In order to use this, format must be set to 'Vision'
+

+ 95 - 6
docs/pisg-doc.sgml

@@ -154,6 +154,12 @@
      <![CDATA[<user nick="Ben" pic="ben_holiday.jpg" bigpic="ben-big.jpg">]]>
     </programlisting>
 
+    You can include * or ? to enable filename globbing to randomly choose one
+    from several pictures: (see <command>ImageGlobPath</command> below)
+    <programlisting>
+    <![CDATA[<user nick="Ben" pic="ben_*.jpg">]]>
+    </programlisting>
+
     You can also set a user's sex:
     <programlisting>
     <![CDATA[<user nick="Ben" sex="m">]]>
@@ -2588,7 +2594,7 @@
 
     <refsynopsisdiv><programlisting>
       <![CDATA[
-        <set PicLocation="userpics/">
+        <set PicLocation="gfx/">
       ]]>
     </programlisting></refsynopsisdiv>
 
@@ -2604,6 +2610,46 @@
     <para> Current directory </para>
     </refsect1>
 
+    <!-- *** USERPICS *** -->
+    <refentry id="UserPics">
+
+    <refmeta> <refentrytitle>UserPics</refentrytitle> </refmeta>
+
+    <refnamediv>
+    <refname>UserPics</refname>
+    <refpurpose>number of user pictures per row</refpurpose>
+    </refnamediv>
+
+    <refsynopsisdiv><programlisting>
+      <![CDATA[
+        <channel="#channel">
+         Logfile = "channel.log"
+         UserPics = "no"
+        </channel>
+
+        <set UserPics="3">
+      ]]>
+    </programlisting></refsynopsisdiv>
+
+    <refsect1>
+    <title>Description</title>
+    <para>
+    <command>UserPics</command> allows you to configure the number of user
+    pictures per row. Per default, one picture will be shown. Since pictures
+    are usually higher than one line of text, this lets the table grow. With
+    settings greater than 1, several pictures will be placed next to each
+    other. A good setting would be <command>UserPics</command>=3 and pictures
+    of size 60x60. Set <command>UserPics</command> to no or 0 to disable user
+    pictures. The latter is useful if you share a user config file between
+    channels and want to disable user pictures for some channels.
+    </para>
+    </refsect1>
+    <refsect1>
+    <title>Default</title>
+    <para> yes (1) </para>
+    </refsect1>
+    </refentry>
+
     <!-- *** IMAGEPATH *** -->
     <refentry id="ImagePath">
 
@@ -2611,7 +2657,7 @@
 
     <refnamediv>
     <refname>ImagePath</refname>
-    <refpurpose>path to user pictures</refpurpose>
+    <refpurpose>path to user pictures (HTML page)</refpurpose>
     </refnamediv>
 
     <refsynopsisdiv><programlisting>
@@ -2622,16 +2668,16 @@
          ImagePath = "userpics/"
         </channel>
 
-        <set ImagePath="alt_users/">
+        <set ImagePath="users/">
       ]]>
     </programlisting></refsynopsisdiv>
 
     <refsect1>
     <title>Description</title>
     <para>
-    <command>ImagePath</command> defines an alternative path to where user
-    pictures is located. The default is that user pictures is located in the
-    same directory as the HTML page.
+    <command>ImagePath</command> defines the path to where user
+    pictures are located, relative to the HTML page generated. The default is
+    that user pictures is located in the same directory as the HTML page.
     </para>
     </refsect1>
     <refsect1>
@@ -2668,6 +2714,7 @@
     <command>DefaultPic</command> defines a picture to be displayed for all
     users which have no other picture defined in the <command>user</command>
     element. This is good for showing "No picture available" or something.
+    May contain globbing patterns, see <command>ImageGlobPath</command> below.
     </para>
     </refsect1>
     <refsect1>
@@ -2676,6 +2723,48 @@
     </refsect1>
     </refentry>
 
+    <!-- *** IMAGEGLOBPATH *** -->
+    <refentry id="ImageGlobPath">
+
+    <refmeta> <refentrytitle>ImageGlobPath</refentrytitle> </refmeta>
+
+    <refnamediv>
+    <refname>ImageGlobPath</refname>
+    <refpurpose>path to user pictures (output generation)</refpurpose>
+    </refnamediv>
+
+    <refsynopsisdiv><programlisting>
+      <![CDATA[
+        <channel="#channel">
+         Logfile = "channel.log"
+         Format = "mIRC"
+         ImageGlobPath = "users/"
+        </channel>
+
+        <set ImageGlobPath="/var/www/pisg/">
+        <set DefaultPic="default-??.gif">
+      ]]>
+    </programlisting></refsynopsisdiv>
+
+    <refsect1>
+    <title>Description</title>
+    <para>
+    <command>ImageGlobPath</command> defines the path to the directory where
+    user pictures are located, relative to the current directory. This setting
+    is used to choose random pictures if ? or * (globbing characters) are used
+    in the picture name. ? matches a single character, * matches a (possibly
+    empty) string. The default is the <command>ImagePath</command> setting.
+    (NB: This setting will be different from <command>ImagePath</command> if
+    the latter is not relative to the current directory, e.g. if you are
+    writing the HTML file outside of the current directory.)
+    </para>
+    </refsect1>
+    <refsect1>
+    <title>Default</title>
+    <para> current <command>ImagePath</command> setting </para>
+    </refsect1>
+    </refentry>
+
     <!-- *** PICWIDTH *** -->
     <refentry id="PicWidth">
 

+ 10 - 10
lang.txt

@@ -190,9 +190,9 @@ mailto = "Mail to"
 
 ### German
 <lang name="DE">
-mostop1 = "<b>[:nick]</b> vergab [:ops] ops im Channel..."
+mostop1 = "<b>[:nick]</b> vergab [:ops] Ops im Channel..."
 mostop2 = "<b>[:nick]</b> war auch sehr zuvorkommend: [:ops] von [:ihm:ihr:]."
-mostop3 = "Komisch, kein op wurde in [:channel] vergeben!"
+mostop3 = "Komisch, kein Op wurde in [:channel] vergeben!"
 
 mostdeop1 = "<b>[:nick]</b> ist die Channel-Polizei. [:Er:Sie:] hat [:deops] Usern das @ wieder weggenommen..."
 mostdeop2 = "<b>[:nick]</b> nahm [:deops] Usern das @ wieder weg."
@@ -206,11 +206,11 @@ mostdehalfop1 = "<b>[:nick]</b> hat am meisten User dehalfopped: genau [:dehalfo
 mostdehalfop2 = "<b>[:nick]</b> hat immerhin schon [:dehalfops] Usern das % wieder geklaut."
 mostdehalfop3 = "Bis jetzt wurde in [:channel] keinem das % weggenommen!"
 
-mostvoice1 = "<b>[:nick]</b> hat [:voices] voices im Channel vergeben..."
-mostvoice2 = "<b>[:nick]</b> war auch sehr höflich: [:voices] voices von [:ihm:ihr:]."
+mostvoice1 = "<b>[:nick]</b> hat [:voices] Voices im Channel vergeben..."
+mostvoice2 = "<b>[:nick]</b> war auch sehr höflich: [:voices] Voices von [:ihm:ihr:]."
 mostvoice3 = "Seltsam, bisher wurden keine Voices in [:channel] vergeben!"
 
-mostdevoice1 = "<b>[:nick]</b> ist der Wachtmeister des Channels mit  [:devoices] devoices."
+mostdevoice1 = "<b>[:nick]</b> ist der Wachtmeister des Channels mit [:devoices] devoices."
 mostdevoice2 = "<b>[:nick]</b> hat [:devoices] Nutzer devoiced."
 mostdevoice3 = "Wow, keine devoices in [:channel]!"
 
@@ -259,7 +259,7 @@ words3 = "Niemand hat etwas gesagt? Seltsamer Channel..."
 mono1 = "<b>[:nick]</b> spricht viel mit sich selbst, [:er:sie:] schrieb über 5 Zeilen in einer Reihe und das [:monos] mal!"
 mono2 = "Ein anderes einsames Herz ist <b>[:nick]</b>, [:der:die:] [:monos] mal mit sich selbst redete."
 
-wpl1 = "<b>[:nick]</b> schrieb im Schnitt [:wpl] Worte pro Zeile ..."
+wpl1 = "<b>[:nick]</b> schrieb im Schnitt [:wpl] Worte pro Zeile..."
 wpl2 = "Der Channeldurchschnitt lag bei [:avg] Worten/Zeile."
 
 long1 = "<b>[:nick]</b> ist die Labertasche im Channel. [:Er:Sie:] schrieb die längste Zeile mit durchschnittlich [:letters] Buchstaben pro Zeile..."
@@ -303,12 +303,12 @@ mostnickstopic = "Chatter mit den meisten Nicks"
 
 totallines = "Gesamtanzahl der Zeilen: [:lines]"
 totalnicks = "Ach ja, da waren noch [:totalnicks] andere, die nicht erwähnenswert sind."
-totaltopic = "Ingesamt wurde das Topic [:total] mal gesetzt"
+totaltopic = "Ingesamt wurde das Topic [:total] mal gesetzt."
 nick = "Nick"
 names = "Benutzte Namen"
 url = "URL"
 numberlines = "Anzahl der Zeilen"
-randquote = "Zufalls quote"
+randquote = "Zufallsquote"
 userpic = "Userbild"
 nottop = "Sie haben es nicht an die Spitze geschafft:"
 word = "Wort"
@@ -325,9 +325,9 @@ today = "heute"
 pagetitle1 = "[:channel] @ [:network] Stats erstellt von [:maintainer]"
 pagetitle2 = "Statistik erstellt am "
 pagetitle3 = "Während des Statistikzeitraums von [:days] Tage(n) wurden <b>[:nicks]</b> verschiedene Nicks in [:channel] gezählt."
-stats_gen_by = "Die Statistiken wurden erstellt mit [:pisg_url] v[:version]"
+stats_gen_by = "Die Statistiken wurden erstellt mit [:pisg_url] v[:version]."
 author = "pisg von [:author_url] und anderen"
-stats_gen_in = "Die Statistik wurde in [:time] generiert"
+stats_gen_in = "Die Statistik wurde in [:time] generiert."
 hours = "Stunden"
 minutes = "Minuten"
 seconds = "Sekunden"

+ 16 - 1
modules/Pisg.pm

@@ -102,6 +102,7 @@ sub get_default_config_settings
         pagefoot => 'none',
         configfile => 'pisg.cfg',
         imagepath => '',
+        imageglobpath => '',
         defaultpic => '',
         logdir => '',
         nfiles => 0,
@@ -423,6 +424,8 @@ sub init_config
             if ((open(INCLUDE, $self->{cfg}->{configfile}) 
                 or open(INCLUDE, $self->{search_path} . "/$self->{cfg}->{configfile}"))) {
                 $r = $self->init_config(\*INCLUDE);
+            } else {
+                print STDERR "Warning: $backup_cfg, line $.: $self->{cfg}->{configfile}: $!\n";
             }
             print "Included config file: $self->{cfg}->{configfile}\n\n"
                 if ($r && !$self->{cfg}->{silent});
@@ -431,7 +434,7 @@ sub init_config
             print STDERR "Warning: $self->{cfg}->{configfile}, line $.: Missing end on element <$1 (probably multi-line?)\n";
         } elsif ($line =~ /\S/) {
             $line =~ s/\n//;
-            print "Warning: $self->{cfg}->{configfile}, line $.: Unrecognized line\n";
+            print STDERR "Warning: $self->{cfg}->{configfile}, line $.: Unrecognized line\n";
         }
     }
 
@@ -457,6 +460,18 @@ sub init_pisg
 
     # Add trailing slash when it's not there..
     $self->{cfg}->{imagepath} =~ s/([^\/])$/$1\//;
+    # Set ImageGlobPath default
+    $self->{cfg}->{imageglobpath} ||= $self->{cfg}->{imagepath};
+    $self->{cfg}->{imageglobpath} =~ s/([^\/])$/$1\//;
+
+    # Set number of picture columns to show
+    if ($self->{cfg}->{userpics} =~ /^n/i) {
+        $self->{cfg}->{userpics} = 0;
+    } elsif ($self->{cfg}->{userpics} =~ /^y/i) {
+        $self->{cfg}->{userpics} = 1;
+    } elsif ($self->{cfg}->{userpics} !~ /^\d+$/) {
+        print STDERR "Warning: $self->{cfg}->{configfile}, line $.: Invalid UserPics setting\n";
+    }
 
     print "Using language template: $self->{cfg}->{lang}\n\n" if ($self->{cfg}->{lang} ne 'EN' && !$self->{cfg}->{silent});
 

+ 16 - 1
modules/Pisg/Common.pm

@@ -8,7 +8,7 @@ Pisg::Common - some common functions of pisg.
 
 use Exporter;
 @ISA = ('Exporter');
-@EXPORT = qw(add_alias add_aliaswild add_ignore add_url_ignore is_ignored url_is_ignored find_alias store_aliases restore_aliases match_urls match_email htmlentities is_nick);
+@EXPORT = qw(add_alias add_aliaswild add_ignore add_url_ignore is_ignored url_is_ignored find_alias store_aliases restore_aliases match_urls match_email htmlentities is_nick randomglob);
 
 use strict;
 $^W = 1;
@@ -209,4 +209,19 @@ sub htmlentities
     return $str;
 }
 
+sub randomglob
+{
+    my $pattern = shift;
+    my $globpath = shift;
+    return $pattern unless $pattern =~ /[*?]/;
+    my @globs = glob $globpath . $pattern;
+    my $return = $globs[int(rand(@globs))];
+    unless($return) {
+        print STDERR "Warning: no picture for $pattern found in $globpath\n";
+        return $pattern;
+    }
+    $return =~ s/^$globpath//;
+    return $return;
+}
+
 1;

+ 49 - 47
modules/Pisg/HTMLGenerator.pm

@@ -63,6 +63,9 @@ sub create_output
     if ($self->{cfg}->{showcpl}) {
         $self->{cfg}->{tablewidth} += 40;
     }
+    if ($self->{cfg}->{userpics}) {
+        $self->{cfg}->{tablewidth} += $self->{cfg}->{userpics} * ($self->{cfg}->{picwidth} || 60);
+    }
     $self->{cfg}->{headwidth} = $self->{cfg}->{tablewidth} - 4;
     $self->_htmlheader();
     $self->_pageheader()
@@ -337,13 +340,12 @@ sub _activedays
         _html("<td align=\"center\" valign=\"bottom\" class=\"asmall\">$lines<br>");
         for ($time = 4; $time >= 0; $time--) {
             if (defined($self->{stats}->{day_times}{$day}[$time])) {
-                my $size = ($self->{stats}->{day_times}{$day}[$time] / $highest_value) * 100;
+                my $size = int(($self->{stats}->{day_times}{$day}[$time] / $highest_value) * 100);
 
-                if ($size < 1 && $size != 0) {
+                if ($size < 1) {
                     # Opera doesn't understand '0.xxxx' in the height="xx" attr,
-                    # so we simply round up to 1.0 here.
-
-                    $size = 1.0;
+                    # so we simply round up to 1 here.
+                    $size = 1;
                 }
 
                 $image = "pic_v_".$time*6;
@@ -388,15 +390,14 @@ sub _activetimes
 
     for my $hour (sort keys %{ $self->{stats}->{times} }) {
 
-        my $size = ($self->{stats}->{times}{$hour} / $highest_value) * 100;
+        my $size = int(($self->{stats}->{times}{$hour} / $highest_value) * 100);
         my $percent = ($self->{stats}->{times}{$hour} / $self->{stats}->{parsedlines}) * 100;
         $percent =~ s/(\.\d)\d+/$1/;
 
-        if ($size < 1 && $size != 0) {
+        if ($size < 1) {
             # Opera doesn't understand '0.xxxx' in the height="xx" attr,
-            # so we simply round up to 1.0 here.
-
-            $size = 1.0;
+            # so we simply round up to 1 here.
+            $size = 1;
         }
 
         $image = "pic_v_".(int($hour/6)*6);
@@ -470,12 +471,13 @@ sub _activenicks
 
     if ($self->{cfg}->{activenicks} > $nicks) { $self->{cfg}->{activenicks} = $nicks; }
 
-    my $have_userpics;
     for (my $c = 0; $c < $self->{cfg}->{activenicks}; $c++) {
+        last unless $self->{cfg}->{userpics};
         my $nick = $active[$c];
-        if ($self->{users}->{userpics}{$nick} && $self->{cfg}->{userpics} !~ /n/i) {
-            $have_userpics = 1;
-            _html("<td class=\"tdtop\"><b>" . $self->_template_text('userpic') ."</b></td>");
+        if ($self->{users}->{userpics}{$nick}) {
+            _html("<td class=\"tdtop\"" .
+                ($self->{cfg}->{userpics} > 1 ? " colspan=\"$self->{cfg}->{userpics}\"" : "") .
+                "><b>" . $self->_template_text('userpic') ."</b></td>");
             last;
         }
     }
@@ -559,41 +561,12 @@ sub _activenicks
         "<td style=\"background-color: $color\">\"$randomline\"</td>"
         : "")
         );
-
-        my $height = $self->{cfg}->{picheight};
-        my $width = $self->{cfg}->{picwidth};
-        if ($width ne '') {
-            $width = "width=\"$width\"";
-        }
-        if ($height ne '') {
-            $height = "height=\"$height\"";
-        }
-        if ($self->{users}->{userpics}{$nick} && $self->{cfg}->{userpics} !~ /n/i) {
-            _html("<td style=\"background-color: $color\" align=\"center\" valign=\"middle\">");
-            if (defined $self->{users}->{biguserpics}{$nick}) {
-                if ($self->{users}->{biguserpics}{$nick} =~ /^http:\/\//i) {
-                    _html("<a href=\"$self->{users}->{biguserpics}{$nick}\">");
-                } else {
-                    _html("<a href=\"$self->{cfg}->{imagepath}$self->{users}->{biguserpics}{$nick}\">");
-                }
-            }
-            if ($self->{users}->{userpics}{$nick} =~ /^http:\/\//i) {
-                _html("<img src=\"$self->{users}->{userpics}{$nick}\" $width $height alt=\"$nick\" />");
-            } else {
-                _html("<img src=\"$self->{cfg}->{imagepath}$self->{users}->{userpics}{$nick}\" $width $height alt=\"$nick\" />");
-            }
-            if (defined $self->{users}->{biguserpics}{$nick}) {
-                _html("</a>");
+        if ($self->{cfg}->{userpics} && $i % $self->{cfg}->{userpics} == 0) {
+            for my $ii (0 .. $self->{cfg}->{userpics} - 1) {
+                last if $i + $ii >= $self->{cfg}->{activenicks};
+                $self->_user_pic($active[$i + $ii], $color);
             }
-            _html("</td>");
-        } elsif ($self->{cfg}->{defaultpic} ne '' && $self->{cfg}->{userpics} !~ /n/i)  {
-             if ($self->{cfg}->{defaultpic} =~ /^http:\/\//i) {
-                _html("<td style=\"background-color: $color\" align=\"center\" valign=\"middle\"><img src=\"$self->{cfg}->{defaultpic}\" $width $height alt=\"\" /></td>");
-             } else {
-                _html("<td style=\"background-color: $color\" align=\"center\" valign=\"middle\"><img src=\"$self->{cfg}->{imagepath}$self->{cfg}->{defaultpic}\" $width $height alt=\"\" /></td>");
-             }
         }
-
         _html("</tr>");
     }
 
@@ -1945,6 +1918,35 @@ sub _user_times
     return $bar;
 }
 
+sub _user_pic
+{
+    my $self = shift;
+    my $nick  = shift;
+    my $color  = shift;
+
+    return unless $self->{users}->{userpics}{$nick} or $self->{cfg}->{defaultpic};
+
+    my $rowspan = $self->{cfg}->{userpics} ? " rowspan=\"$self->{cfg}->{userpics}\"" : "";
+    _html("<td style=\"background-color: $color\" align=\"center\" valign=\"middle\"$rowspan>");
+
+    my $biguserpic = $self->{users}->{biguserpics}{$nick};
+    $biguserpic = $self->{cfg}->{imagepath} . randomglob($biguserpic, $self->{cfg}->{imageglobpath})
+        if $biguserpic and $biguserpic !~ /^http:\/\//i;
+    $biguserpic = "<a href=\"$biguserpic\">" if $biguserpic;
+    _html($biguserpic) if $biguserpic;
+
+    my $pic = $self->{users}->{userpics}{$nick} || $self->{cfg}->{defaultpic};
+    $pic = $self->{cfg}->{imagepath} . randomglob($pic, $self->{cfg}->{imageglobpath})
+        unless $pic =~ /^http:\/\//i;
+    my $height = $self->{cfg}->{picheight} ? " width=\"$self->{cfg}->{picheight}\"" : "";
+    my $width = $self->{cfg}->{picwidth} ? " height=\"$self->{cfg}->{picwidth}\"" : "";
+    my $alt = $self->{users}->{userpics}{$nick} ? " alt=\"$nick\"" : "";
+    _html("<img src=\"$pic\"$width$height$alt />");
+
+    _html("</a>") if $biguserpic;
+    _html("</td>");
+}
+
 sub _mostnicks
 {
     # List showing the user with most used nicks

+ 104 - 0
modules/Pisg/Parser/Format/Vision.pm

@@ -0,0 +1,104 @@
+package Pisg::Parser::Format::Vision;
+
+# Documentation for the Pisg::Parser::Format modules is found in Template.pm
+
+use strict;
+$^W = 1;
+
+sub new
+{
+    my ($type, %args) = @_;
+    my $self = {
+        cfg => $args{cfg},
+        normalline => '^\[(\d+):\d+\S+ <([^>]+)> (.*)$',
+        actionline => '^\[(\d+):\d+\S+ \* (\S+) (.*)$',
+        thirdline  => '^\[(\d+):(\d+)\S+ \*{3} (.+)$'
+    };
+
+    bless($self, $type);
+    return $self;
+}
+
+sub normalline
+{
+    my ($self, $line, $lines) = @_;
+    my %hash;
+
+    if ($line =~ /$self->{normalline}/o) {
+
+        $hash{hour}   = $1;
+        $hash{nick}   = remove_prefix($2);
+        $hash{saying} = $3;
+
+        return \%hash;
+    } else {
+        return;
+    }
+}
+
+sub actionline
+{
+    my ($self, $line, $lines) = @_;
+    my %hash;
+
+    if ($line =~ /$self->{actionline}/o) {
+
+        $hash{hour}   = $1;
+        $hash{nick}   = remove_prefix($2);
+        $hash{saying} = $3;
+
+        return \%hash;
+    } else {
+        return;
+    }
+}
+
+sub thirdline
+{
+    my ($self, $line, $lines) = @_;
+    my %hash;
+
+    if ($line =~ /$self->{thirdline}/o) {
+
+        my @line = split(/\s/, $3);
+
+        $hash{hour} = $1;
+        $hash{min}  = $2;
+        $hash{nick} = remove_prefix($line[0]);
+
+        if ($#line >= 7 && ($line[1].$line[2].$line[3]) eq 'hasbeenkicked') {
+            $hash{kicker} = $line[7];
+
+        } elsif ($#line >= 4 && ($line[1].$line[2] eq 'Topicchanged')) {
+            $hash{newtopic} = join(' ', @line[5..$#line]);
+            $hash{nick} = remove_prefix($line[4]);
+            $hash{nick} =~ s/:$//;
+
+        } elsif ($#line >= 3 && ($line[1].$line[2]) eq 'setmode') {
+            $hash{newmode} = $line[3];
+
+        } elsif ($#line >= 3 && ($line[2].$line[3]) eq 'hasjoined') {
+            $hash{newjoin} = $line[0];
+
+        } elsif ($#line >= 5 && ($line[2].$line[3]) eq 'nowknown') {
+            $hash{newnick} = $line[5];
+            $hash{newnick} =~ s/\.$//;
+        }
+
+        return \%hash;
+
+    } else {
+        return;
+    }
+}
+
+sub remove_prefix
+{
+    my ($str) = @_;
+
+    $str =~ s/^[@%\+]//o;
+
+    return $str;
+}
+
+1;