version 0.71
[irssistats] / irssistats.c
index ae6b2a8..6547005 100644 (file)
@@ -1,14 +1,42 @@
-/* Usage: irssistats [/path/to/file.conf] */
+/*
+ * irssistats version 0.71
+ *
+ * This tool generates IRC stats based on irssi logs.
+ * Usage: irssistats [/path/to/file.conf]
+ *
+ * Copyright (C) 2002-2004  Antoine Jacquet <royale@zerezo.com>
+ * http://royale.zerezo.com/irssistats/
+ *
+ * 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
+ */
+
 
 #include <sys/types.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <time.h>
 #include <string.h>
+#ifdef __WIN32__
+#define GLOBALCONF "irssistats.conf"
+#else
+#define GLOBALCONF "/etc/irssistats.conf"
 #include <regex.h>
+#endif
 
 /* Config */
-#define MAXUSERS 10000
+#define BASEUSERS 1000
 #define MAXNICKLENGTH 50
 #define MAXLINELENGTH 2000
 #define MAXQUOTELENGTH 100
@@ -20,7 +48,7 @@
 #define MINWORDLENGTH 5
 
 /* irssistats */
-#define VERSION "0.5"
+#define VERSION "0.71"
 #define URL "http://royale.zerezo.com/irssistats/"
 
 /* Counters */
 char *counters[NBCOUNTERS]={"C_SMILE","C_FROWN","C_EXCLAM","C_QUESTION","C_ME","C_TOPIC","C_MODE","C_KICK","C_KICKED","C_URL","C_JOIN","C_NICK","C_MONOLOGUE"};
 
 /* Languages */
-#define NBLANGUAGES 8
-#define NBKEYS 38
+#define NBLANGUAGES 11
+#define NBKEYS 39
 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
 {
   { /* English language */
     { "English",      "en" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statistics for %s by %s" },
     { "LEGEND",       "Legend" },
     { "LASTDAYS",     "Lastdays statistics" },
@@ -88,6 +117,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
   },
   { /* French language */
     { "French",       "fr" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statistiques de %s par %s" },
     { "LEGEND",       "L&eacute;gende" },
     { "LASTDAYS",     "Statistiques des derniers jours" },
@@ -130,6 +160,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
   { /* German language */
     /* contributed by Valentin Gelhorn <valentin.gelhorn@web.de> */
     { "German",       "de" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statistiken f&uuml;r %s von %s" },
     { "LEGEND",       "Legende" },
     { "LASTDAYS",     "Statistik der letzten Tage" },
@@ -172,6 +203,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
   { /* Spanish language */
     /* contributed by Alex <ainaker@gmx.net> */
     { "Spanish",      "es" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Estad&iacute;sticas de %s por %s" },
     { "LEGEND",       "Leyenda" },
     { "LASTDAYS",     "Estad&iacute;sticas de los &uacute;ltimos d&iacute;as" },
@@ -212,8 +244,52 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "C_MONOLOGUE",  "Habla solo" }
   },
   { /* Polish language */
-    /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
+    /* contributed by Jakub Jankowski <shasta@atn.pl> */
     { "Polish",       "pl" },
+    { "CHARSET",      "ISO-8859-2" },
+    { "HEADER",       "Statystyki dla %s zebrane przez %s" },
+    { "LEGEND",       "Legenda" },
+    { "LASTDAYS",     "Statystyki z ostatnich dni" },
+    { "TOPHOURS",     "Statystyki godzinowe" },
+    { "TOPUSERS",     "Najaktywniejsi" },
+    { "OTHERS",       "Jest jeszcze %d nie sklasyfikowanych..." },
+    { "NBLINES",      "linii" },
+    { "NICK",         "nick" },
+    { "AVGLETTERS",   "liter/liniê" },
+    { "HOURS",        "godziny" },
+    { "QUOTE",        "losowa wypowied¼" },
+    { "TOPUSERSTIME", "Najaktywniejsi wed³ug pory dnia" },
+    { "RANDTOPICS",   "Kilka topików" },
+    { "CHANGEDBY",    "ustawiony przez" },
+    { "NEWTOPIC",     "topik" },
+    { "RANDURLS",     "Kilka URL-i" },
+    { "POSTEDBY",     "poda³(a)" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "Najczê¶ciej wystêpuj±ce s³owa" },
+    { "WORD",         "s³owo" },
+    { "OCCURRENCES",  "wyst±pieñ" },
+    { "BIGNUMBERS",   "Kilka wielkopomnych liczb..." },
+    { "NUMBERS",      "kategorie" },
+    { "TIME",         "Dokonano analizy %d linii (obejmuj±cych %d dni) w czasie %d sekund" },
+    { "FOOTER",       "Statystyki wygenerowane przez" },
+    { "C_SMILE",      "jest czêsto szczesliwy(a) :)" },
+    { "C_FROWN",      "jest czêsto smutny(a) :(" },
+    { "C_EXCLAM",     "czêsto KRZYCZY!" },
+    { "C_QUESTION",   "zadaje du¿o pytañ?" },
+    { "C_ME",         "lubi u¿ywaæ /me" },
+    { "C_TOPIC",      "czêsto zmienia topik" },
+    { "C_MODE",       "czêsto zmienia tryby kana³u" },
+    { "C_KICK",       "lubi kopaæ" },
+    { "C_KICKED",     "czêsto wykopywany(a)" },
+    { "C_URL",        "podaje du¿o URL-i" },
+    { "C_JOIN",       "nie wie czy zostaæ, czy wyj¶æ" },
+    { "C_NICK",       "czêsto zmienia nick" },
+    { "C_MONOLOGUE",  "uwielbia monologi" }
+  },
+  { /* Polish language */
+    /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
+    { "Polish",       "pl-old" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statystyki dla %s przez %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statystyki z ostatnich dni" },
@@ -256,6 +332,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
   { /* Finnish language */
     /* contributed by Antti Huopana <ahuopana@ratol.fi> */
     { "Finnish",      "fi" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Kanavan %s tilastot - %s" },
     { "LEGEND",       "Merkkien selitykset" },
     { "LASTDAYS",     "Viime päivien tilastot" },
@@ -298,6 +375,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
   { /* Italian language */
     /* contributed by Coviello Giuseppe <giuseppecoviello@tin.it> <http://coviello.altervista.org> */
     { "Italian",      "it" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statistiche per il canale %s di %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statistiche degli ultimi giorni" },
@@ -339,13 +417,15 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
   },
   { /* Dutch language */
     /* contributed by Jeroen Ubbink <crasp@blackbyte.nl> */
+    /* updated by Wouter Horré <wouter@ligezin.be> */
     { "Dutch",        "nl" },
+    { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statistieken voor %s door %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statistieken van de laatste dagen" },
     { "TOPHOURS",     "Statistieken per uur" },
     { "TOPUSERS",     "Meest actieve mensen" },
-    { "OTHERS",       "Er zijn nog %d niet in de top..." },
+    { "OTHERS",       "Er zijn nog %d mensen die de top niet haalden..." },
     { "NBLINES",      "regels" },
     { "NICK",         "nick" },
     { "AVGLETTERS",   "letters/lijn" },
@@ -364,7 +444,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "BIGNUMBERS",   "Enkele grote aantallen..." },
     { "NUMBERS",      "numbers" },
     { "TIME",         "%d regels (%d dagen) verwerkt in %d seconden" },
-    { "FOOTER",       "Statistieken gegenereert door" },
+    { "FOOTER",       "Statistieken gegenereerd door" },
     { "C_SMILE",      "is vaak vrolijk :)" },
     { "C_FROWN",      "is vaak droevig :(" },
     { "C_EXCLAM",     "schreeuwt veel !" },
@@ -378,7 +458,93 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "C_JOIN",       "twijfelt tussen blijven of gaan" },
     { "C_NICK",       "verandert vaak van nick" },
     { "C_MONOLOGUE",  "spreekt veel monologen" }
-  }  
+  },
+  { /* Russian language */
+    /* contributed by kamikaze <kamikaze@rss.lv> */
+    { "Russian",      "ru" },
+    { "CHARSET",      "KOI8-R" },
+    { "HEADER",       "óÔÁÔÉÓÔÉËÁ ÄÌÑ %s ÏÔ %s" },
+    { "LEGEND",       "ïÂÏÚÎÁÞÅÎÉÑ" },
+    { "LASTDAYS",     "óÔÁÔÉÓÔÉËÁ ÐÏÓÌÅÄÎÉÈ ÄÎÅÊ" },
+    { "TOPHOURS",     "ðÏÞÁÓÏ×ÁÑ ÓÔÁÔÉÓÔÉËÁ" },
+    { "TOPUSERS",     "áËÔÉ×ÎÅÊÛÉÅ ÌÀÄÉ" },
+    { "OTHERS",       "ïÓÔÁÌÏÓØ %d ÎÅÐÏÄÓÞÉÔÁÎÙÈ..." },
+    { "NBLINES",      "ÓÔÒÏËÉ" },
+    { "NICK",         "ÎÉË" },
+    { "AVGLETTERS",   "ÂÕË×Ù/ÓÔÒÏËÉ" },
+    { "HOURS",        "ÞÁÓÙ" },
+    { "QUOTE",        "ÓÌÕÞÁÊÎÏÅ ÓÏÏÂÝÅÎÉÅ" },
+    { "TOPUSERSTIME", "áËÔÉ×ÎÅÊÛÉÅ ÌÀÄÉ ÐÏ ×ÒÅÍÅÎÉ ÄÎÑ" },
+    { "RANDTOPICS",   "îÅÓËÏÌØËÏ ÔÏÐÉËÏ×" },
+    { "CHANGEDBY",    "ÉÚÍÅΣÎ" },
+    { "NEWTOPIC",     "ÎÏ×ÙÊ ÔÏÐÉË" },
+    { "RANDURLS",     "îÅÓËÏÌØËÏ URLÏ×" },
+    { "POSTEDBY",     "ÏÐÕÂÌÉËÏ×ÁÌ" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "þÁÓÔÏ ÉÓÐÏÌØÚÕÅÍÙÅ ÓÌÏ×Á" },
+    { "WORD",         "ÓÌÏ×Ï" },
+    { "OCCURRENCES",  "ÐÒÏÉÛÅÓÔ×ÉÑ" },
+    { "BIGNUMBERS",   "îÅÓËÏÌØËÏ ÂÏÌØÛÉÈ ÞÉÓÅÌ..." },
+    { "NUMBERS",      "ÞÉÓÌÁ" },
+    { "TIME",         "%d ÓÔÒÏË (%d ÄÎÅÊ) ÏÂÒÁÂÏÔÁÎÏ ÚÁ %d ÓÅËÕÎÄ" },
+    { "FOOTER",       "óÔÁÔÉÓÔÉËÁ ÓÇÅÎÅÒÉÒÏ×ÁÎÁ" },
+    { "C_SMILE",      "ÞÁÓÔÏ ÓÞÁÓÌÉ× :)" },
+    { "C_FROWN",      "ÞÁÓÔÏ ÎÅÓÞÁÓÔÅΠ:(" },
+    { "C_EXCLAM",     "ÍÎÏÇÏ ×ÏÓËÌÉÃÁÅÔ !" },
+    { "C_QUESTION",   "ÚÁÄÁ£Ô ÍÎÏÇÏ ×ÏÐÒÏÓÏ× ?" },
+    { "C_ME",         "ÌÀÂÉÔ /me command" },
+    { "C_TOPIC",      "ÞÁÓÔÏ ÍÅÎÑÅÔ ÔÏÐÉË" },
+    { "C_MODE",       "ÞÁÓÔÏ ÍÅÎÑÅÔ ÒÅÖÉÍÙ" },
+    { "C_KICK",       "ÌÀÂÉÔ /kick" },
+    { "C_KICKED",     "ÞÁÓÔÏ ×ÙËÉÄÙ×ÁÀÔ" },
+    { "C_URL",        "ÐÕÂÌÉËÕÅÔ ÍÎÏÇÏ URLÏ×" },
+    { "C_JOIN",       "ÎÅ ÚÎÁÅÔ - ÏÓÔÁÔØÓÑ ÉÌÉ ÕÊÔÉ" },
+    { "C_NICK",       "ÞÁÓÔÏ ÍÅÎÑÅÔ Ó×ÏÊ ÎÉË" },
+    { "C_MONOLOGUE",  "éÓÐÏÌØÚÕÅÔ ÍÎÏÇÏ ÍÏÎÏÌÏÇÏ×" }
+  },
+  { /* Estonian language */
+    /* contributed by Martin Vool <mardicas@hot.ee> */
+    { "Estonian",     "et" },
+    { "CHARSET",      "ISO-8859-4" },
+    { "HEADER",       "Statistika kanalile %s on koostanud %s" },
+    { "LEGEND",       "Legend" },
+    { "LASTDAYS",     "Viimaste päevade statistika" },
+    { "TOPHOURS",     "Tunni statistika" },
+    { "TOPUSERS",     "Kõige aktiivsemad inimesed" },
+    { "OTHERS",       "%d inimest on rääkinud" },
+    { "NBLINES",      "rida" },
+    { "NICK",         "nimi" },
+    { "AVGLETTERS",   "tähte/rida" },
+    { "HOURS",        "kell" },
+    { "QUOTE",        "suvaline teade" },
+    { "TOPUSERSTIME", "Kõige aktiivsemad inimesed päeva aja järgi" },
+    { "RANDTOPICS",   "Mõned topicud" },
+    { "CHANGEDBY",    "muutis" },
+    { "NEWTOPIC",     "topicud" },
+    { "RANDURLS",     "Mõned aadressid" },
+    { "POSTEDBY",     "postitas" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "Enim kasutatud sõnad" },
+    { "WORD",         "sõna" },
+    { "OCCURRENCES",  "sagedus" },
+    { "BIGNUMBERS",   "Mõned suured numbrid" },
+    { "NUMBERS",      "iseloom" },
+    { "TIME",         "%d rida (%d päeva) on möödunud %d sekundit" },
+    { "FOOTER",       "Statistika on koostanud" },
+    { "C_SMILE",      "on tihti õnnelik :)" },
+    { "C_FROWN",      "on tihti kurb :(" },
+    { "C_EXCLAM",     "põrnitseb palju" },
+    { "C_QUESTION",   "küsib palju küsimusi" },
+    { "C_ME",         "/me manjakk" },
+    { "C_TOPIC",      "vahetab tihti topicut" },
+    { "C_MODE",       "vahetab tihti modesid" },
+    { "C_KICK",       "kickib palju" },
+    { "C_KICKED",     "saab tihti kicke" },
+    { "C_URL",        "reklaamib palju" },
+    { "C_JOIN",       "sõelub sisse ja välja" },
+    { "C_NICK",       "vahetab pidevalt nime" },
+    { "C_MONOLOGUE",  "räägib palju monolooge" }
+  }
 };
 
 int language=0; /* default to english */
@@ -396,39 +562,47 @@ char *L(char *key)
 int debug=1; /* 0 = none ; 1 = normal ; 2 = verbose */
 char channel[MAXLINELENGTH]="set_channel_in_config_file";
 char maintainer[MAXLINELENGTH]="set_maintainer_in_config_file";
-char theme[MAXLINELENGTH]="default";
+char theme[MAXLINELENGTH]="default,biseau,blue,dark,damier,grayscale,namour,niflheim,pisg,zeduel,zerezo";
 int refresh_time=0; /* 0 = disabled */
 int w3c_link=1; /* 0 = disabled */
+int logo=1; /* 0 = disabled */
 char header[MAXLINELENGTH]="none";
 char footer[MAXLINELENGTH]="none";
 int totallines=0;
 time_t debut;
+int top_words=1; /* 0 = disabled */
+int ranking=0; /* 0 = lines ; 1 = words ; 2 = letters */
+int quarter=0; /* 1 = enabled */
+int photo_size=60;
 
-struct
+struct user
 {
   char nick[MAXNICKLENGTH];
   int lines;
+  int words;
   int letters;
   int hours[4];
   char quote[MAXQUOTELENGTH+1];
   int counters[NBCOUNTERS];
+  char *photo;
   int temp;
-} users[MAXUSERS];
+} *users;
 int nbusers=0;
+int maxusers=BASEUSERS;
 
 struct
 {
   char nick[MAXNICKLENGTH];
   char url[MAXLINELENGTH];
   char shorturl[MAXQUOTELENGTH+1];
-} urls[5];
+} urls[NBURLS];
 int nburls=0;
 
 struct
 { 
   char nick[MAXNICKLENGTH];
   char topic[MAXQUOTELENGTH+1];
-} topics[5];
+} topics[NBTOPICS];
 int nbtopics=0;
 
 struct
@@ -438,7 +612,7 @@ struct
 } lastdays[31];
 int days=0;
 
-int hours[24];
+int hours[24*4];
 int lines=0;
 
 struct letter
@@ -455,13 +629,13 @@ struct
 
 #define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
 #define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
-void findwords(char *message)
+int findwords(char *message)
 {
-  int i,c;
+  int i,c,n=0;
   struct letter *pos,*tmp;
   for (;;)
   {
-    while (!isletter(*message)) if (*message=='\0') return; else message++;
+    while (!isletter(*message)) if (*message=='\0') return n; else message++;
     pos=&words;
     while (isletter(*message))
     {
@@ -469,6 +643,11 @@ void findwords(char *message)
       if (pos->next[(int)c]==NULL)
       {
         tmp=malloc(sizeof(struct letter));
+        if (tmp==NULL)
+        {
+          fprintf(stderr, "findwords(): malloc failure\n");
+          exit(1);
+        }
         tmp->nb=0;
         for (i=0;i<26;i++) tmp->next[i]=NULL;
         pos->next[(int)c]=tmp;
@@ -477,8 +656,9 @@ void findwords(char *message)
       message++; 
     }
     pos->nb++;
+    n++;
   }
-  return;
+  return n;
 }
 
 char tempword[MAXLINELENGTH];
@@ -542,24 +722,33 @@ int dichotomic(char *nick)
   if (strcmp(nick,users[start].nick)!=0)
   {
     nbusers++;
-    if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
+    if (nbusers>=maxusers)
+    {
+      maxusers+=BASEUSERS;
+      if (debug==2) fprintf(stderr,"allocating more users : %d\n",maxusers);
+      if ((users=realloc(users,maxusers*sizeof(struct user)))==NULL) { fprintf(stderr,"too many users : unable to realloc memory\n"); exit(1); }
+    }
     for (i=nbusers-1;i>start;i--)
     {
       strcpy(users[i].nick,users[i-1].nick);
       users[i].lines=users[i-1].lines;
+      users[i].words=users[i-1].words;
       users[i].letters=users[i-1].letters;
       for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
       strcpy(users[i].quote,users[i-1].quote);
       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=users[i-1].counters[j];
       users[i].temp=users[i-1].temp;
+      users[i].photo=users[i-1].photo;
     }
     strcpy(users[start].nick,nick);
     users[start].lines=0;
+    users[start].words=0;
     users[start].letters=0;
     for (j=0;j<4;j++) users[start].hours[j]=0;
     users[start].quote[0]='\0';
     for (j=0;j<NBCOUNTERS;j++) users[start].counters[j]=0;
     users[start].temp=0;
+    users[start].photo=NULL;
   }
   return(start);
 }
@@ -580,8 +769,19 @@ void parse_log(char *logfile)
   while (fgets(line,MAXLINELENGTH,fic)!=NULL)
   {
     /* remove \n */
-    for (i=0;line[i]!=0;i++);
-    if (i>=MAXLINELENGTH-1) { fprintf(stderr,"line %d is too long\n",totallines); exit(1); }
+    for (i=0;line[i]!=0 && i<MAXLINELENGTH-1;i++);
+    if (i>=MAXLINELENGTH-1) { 
+      if(debug){
+        fprintf(stderr,"line %d is too long, skipping\n",totallines+1); 
+      }
+      continue; 
+    }
+    if (i<8) {
+      if(debug) {
+        fprintf(stderr, "line %d is too short to be valid, skipping\n",totallines+1);
+      }
+      continue;
+    }
     line[i-1]='\0';
     pos=0;
     totallines++;
@@ -605,7 +805,13 @@ void parse_log(char *logfile)
     }
     else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
     {
-      for (i=10;line[i]!=' ';i++);
+      for (i=10;line[i]!=' ' && i <= 10 + MAXNICKLENGTH;i++);
+      if(i > 10 + MAXNICKLENGTH) {
+        if(debug) {
+          fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+        }
+        continue;
+      }
       line[i]='\0';
       nick=&line[10];
       message=&line[i+1];
@@ -650,14 +856,26 @@ void parse_log(char *logfile)
       hour=atoi(line);
       if (line[7]=='*') /* 00:00  * Nick the message */
       {
-        for (i=9;line[i]!=' ';i++);
+        for (i=9;line[i]!=' ' && i <= 9 + MAXNICKLENGTH;i++);
+        if(i > 9 + MAXNICKLENGTH) {
+          if(debug) {
+            fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+          }
+          continue; 
+        }
         nick=&line[9];
         message=&line[i+1];
       }
       else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
                              /* 00:00 <>>?Nick<<> the personal message */
       {
-        for (i=10;line[i]!='<';i++);
+        for (i=10;line[i]!='<' && i <= 10 + MAXNICKLENGTH;i++);
+         if(i > 10 + MAXNICKLENGTH) { 
+          if(debug) {
+            fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+          }
+          continue; 
+        }
         nick=&line[10];
         if (line[9]=='>') nick++;
         message=&line[i+5];
@@ -675,7 +893,13 @@ void parse_log(char *logfile)
             nickstart = 7;
         }
           
-        for (i=nickstart;line[i]!='>';i++);
+        for (i=nickstart;line[i]!='>' && i <= nickstart + MAXNICKLENGTH;i++);
+        if(i > nickstart + MAXNICKLENGTH) { 
+          if(debug) {
+            fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+          }
+          continue; 
+        }
         nick=&line[nickstart];
         message=&line[i+2];
       }
@@ -694,11 +918,17 @@ void parse_log(char *logfile)
       }
       j=strlen(message);
       users[i].lines++;
+      if (top_words || ranking==1) users[i].words+=findwords(message);
       users[i].letters+=j;
       users[i].hours[hour/6]++;
       lastdays[0].lines++;
       lastdays[0].hours[hour/6]++;
       lines++;
+      if (quarter)
+      {
+        line[5]='\0';
+        hour=hour*4+atoi(&line[3])/15;
+      }
       hours[hour]++;
       if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
       else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
@@ -722,7 +952,6 @@ void parse_log(char *logfile)
           strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
         }
       }
-      findwords(message);
     }
     pos=0;
   }
@@ -730,6 +959,7 @@ void parse_log(char *logfile)
   if (debug) printf(" done\n");
 }
 
+#ifndef __WIN32__
 void parse_nick(char *nickfile)
 {
   FILE *fic;
@@ -753,11 +983,13 @@ void parse_nick(char *nickfile)
         users[user].temp=users[i].temp;
       }
       users[user].lines+=users[i].lines;
+      users[user].words+=users[i].words;
       users[user].letters+=users[i].letters;
       for (j=0;j<4;j++) users[user].hours[j]+=users[i].hours[j];
       for (j=0;j<NBCOUNTERS;j++) users[user].counters[j]+=users[i].counters[j];
       /* "remove" old user */
       users[i].lines=-1;
+      users[i].words=-1;
       users[i].letters=-1;
       for (j=0;j<4;j++) users[i].hours[j]=-1;
       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
@@ -768,10 +1000,29 @@ void parse_nick(char *nickfile)
   /* "remove" the ignored nicks */
   i=dichotomic("<NULL>");
   users[i].lines=-1;
+  users[i].words=-1;
   users[i].letters=-1;
   for (j=0;j<4;j++) users[i].hours[j]=-1;
   for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
 }
+#endif
+
+void parse_photo(char *photofile)
+{
+  FILE *fic;
+  char line[MAXLINELENGTH];
+  int user;
+  
+  if ((fic=fopen(photofile,"rt"))==NULL) { fprintf(stderr,"can't open photo file \"%s\"\n",photofile); exit(1); }
+  while (fscanf(fic,"%s",line)==1)
+  {
+    user=dichotomic(line);
+    fscanf(fic,"%s",line);
+    users[user].photo=malloc(strlen(line)+1);
+    strcpy(users[user].photo,line);
+  }
+  fclose(fic);
+}
 
 void gen_xhtml(char *xhtmlfile)
 {
@@ -780,6 +1031,10 @@ void gen_xhtml(char *xhtmlfile)
   int i,j,k;
   int user,max,temp;
   char line[MAXLINELENGTH];
+  char *subtheme;
+  int photos=0;
+  
+  for (i=0;i<nbusers;i++) if (users[i].photo!=NULL) photos=1;
   
   if ((fic=fopen(xhtmlfile,"wt"))==NULL) { fprintf(stderr,"can't open xhtml file \"%s\"\n",xhtmlfile); exit(1); }
   
@@ -790,10 +1045,13 @@ void gen_xhtml(char *xhtmlfile)
     fprintf(fic,"<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
     fprintf(fic,"<html>\n\n<head>\n<title>");
     fprintf(fic,L("HEADER"),channel,maintainer);
-    fprintf(fic,"</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />\n");
+    fprintf(fic,"</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n",L("CHARSET"));
     if (refresh_time)
       fprintf(fic,"<meta http-equiv=\"Refresh\" content=\"%d\" />\n",refresh_time);
-    fprintf(fic,"<link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\" />\n",theme);
+    subtheme=strtok(theme,",");
+    fprintf(fic,"<link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\" title=\"%s\" />\n",subtheme,subtheme);
+    while ((subtheme=strtok(NULL,","))!=NULL)
+      fprintf(fic,"<link rel=\"alternate stylesheet\" type=\"text/css\" href=\"%s.css\" title=\"%s\" />\n",subtheme,subtheme);
     fprintf(fic,"</head>\n\n");
     fprintf(fic,"<body>\n\n");
   }
@@ -818,7 +1076,7 @@ void gen_xhtml(char *xhtmlfile)
   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
   for (i=30;i>=0;i--)
   {
-    fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%d</small>",lastdays[i].lines); /* width=\"15\" */
+    fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%d</small>",lastdays[i].lines);
     for (j=0;j<4;j++) if (lastdays[i].hours[j]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",j+1,150*lastdays[i].hours[j]/max);
     fprintf(fic,"</td>\n");
   }
@@ -830,34 +1088,84 @@ void gen_xhtml(char *xhtmlfile)
   /* top hours */
   fprintf(fic,"<div id=\"irssistats_tophours\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("TOPHOURS"));
   max=-1;
-  for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
-  for (i=0;i<24;i++)
+  for (i=0;i<24*4;i++) if (hours[i]>max) max=hours[i];
+  if (quarter) for (i=0;i<24*4;i++)
+  {
+    fprintf(fic,"<td align=\"center\" valign=\"bottom\">");
+    if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"width:4px;height:%dpx\"></div>",i/4/6+1,150*hours[i]/max);
+    fprintf(fic,"</td>\n");
+  }
+  else for (i=0;i<24;i++)
   {
-    fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%.1f%%</small>",lines!=0?(float)100*hours[i]/lines:0); /* width=\"15\" */
+    fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%.1f%%</small>",lines!=0?(float)100*hours[i]/lines:0);
     if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",i/6+1,150*hours[i]/max);
     fprintf(fic,"</td>\n");
   }
   fprintf(fic,"</tr>\n<tr>\n");
   for (i=0;i<24;i++)
-    fprintf(fic,"<th>%d</th>\n",i);
+    if (quarter) fprintf(fic,"<th colspan=\"4\">%d</th>\n",i);
+    else fprintf(fic,"<th>%d</th>\n",i);
   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
   
   /* top users */
   fprintf(fic,"<div id=\"irssistats_topusers\">\n<h2>%s</h2>\n",L("TOPUSERS"));
-  fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th><th>%s</th><th colspan=\"2\">%s</th><th>%s</th></tr>\n",L("NICK"),L("NBLINES"),L("HOURS"),L("AVGLETTERS"),L("QUOTE"));
+  switch (ranking)
+  {
+    case 0:
+      fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th><th>%s</th><th colspan=\"2\">%s</th><th>%s</th>\n",L("NICK"),L("NBLINES"),L("HOURS"),L("AVGLETTERS"),L("QUOTE"));
+      break;
+    default:
+      /* "letters" and "words" ranking are not yet translated so we use the generic word "rank" ... */
+      fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th><th>%s</th><th colspan=\"2\">%s</th><th>%s</th>\n",L("NICK"),"rank",L("HOURS"),L("AVGLETTERS"),L("QUOTE"));
+      break;
+  }
+  if (photos) fprintf(fic,"<th></th>");
+  fprintf(fic,"</tr>");
   for (i=1;i<=NBUSERS;i++)
   {
     user=-1;
     max=0;
-    for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
+    switch (ranking)
+    {
+      case 0:
+        for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
+        break;
+      case 1:
+        for (j=0;j<nbusers;j++) if (users[j].words>max) max=users[user=j].words;
+        break;
+      case 2:
+        for (j=0;j<nbusers;j++) if (users[j].letters>max) max=users[user=j].letters;
+        break;
+    }
     if (user!=-1)
     {
-      fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].lines);
+      switch (ranking)
+      {
+        case 0:
+          fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].lines);
+          break;
+        case 1:
+          fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].words);
+          break;
+        case 2:
+          fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].letters);
+          break;
+      }
       for (j=0;j<4;j++) if (users[user].hours[j]!=0) fprintf(fic,"<div class=\"h%d\" style=\"width:%dpx\"></div>",j+1,100*users[user].hours[j]/users[user].lines);
       fprintf(fic,"</td><td>%d</td><td><div class=\"hm\" style=\"width:%dpx\"></div></td><td>\"",users[user].lines!=0?users[user].letters/users[user].lines:0,users[user].lines!=0?users[user].letters/users[user].lines:0);
       printhtml(fic,users[user].quote);
-      fprintf(fic,"\"</td></tr>\n");
+      fprintf(fic,"\"</td>");
+      if (photos && users[user].photo!=NULL)
+      {
+        if (photo_size)
+          fprintf(fic,"<td class=\"tdphoto\"><a href=\"%s\"><img src=\"%s\" class=\"imgphoto\" width=\"%d\" height=\"%d\" alt=\"\" style=\"border: 0\" /></a></td>",users[user].photo,users[user].photo,photo_size,photo_size);
+        else
+          fprintf(fic,"<td class=\"tdphoto\"><img src=\"%s\" class=\"imgphoto\" alt=\"\" /></td>",users[user].photo,users[user].photo);
+      }
+      fprintf(fic,"</tr>\n");
       users[user].lines=-1;
+      users[user].words=-1;
+      users[user].letters=-1;
     }    
   }
   fprintf(fic,"</table>\n");
@@ -920,12 +1228,15 @@ void gen_xhtml(char *xhtmlfile)
   fprintf(fic,"</table>\n</div>\n\n");
   
   /* top words */
-  fprintf(fic,"<div id=\"irssistats_topwords\">\n<h2>%s</h2>\n",L("TOPWORDS"));
-  fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th></tr>\n",L("WORD"),L("OCCURRENCES"));
-  for (i=0;i<NBWORDS;i++)
-    if (topwords[i].nb!=0) fprintf(fic,"<tr><td>%d</td><td>\"%s\"</td><td>%d</td></tr>\n",i+1,topwords[i].word,topwords[i].nb);
-  fprintf(fic,"</table>\n</div>\n\n");
-
+  if (top_words)
+  {
+    fprintf(fic,"<div id=\"irssistats_topwords\">\n<h2>%s</h2>\n",L("TOPWORDS"));
+    fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th></tr>\n",L("WORD"),L("OCCURRENCES"));
+    for (i=0;i<NBWORDS;i++)
+      if (topwords[i].nb!=0) fprintf(fic,"<tr><td>%d</td><td>\"%s\"</td><td>%d</td></tr>\n",i+1,topwords[i].word,topwords[i].nb);
+    fprintf(fic,"</table>\n</div>\n\n");
+  }
+  
   /* big numbers */
   fprintf(fic,"<div id=\"irssistats_bignumbers\">\n<h2>%s</h2>\n",L("BIGNUMBERS"));
   fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th><th>%s</th></tr>\n",L("NICK"),L("NUMBERS"),L("NBLINES"));
@@ -947,10 +1258,15 @@ void gen_xhtml(char *xhtmlfile)
     fprintf(fic,"<p>\n<a href=\"http://validator.w3.org/check/referer\"><img src=\"valid-xhtml10.png\" height=\"31\" width=\"88\" alt=\"Valid XHTML 1.0!\" /></a>\n");
     fprintf(fic,"<a href=\"http://jigsaw.w3.org/css-validator/check/referer\"><img src=\"valid-css.png\" height=\"31\" width=\"88\" alt=\"Valid CSS!\" /></a>\n</p>\n");
   }
-  fprintf(fic,"</div>\n\n</div>");
-  if (strcmp("none",header)==0)
+  fprintf(fic,"</div>\n\n");
+  
+  /* logo*/
+  if (logo) fprintf(fic,"<div class=\"logo\"></div>\n\n");
+  
+  /* end */
+  if (strcmp("none",footer)==0)
   {
-    fprintf(fic,"\n\n</body>\n\n</html>\n");
+    fprintf(fic,"</div>\n\n</body>\n\n</html>\n");
   }
   else
   {
@@ -964,6 +1280,17 @@ void gen_xhtml(char *xhtmlfile)
 
 void parse_config(char *configfile)
 {
+  void expand(char *path)
+  {
+    char temp[MAXLINELENGTH];
+    if (*path=='~')
+    {
+      snprintf(temp,MAXLINELENGTH-1,"%s%s",getenv("HOME"),path+1);
+      temp[MAXLINELENGTH-1]='\0';
+      strcpy(path,temp);
+    }
+  }
+  
   FILE *fic;
   char line[MAXLINELENGTH];
   char keyword[MAXLINELENGTH];
@@ -981,11 +1308,12 @@ void parse_config(char *configfile)
   }
   else
   {
-    sprintf(line,"%s/.irssistats",getenv("HOME"));
+    snprintf(line,MAXLINELENGTH-1,"%s/.irssistats",getenv("HOME"));
+    line[MAXLINELENGTH-1]='\0';
     if ((fic=fopen(line,"rt"))==NULL)
-      if ((fic=fopen("/etc/irssistats.conf","rt"))==NULL)
+      if ((fic=fopen(GLOBALCONF,"rt"))==NULL)
       {
-        fprintf(stderr,"can't find config file : \"%s\" nor \"/etc/irssistats.conf\"\n",line);
+        fprintf(stderr,"can't find config file : \"%s\" nor \"" GLOBALCONF "\"\n",line);
         fprintf(stderr,"please give the path to the config file in argument\n");
         exit(1);
       }
@@ -1006,7 +1334,8 @@ void parse_config(char *configfile)
         else
         if (strcmp("verbose",value)==0) { debug=2; fprintf(stderr,"switching to verbose output\n"); }
         else { fprintf(stderr,"unknown value for \"debug\" option, must be \"normal\", \"verbose\" or \"none\"\n"); exit(1); }
-      } else
+      }
+      else
       
       if (strcmp("channel",keyword)==0)
       {
@@ -1050,6 +1379,13 @@ void parse_config(char *configfile)
       }
       else
       
+      if (strcmp("photo_size",keyword)==0)
+      {
+        photo_size=atoi(value);
+        if (debug==2) fprintf(stderr,"setting photo_size to \"%d\"\n",photo_size);
+      }
+      else
+      
       if (strcmp("w3c_link",keyword)==0)
       {
         if (debug==2) fprintf(stderr,"setting w3c_link to \"%s\"\n",value);
@@ -1059,8 +1395,18 @@ void parse_config(char *configfile)
       }
       else
       
+      if (strcmp("logo",keyword)==0)
+      {
+        if (debug==2) fprintf(stderr,"setting logo to \"%s\"\n",value);
+        if (strcmp("no",value)==0) logo=0;
+        else if (strcmp("yes",value)==0) logo=1;
+        else { fprintf(stderr,"unknown value for \"logo\" option, must be \"yes\" or \"no\"\n"); exit(1); }
+      }
+      else
+      
       if (strcmp("header",keyword)==0)
       {
+        expand(value);
         if (debug==2) fprintf(stderr,"setting header to \"%s\"\n",value);
         strcpy(header,value);
       }
@@ -1068,6 +1414,7 @@ void parse_config(char *configfile)
       
       if (strcmp("footer",keyword)==0)
       {
+        expand(value);
         if (debug==2) fprintf(stderr,"setting footer to \"%s\"\n",value);
         strcpy(footer,value);
       }
@@ -1075,6 +1422,7 @@ void parse_config(char *configfile)
       
       if (strcmp("input",keyword)==0)
       {
+        expand(value);
         if (debug==2) fprintf(stderr,"parsing log file \"%s\"\n",value);
         parse_log(value);
       }
@@ -1082,13 +1430,27 @@ void parse_config(char *configfile)
       
       if (strcmp("nickfile",keyword)==0)
       {
+        expand(value);
         if (debug==2) fprintf(stderr,"nick alias using file \"%s\"\n",value);
+#ifdef __WIN32__
+        fprintf(stderr,"no support for nickfile in WIN32 version\n");
+#else
         parse_nick(value);
+#endif
+      }
+      else
+      
+      if (strcmp("photofile",keyword)==0)
+      {
+        expand(value);
+        if (debug==2) fprintf(stderr,"parsing photo file \"%s\"\n",value);
+        parse_photo(value);
       }
       else
       
       if (strcmp("output",keyword)==0)
       {
+        expand(value);
         if (debug==2) fprintf(stderr,"generating xhtml file \"%s\"\n",value);
         bestwords(words,0);
         gen_xhtml(value);
@@ -1098,14 +1460,48 @@ void parse_config(char *configfile)
         nburls=0;
         nbtopics=0;
         days=0;
-        for (i=0;i<24;i++) hours[i]=0;
+        for (i=0;i<24*4;i++) hours[i]=0;
         lines=0;
         freewords(&words);
         for (i=0;i<NBWORDS;i++) topwords[i].nb=0;
         totallines=0;
         debut=time(NULL);
+        for (i=0;i<nbusers;i++)
+        {
+          free(users[i].photo);
+          users[i].photo=NULL;
+        }
+      }
+      else
+      
+      if (strcmp("top_words",keyword)==0)
+      {
+        if (debug==2) fprintf(stderr,"setting top_words to \"%s\"\n",value);
+        if (strcmp("no",value)==0) top_words=0;
+        else if (strcmp("yes",value)==0) top_words=1;
+        else { fprintf(stderr,"unknown value for \"top_words\" option, must be \"yes\" or \"no\"\n"); exit(1); }
       }
+      else
       
+      if (strcmp("ranking",keyword)==0)
+      {
+        if (strcmp("lines",value)==0) ranking=0;
+        else
+        if (strcmp("words",value)==0) ranking=1;
+        else
+        if (strcmp("letters",value)==0) ranking=2;
+        else { fprintf(stderr,"unknown value for \"ranking\" option, must be \"lines\", \"words\" or \"letters\"\n"); exit(1); }
+      }
+      else
+      
+      if (strcmp("quarter",keyword)==0)
+      {
+        if (debug==2) fprintf(stderr,"setting quarter to \"%s\"\n",value);
+        if (strcmp("no",value)==0) quarter=0;
+        else if (strcmp("yes",value)==0) quarter=1;
+        else { fprintf(stderr,"unknown value for \"quarter\" option, must be \"yes\" or \"no\"\n"); exit(1); }
+      }
+
       else { fprintf(stderr,"error in config file : \"%s\" is an unknown keyword (line %d)\n",keyword,configlines); exit(1); }        
     }
   }
@@ -1114,6 +1510,7 @@ void parse_config(char *configfile)
 
 int main(int argc,char *argv[])
 {
+  if ((users=malloc(maxusers*sizeof(struct user)))==NULL) { fprintf(stderr,"unable to malloc memory\n"); exit(1); }
   srand(debut=time(NULL));
   if (argc==1) parse_config(NULL);
   else if (argc==2) parse_config(argv[1]);