version 0.4
[irssistats] / irssistats.c
index e9a26c3..b7a855c 100644 (file)
-/* Usage: cat /path/to/file.log | ./irssistats > /path/to/file.html */
+/* Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme [nickfile] > /path/to/file.html */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <regex.h>
 
 /* Config */
-#define CHANNEL "#channel"
-#define MAINTAINER "maintainer"
+#define MAXUSERS 5000
+#define MAXNICKLENGTH 50
+#define MAXLINELENGTH 2000
+#define MAXQUOTELENGTH 100
+#define NBUSERS 50
+#define NBUSERSTIME 10
+#define NBURLS 5
+#define NBTOPICS 5
+#define NBWORDS 20
+#define MINWORDLENGTH 5
 
-/* Language */
-#define HEADER "Statistiques de %s par %s"
-#define LEGEND "L&eacute;gende"
-#define LASTDAYS "Statistiques des derniers jours"
-#define TOPHOURS "Statistiques horaires"
-#define TOPUSERS "Personnes les plus actives"
-#define OTHERS "Il reste %d personnes non class&eacute;es..."
-#define NBLINES "lignes"
-#define NICK "nick"
-#define AVGLETTERS "lettres/lignes"
-#define HOURS "heures"
-#define QUOTE "message al&eacute;atoire"
-#define RANDTOPICS "Quelques topics"
-#define CHANGEDBY "chang&eacute; par"
-#define NEWTOPIC "nouveau topic"
-#define RANDURLS "Quelques URLs"
-#define POSTEDBY "post&eacute;e par"
-#define POSTEDURL "URL"
-#define BIGNUMBERS "Quelques grands nombres..."
-#define TIME "%d lignes trait&eacute;es en %d secondes"
-#define FOOTER "Statistiques g&eacute;n&eacute;r&eacute;es par"
+/* irssistats */
+#define VERSION "0.4"
+#define URL "http://royale.zerezo.com/programmation/irssistats/"
 
-/* Colors */
-#define BGCOLOR "#FFFFFF"
-#define TEXT "#000000"
-#define LINK "#0000EE"
-#define VLINK "#551A8B"
-#define ALINK "#FF0000"
-#define TITLE1 "#000000"
-#define TITLE2 "#000000"
-#define BGTABLE "#EEEEEE"
-#define BGTITLE "#FFEEEE"
+/* Counters */
+#define D_SMILE     0
+#define D_FROWN     1
+#define D_EXCLAM    2
+#define D_QUESTION  3
+#define D_ME        4
+#define D_TOPIC     5
+#define D_MODE      6
+#define D_KICK      7
+#define D_KICKED    8
+#define D_URL       9
+#define D_JOIN      10
+#define D_NICK      11
+#define D_MONOLOGUE 12
+#define NBCOUNTERS  13
+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"};
 
-/* Dark Theme... */
-/*
-#define BGCOLOR "#000000"
-#define TEXT "#FFFFFF"
-#define LINK "#AAAAFF"
-#define VLINK "#CCCCDD"
-#define ALINK "#FFAAAA"
-#define TITLE1 "#AAAAFF"
-#define TITLE2 "#FFAAAA"
-#define BGTABLE "#225522"
-#define BGTITLE "#552222"
-*/
+/* Languages */
+#define NBLANGUAGES 4
+#define NBKEYS 38
+char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
+{
+  { /* English language */
+    { "English",      "en" },
+    { "HEADER",       "Statistics for #%s by %s" },
+    { "LEGEND",       "Legend" },
+    { "LASTDAYS",     "Lastdays statistics" },
+    { "TOPHOURS",     "Hourly statistics" },
+    { "TOPUSERS",     "Most active people" },
+    { "OTHERS",       "There are %d left not ranked..." },
+    { "NBLINES",      "lines" },
+    { "NICK",         "nick" },
+    { "AVGLETTERS",   "letters/lines" },
+    { "HOURS",        "hours" },
+    { "QUOTE",        "random message" },
+    { "TOPUSERSTIME", "Most active people by time of day" },
+    { "RANDTOPICS",   "Some topics" },
+    { "CHANGEDBY",    "changed by" },
+    { "NEWTOPIC",     "new topic" },
+    { "RANDURLS",     "Some URLs" },
+    { "POSTEDBY",     "posted by" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "Most used words" },
+    { "WORD",         "word" },
+    { "OCCURRENCES",  "occurrences" },
+    { "BIGNUMBERS",   "Some big numbers..." },
+    { "NUMBERS",      "numbers" },
+    { "TIME",         "%d lines parsed in %d seconds" },
+    { "FOOTER",       "Statistics generated by" },
+    { "C_SMILE",      "is often happy :)" },
+    { "C_FROWN",      "is often sad :(" },
+    { "C_EXCLAM",     "yells a lot !" },
+    { "C_QUESTION",   "asks a lot of questions ?" },
+    { "C_ME",         "likes /me command" },
+    { "C_TOPIC",      "often changes the topic" },
+    { "C_MODE",       "often changes the modes" },
+    { "C_KICK",       "likes to /kick" },
+    { "C_KICKED",     "is often kicked" },
+    { "C_URL",        "posts many URLs" },
+    { "C_JOIN",       "doesn't know wether to stay or quit" },
+    { "C_NICK",       "often changes his nick" },
+    { "C_MONOLOGUE",  "speaks a lot of monologues" }
+  },
+  { /* French language */
+    { "Français",     "fr" },
+    { "HEADER",       "Statistiques de #%s par %s" },
+    { "LEGEND",       "L&eacute;gende" },
+    { "LASTDAYS",     "Statistiques des derniers jours" },
+    { "TOPHOURS",     "Statistiques horaires" },
+    { "TOPUSERS",     "Personnes les plus actives" },
+    { "OTHERS",       "Il reste %d personnes non class&eacute;es..." },
+    { "NBLINES",      "lignes" },
+    { "NICK",         "nick" },
+    { "AVGLETTERS",   "lettres/lignes" },
+    { "HOURS",        "heures" },
+    { "QUOTE",        "message al&eacute;atoire" },
+    { "TOPUSERSTIME", "Personnes les plus actives par p&eacute;riode de la journ&eacutee" },
+    { "RANDTOPICS",   "Quelques topics" },
+    { "CHANGEDBY",    "chang&eacute; par" },
+    { "NEWTOPIC",     "nouveau topic" },
+    { "RANDURLS",     "Quelques URLs" },
+    { "POSTEDBY",     "post&eacute;e par" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "Mots les plus utilis&eacute;s" },
+    { "WORD",         "mot" },
+    { "OCCURRENCES",  "occurrences" },
+    { "BIGNUMBERS",   "Quelques grands nombres..." },
+    { "NUMBERS",      "nombres" },
+    { "TIME",         "%d lignes trait&eacute;es en %d secondes" },
+    { "FOOTER",       "Statistiques g&eacute;n&eacute;r&eacute;es par" },
+    { "C_SMILE",      "est souvent heureux :)" },
+    { "C_FROWN",      "est souvent triste :(" },
+    { "C_EXCLAM",     "hurle beaucoup !" },
+    { "C_QUESTION",   "pose beaucoup de questions ?" },
+    { "C_ME",         "aime la commande /me" },
+    { "C_TOPIC",      "change souvent le topic" },
+    { "C_MODE",       "change souvent les modes" },
+    { "C_KICK",       "aime la commande /kick" },
+    { "C_KICKED",     "est souvent kick&eacute;" },
+    { "C_URL",        "poste beaucoup d'URLs" },
+    { "C_JOIN",       "ne sait pas s'il doit rester ou partir" },
+    { "C_NICK",       "change souvent de nick" },
+    { "C_MONOLOGUE",  "parle beaucoup de monologues" }
+  },
+  { /* German language */
+    /* contributed by Valentin Gelhorn <valentin.gelhorn@web.de> */
+    { "German",       "de" },
+    { "HEADER",       "Statistiken f&uuml;r #%s von %s" },
+    { "LEGEND",       "Legende" },
+    { "LASTDAYS",     "Statistik der letzten Tage" },
+    { "TOPHOURS",     "St&uuml;ndliche Statistik" },
+    { "TOPUSERS",     "Die aktivsten Personen" },
+    { "OTHERS",       "Es bleiben noch %d uneingetragene" },
+    { "NBLINES",      "Zeilen" },
+    { "NICK",         "Nick" },
+    { "AVGLETTERS",   "Buchstaben/Zeile" },
+    { "HOURS",        "Stunden" },
+    { "QUOTE",        "Zuf&auml;llig ausgewaehlte Zitate" },
+    { "TOPUSERSTIME", "Die aktivsten Personen zur bestimmten Tageszeit" },
+    { "RANDTOPICS",   "Ein paar Topics" },
+    { "CHANGEDBY",    "Gesetzt von" },
+    { "NEWTOPIC",     "Neues topic" },
+    { "RANDURLS",     "Ein paar URLs" },
+    { "POSTEDBY",     "Geschrieben von" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "Am h&auml;ufigsten benutze W&ouml;rter" },
+    { "WORD",         "Wort" },
+    { "OCCURRENCES",  "Vorkommen" },
+    { "BIGNUMBERS",   "Ein paar grosse Zahlen" },
+    { "NUMBERS",      "Zahlen" },
+    { "TIME",         "%d Zeilen analysiert in %d Sekunden" },
+    { "FOOTER",       "Statistiken wurden erstellt von" },
+    { "C_SMILE",      "ist oft gl&uuml;klich :)" },
+    { "C_FROWN",      "ist oft traurig :(" },
+    { "C_EXCLAM",     "schreit oft !" },
+    { "C_QUESTION",   "stellt viele Fragen ?" },
+    { "C_ME",         "mag /me'en" },
+    { "C_TOPIC",      "aendert oft das Topico" },
+    { "C_MODE",       "aendert oft die Modes" },
+    { "C_KICK",       "mag /kick'en" },
+    { "C_KICKED",     "wird oft gekickt"},
+    { "C_URL",        "schreibt viele URLs"},
+    { "C_JOIN",       "kann sich nicht entscheiden ob er bleiben oder gehen soll" },
+    { "C_NICK",       "&auml;ndert oft seinen Nick" },
+    { "C_MONOLOGUE",  "spricht oft Monologe" }
+  },
+  { /* Spanish language */
+    /* contributed by Alex <ainaker@gmx.net> */
+    { "Spanish",      "es" },
+    { "HEADER",       "Estad&iacute;sticas de #%s por %s" },
+    { "LEGEND",       "Leyenda" },
+    { "LASTDAYS",     "Estad&iacute;sticas de los &uacute;ltimos d&iacute;as" },
+    { "TOPHOURS",     "Estad&iacute;sticas por horas" },
+    { "TOPUSERS",     "Los que m&aacute;s escriben" },
+    { "OTHERS",       "Hay %d m&aacute;s que no llegaron..." },
+    { "NBLINES",      "l&iacute;neas" },
+    { "NICK",         "nick" },
+    { "AVGLETTERS",   "letras por l&iacute;nea" },
+    { "HOURS",        "horas" },
+    { "QUOTE",        "Frase aleatoria" },
+    { "TOPUSERSTIME", "Los que m&aacute;s escriben seg&uacute;n la hora" },
+    { "RANDTOPICS",   "Algunos topics" },
+    { "CHANGEDBY",    "Puestos por" },
+    { "NEWTOPIC",     "topic" },
+    { "RANDURLS",     "Algunas URLs" },
+    { "POSTEDBY",     "puestas por" },
+    { "POSTEDURL",    "URL" },
+    { "TOPWORDS",     "Palabras m&aacute;s usadas" },
+    { "WORD",         "Palabra" },
+    { "OCCURRENCES",  "Frecuencia" },
+    { "BIGNUMBERS",   "Algunos datos..." },
+    { "NUMBERS",      "N&uacute;mero de veces" },
+    { "TIME",         "%d lineas procesadas en %d segundos" },
+    { "FOOTER",       "Estad&iacute;sticas generadas por" },
+    { "C_SMILE",      "Suele estar fel&iacute;z :)" },
+    { "C_FROWN",      "Suele estar triste :(" },
+    { "C_EXCLAM",     "Grita mucho !" },
+    { "C_QUESTION",   "Hace muchas preguntas ?" },
+    { "C_ME",         "Abusa del comando /me" },
+    { "C_TOPIC",      "Suele cambiar el topic" },
+    { "C_MODE",       "Cambia a veces los modos del canal" },
+    { "C_KICK",       "Le gusta patear" },
+    { "C_KICKED",     "Es pateado con frecuencia" },
+    { "C_URL",        "Pone muchas URLs" },
+    { "C_JOIN",       "No sabe si irse o quedarse" },
+    { "C_NICK",       "Cambia mucho de nick" },
+    { "C_MONOLOGUE",  "Habla solo" }
+  }
+};
 
+int language;
 
-/* irssistats */
-#define VERSION "0.1"
-#define URL "http://royale.zerezo.com/programmation/irssistats/"
+char *L(char *key)
+{
+  int i;
+  for (i=1;i<=NBKEYS;i++) if (strcmp(key,keys[language][i][0])==0) return(keys[language][i][1]);
+  fprintf(stderr,"unknown language key: %s\n",key);
+  return("");
+}
 
+/* Themes */
+#define NBTHEMES 5
+#define NBCOLORS 9
+char *colors[NBTHEMES][NBCOLORS+1][2]= /* first key used for theme name/description and abbreviation */
+{
+  { /* Default theme */
+    { "White background", "default" },
+    { "BGCOLOR", "#FFFFFF" },
+    { "TEXT",    "#000000" },
+    { "LINK",    "#0000EE" },
+    { "VLINK",   "#551A8B" },
+    { "ALINK",   "#FF0000" },
+    { "TITLE1",  "#000000" },
+    { "TITLE2",  "#000000" },
+    { "BGTABLE", "#EEEEEE" },
+    { "BGTITLE", "#FFEEEE" }
+  },
+  { /* Dark theme */
+    { "Black background", "dark" },
+    { "BGCOLOR", "#000000" },
+    { "TEXT",    "#FFFFFF" },
+    { "LINK",    "#AAAAFF" },
+    { "VLINK",   "#CCCCDD" },
+    { "ALINK",   "#FFAAAA" },
+    { "TITLE1",  "#AAAAFF" },
+    { "TITLE2",  "#FFAAAA" },
+    { "BGTABLE", "#225522" },
+    { "BGTITLE", "#552222" }
+  },
+  { /* zeRezo theme */
+    { "Green theme...", "zerezo" },
+    { "BGCOLOR", "#000000" },
+    { "TEXT",    "#FFFFFF" },
+    { "LINK",    "#14F024" },
+    { "VLINK",   "#0CBA18" },
+    { "ALINK",   "#FFFFFF" },
+    { "TITLE1",  "#0CBA18" },
+    { "TITLE2",  "#84DB8C" },
+    { "BGTABLE", "#085D10" },
+    { "BGTITLE", "#0B810B" }
+  },
+  { /* tit-namour theme */
+    { "Purple and Pink", "namour" },
+    { "BGCOLOR", "#9933CC" },
+    { "TEXT",    "#DDAAFF" },
+    { "LINK",    "#CC99FF" },
+    { "VLINK",   "#999999" },
+    { "ALINK",   "#FFC8C8" },
+    { "TITLE1",  "#FFC8C8" },
+    { "TITLE2",  "#FFC8C8" },
+    { "BGTABLE", "#7711AA" },
+    { "BGTITLE", "#550088" }
+  },
+  { /* zeDuel theme */
+    { "Orange theme...", "zeduel" },
+    { "BGCOLOR", "#FFFFFF" },
+    { "TEXT",    "#000000" },
+    { "LINK",    "#FF7700" },
+    { "VLINK",   "#C05A00" },
+    { "ALINK",   "#FF9A41" },
+    { "TITLE1",  "#C05A00" },
+    { "TITLE2",  "#FF7700" },
+    { "BGTABLE", "#FFEEEE" },
+    { "BGTITLE", "#FF7700" }
+  }
+};
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <string.h>
+int theme;
 
-#define MAXUSERS 2000
-#define MAXNICKLENGTH 50
-#define MAXLINELENGTH 2000
+char *T(char *color)
+{
+  int i;
+  for (i=1;i<=NBCOLORS;i++) if (strcmp(color,colors[theme][i][0])==0) return(colors[theme][i][1]);
+  fprintf(stderr,"unknown theme color: %s\n",color);
+  return("");
+}
 
-struct 
+/* Variables */
+
+char *channel;
+char *maintainer;
+
+struct
 {
   char nick[MAXNICKLENGTH];
   int lines;
   int letters;
   int hours[4];
-  char quote[MAXLINELENGTH];
+  char quote[MAXQUOTELENGTH+1];
+  int counters[NBCOUNTERS];
 } users[MAXUSERS];
 int nbusers=0;
 
@@ -79,13 +319,14 @@ struct
 {
   char nick[MAXNICKLENGTH];
   char url[MAXLINELENGTH];
+  char shorturl[MAXQUOTELENGTH+1];
 } urls[5];
 int nburls=0;
 
 struct
 { 
   char nick[MAXNICKLENGTH];
-  char topic[MAXLINELENGTH];
+  char topic[MAXQUOTELENGTH+1];
 } topics[5];
 int nbtopics=0;
 
@@ -99,32 +340,161 @@ int days=0;
 int hours[24];
 int lines=0;
 
-void unhtml(char *string) /* replace < and > by { and } */
+struct letter
+{
+  int nb;
+  struct letter *next[26];
+} words;
+
+struct
+{
+  int nb;
+  char word[MAXLINELENGTH];
+} topwords[NBWORDS];
+
+#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 i,c;
+  struct letter *pos,*tmp;
+  for (;;)
+  {
+    while (!isletter(*message)) if (*message=='\0') return; else message++;
+    pos=&words;
+    while (isletter(*message))
+    {
+      c=lowercase(*message)-'a';
+      if (pos->next[(int)c]==NULL)
+      {
+        tmp=malloc(sizeof(struct letter));
+        tmp->nb=0;
+        for (i=0;i<26;i++) tmp->next[i]=NULL;
+        pos->next[(int)c]=tmp;
+      }
+      pos=pos->next[(int)c];
+      message++; 
+    }
+    pos->nb++;
+  }
+  return;
+}
+
+char tempword[MAXLINELENGTH];
+void bestwords(struct letter pos,int cur)
+{
+  int i,j;
+  if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
+  {
+    for (i=0;pos.nb<topwords[i].nb;i++);
+    for (j=NBWORDS-1;j>i;j--)
+    {
+      topwords[j].nb=topwords[j-1].nb;
+      strcpy(topwords[j].word,topwords[j-1].word);
+    }
+    topwords[i].nb=pos.nb;
+    strcpy(topwords[i].word,tempword);
+  }
+  for (i=0;i<26;i++) if (pos.next[i]!=NULL)
+  {
+    tempword[cur]='a'+i;
+    bestwords(*(pos.next[i]),cur+1);
+  }
+  tempword[cur]='\0';
+}
+
+void printhtml(char *string) /* replace < and > by &lt; and &gt; */
 {
   while (*string!='\0')
   {
-    if (*string=='<') *string='{';
-    if (*string=='>') *string='}';
+    switch (*string)
+    {
+      case '<':printf("&lt;"); break;
+      case '>':printf("&gt;"); break;
+      case '&':printf("&amp;"); break;
+      default:printf("%c",*string); break;
+    }
     string++;
   }
   return;
 }
 
+int dichotomic(char *nick)
+{
+  int i,j,start=0,end=nbusers-1,middle;
+  while (start<=end)
+  {
+    middle=(start+end)/2;
+    if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
+  }
+  if (strcmp(nick,users[start].nick)!=0)
+  {
+    nbusers++;
+    if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\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].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];
+    }
+    strcpy(users[start].nick,nick);
+    users[start].lines=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;
+  }
+  return(start);
+}
+
 int main(int argc,char *argv[])
 {
-  int i,j;
-  int max,user,hour;
+  int i,j,k;
+  int max,user,temp,hour;
+  int mononick,monolines;
   time_t debut;
   int totallines=0;
   int pos=0;
   char c;
   char *nick,*message;
   char line[MAXLINELENGTH];
+  FILE *fic;
+  regex_t preg;
+  
+  /*** INIT ***/
   
+  if ((argc<5) || (argc>6))
+  {
+    fprintf(stderr,"Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme [nickfile] > /path/to/file.html\n\n");
+    fprintf(stderr,"Version :\nirssistats %s\n\n",VERSION);
+    fprintf(stderr,"Supported languages :\n");
+    for (i=0;i<NBLANGUAGES;i++) fprintf(stderr,"%s = %s\n",keys[i][0][1],keys[i][0][0]);
+    fprintf(stderr,"\nSupported themes :\n");
+    for (i=0;i<NBTHEMES;i++) fprintf(stderr,"%s = %s\n",colors[i][0][1],colors[i][0][0]);
+    return(1);
+  }
+  channel=argv[1];
+  maintainer=argv[2];
+  for (i=0;i<NBLANGUAGES;i++) if (strcmp(argv[3],keys[i][0][1])==0) { language=i; break; }
+  if (i==NBLANGUAGES)
+  {
+    fprintf(stderr,"Invalid language : %s\n",argv[3]);
+    return(1);
+  }
+  for (i=0;i<NBTHEMES;i++) if (strcmp(argv[4],colors[i][0][1])==0) { theme=i; break; }
+  if (i==NBTHEMES)
+  {
+    fprintf(stderr,"Invalid theme : %s\n",argv[4]);
+    return(1);
+  }
+
   /*** LOG ***/
   
   srand(debut=time(NULL));
-  fprintf(stderr,"working");
+  fprintf(stderr,"working:");
   while (!feof(stdin))
   {
     c=getchar();
@@ -132,6 +502,7 @@ int main(int argc,char *argv[])
     if (pos>=MAXLINELENGTH) { fprintf(stderr,"line too long\n"); exit(1); }
     if (c=='\n')
     {
+      line[pos-1]='\0';
       totallines++;
       if (totallines%10000==0) { fprintf(stderr,"."); fflush(stdout); }
       if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
@@ -145,165 +516,305 @@ int main(int argc,char *argv[])
         for (j=0;j<4;j++) lastdays[0].hours[j]=0;
         days++;
       }
-      else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
+      else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
+      {
+        for (i=strlen(line);line[i]!=' ';i--);
+        nick=&line[i+1];
+        users[dichotomic(nick)].counters[D_MODE]++;
+      }
+      else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
       {
         for (i=10;line[i]!=' ';i++);
         line[i]='\0';
         nick=&line[10];
-        unhtml(nick);
         message=&line[i+1];
-        unhtml(message);
-        if (strncmp("changed the topic of",message,20)==0)
+        if (strncmp("changed the topic of",message,20)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
         {
+          users[dichotomic(nick)].counters[D_TOPIC]++;
           for (i=21;message[i]!=':';i++);
           message=&message[i+2];
-          line[pos-1]='\0';
           nbtopics++;
-          if ((nbtopics<=5) || (rand()%nbtopics==0))
+          if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
           {
-            for (i=4;i>0;i--)
-            {
-              strcpy(topics[i].nick,topics[i-1].nick);
-              strcpy(topics[i].topic,topics[i-1].topic);
-            }
-            strcpy(topics[0].nick,nick);
-            strcpy(topics[0].topic,message);
+            temp=nbtopics<=NBTOPICS?nbtopics-1:rand()%NBTOPICS;
+            strcpy(topics[temp].nick,nick);
+            strncpy(topics[temp].topic,message,MAXQUOTELENGTH);
           }
         }
+        else if (strncmp("was kicked from",message,15)==0) /* 00:00 -!- Nick was kicked from #channel by Nick [Reason] */
+        {
+          users[dichotomic(nick)].counters[D_KICKED]++;
+          for (i=16;message[i]!=' ';i++);
+          message=&message[i+4];
+          for (i=0;message[i]!=' ';i++);
+          message[i]='\0';
+          users[dichotomic(message)].counters[D_KICK]++;
+        }
+        else if (strncmp("is now known as",message,15)==0) /* 00:00 -!- Nick is now known as Nick */
+          users[dichotomic(nick)].counters[D_NICK]++;
+        else if (message[0]=='[') /* 00:00 -!- Nick [user@host] something... */
+        {
+          for (i=0;message[i]!=']';i++);
+          message=&message[i+2];
+          if (strncmp("has joined",message,10)==0) /* 00:00 -!- Nick [user@host] has joined #channel */
+            users[dichotomic(nick)].counters[D_JOIN]++;
+          else if (strncmp("has quit",message,8)==0); /* 00:00 -!- Nick [user@host] has quit [Reason] */
+          else if (strncmp("has left",message,8)==0); /* 00:00 -!- Nick [user@host] has left #channel [Reason] */
+          else;
+        }
       }
-      else  /* 00:00 <?Nick> the message */
+      else if ((line[6]=='<') || (line[7]=='*'))
       {
         line[2]='\0';
         hour=atoi(line);
-        if ((line[6]=='<') && (line[7]!='>'))
+        if (line[7]=='*') /* 00:00  * Nick the message */
+        {
+          for (i=9;line[i]!=' ';i++);
+          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=7;line[i]!='>';i++);
-          line[i]='\0';
+          for (i=10;line[i]!='<';i++);
+          nick=&line[10];
+          if (line[9]=='>') nick++;
+          message=&line[i+5];
+        }
+        else /* 00:00 <?Nick> the message */
+        {
+          for (i=8;line[i]!='>';i++);
           nick=&line[8];
-          unhtml(nick);
-          line[pos-1]='\0';
           message=&line[i+2];
-          unhtml(message);
-          for (i=0;i<nbusers;i++) if (strcmp(nick,users[i].nick)==0) break;
-          if (i==nbusers)
-          {
-            strcpy(users[nbusers].nick,nick);
-            nbusers++;
-            if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
-          }
-          users[i].lines++;
-          users[i].letters+=strlen(message);
-          users[i].hours[hour/6]++;
-          lastdays[0].lines++;
-          lastdays[0].hours[hour/6]++;
-          lines++;
-          hours[hour]++;
-          if (rand()%users[i].lines==0) strncpy(users[i].quote,message,100);
-          if (strncmp("http://",message,7)==0)
+        }
+        line[i]='\0';
+        i=dichotomic(nick);
+        if (line[7]=='*') users[i].counters[D_ME]++;
+        if (i==mononick)
+        {
+          monolines++;
+          if (monolines==5) users[i].counters[D_MONOLOGUE]++;
+        }
+        else
+        {
+          mononick=i;
+          monolines=1;
+        }
+        j=strlen(message);
+        users[i].lines++;
+        users[i].letters+=j;
+        users[i].hours[hour/6]++;
+        lastdays[0].lines++;
+        lastdays[0].hours[hour/6]++;
+        lines++;
+        hours[hour]++;
+        if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
+        else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
+        else if ((message[j-3]==' ')&&(message[j-2]==':'))
+        {
+          if (message[j-1]==')') users[i].counters[D_SMILE]++;
+          else if (message[j-1]=='(') users[i].counters[D_FROWN]++;
+        }
+        if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
+        if (strncmp("http://",message,7)==0)
+        {
+          users[i].counters[D_URL]++;
+          for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
+          message[i]='\0';
+          nburls++;
+          if ((nburls<=NBURLS) || (rand()%(nburls/NBURLS)==0))
           {
-            for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
-            message[i]='\0';
-            nburls++;
-            if ((nburls<=5) || (rand()%nburls==0))
-            {
-              for (i=4;i>0;i--)
-              {
-                strcpy(urls[i].nick,urls[i-1].nick);
-                strcpy(urls[i].url,urls[i-1].url);
-              }
-              strcpy(urls[0].nick,nick);
-              strcpy(urls[0].url,message);
-            }
+            temp=nburls<=NBURLS?nburls-1:rand()%NBURLS;
+            strcpy(urls[temp].nick,nick);
+            strcpy(urls[temp].url,message);
+            strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
           }
         }
+        findwords(message);
       }
       pos=0;
     }
   }
   fprintf(stderr,"done\n");
 
+  bestwords(words,0);
+
+  /*** ALIAS ***/
+  
+  if (argc==6)
+  {
+    if ((fic=fopen(argv[5],"rt"))==NULL) { fprintf(stderr,"can't open nick file\n"); exit(1); }
+    while (fscanf(fic,"%s",line)==1)
+    {
+      user=dichotomic(line);
+      fscanf(fic,"%s",line);
+      if (regcomp(&preg,line,0)!=0) { fprintf(stderr,"error in nick file"); exit(1); }
+      temp=users[user].lines;
+      for (i=0;i<nbusers;i++) if ((i!=user) && (regexec(&preg,users[i].nick,0,0,0)==0) && (users[i].lines>=0))
+      {
+        if (users[i].lines>temp) /* for nick alias, keep the random quote of the most used nick */
+        {
+          strcpy(users[user].quote,users[i].quote);
+          temp=users[i].lines;
+        }
+        users[user].lines+=users[i].lines;
+        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].letters=-1;
+        for (j=0;j<4;j++) users[i].hours[j]=-1;
+        for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
+      }
+      regfree(&preg);
+    }
+    fclose(fic);
+  }
+  
   /*** HTML ***/
 
   /* header */
   printf("<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
   printf("<html>\n\n<head>\n<base target=\"_blank\">\n<title>");
-  printf(HEADER,CHANNEL,MAINTAINER);
+  printf(L("HEADER"),channel,maintainer);
   printf("</title>\n</head>\n\n");
-  printf("<body bgcolor=\"%s\" text=\"%s\" link=\"%s\" vlink=\"%s\" alink=\"%s\">\n\n<center>\n\n<font color=\"%s\"><h1>",BGCOLOR,TEXT,LINK,VLINK,ALINK,TITLE1);
-  printf(HEADER,CHANNEL,MAINTAINER);
+  printf("<body bgcolor=\"%s\" text=\"%s\" link=\"%s\" vlink=\"%s\" alink=\"%s\">\n\n<center>\n\n<font color=\"%s\"><h1>",T("BGCOLOR"),T("TEXT"),T("LINK"),T("VLINK"),T("ALINK"),T("TITLE1"));
+  printf(L("HEADER"),channel,maintainer);
   printf("</h1></font>\n%s<br>\n<br><br>\n\n",ctime(&debut));
 
   /* legend */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table bgcolor=%s>\n<tr>\n",TITLE2,LEGEND,BGTABLE);
-  for (i=0;i<4;i++) printf("<td><img src=\"h%d.png\" width=\"40\" height=\"15\"></td><td> : %s %d-%d&nbsp;</td><td width=\"10\"></td>\n",i+1,HOURS,i*6,i*6+5);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table bgcolor=%s>\n<tr>\n",T("TITLE2"),L("LEGEND"),T("BGTABLE"));
+  for (i=0;i<4;i++) printf("<td><img src=\"h%d.png\" width=\"40\" height=\"15\"></td><td> : %s %d-%d</td><td width=\"10\"></td>\n",i+1,L("HOURS"),i*6,i*6+5);
   printf("</tr>\n</table>\n<br><br>\n\n");
   
   /* last days */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",TITLE2,LASTDAYS);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("LASTDAYS"));
   max=-1;
   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
   for (i=30;i>=0;i--)
   {
-    printf("<td align=\"center\" valign=\"bottom\"><font size=\"-2\">%d</font><br>",lastdays[i].lines);
-    if (max!=0) for (j=0;j<4;j++) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",j+1,150*lastdays[i].hours[j]/max);
+    printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%d</font><br>",lastdays[i].lines);
+    for (j=0;j<4;j++) if (lastdays[i].hours[j]!=0) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",j+1,150*lastdays[i].hours[j]/max);
     printf("</td>\n");
   }
   printf("</tr>\n<tr>\n");
   for (i=30;i>=0;i--)
-    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",BGTABLE,i);
+    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
   printf("</tr>\n</table>\n<br><br>\n\n");
   
   /* top hours */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",TITLE2,TOPHOURS);
-  if (lines!=0) for (i=0;i<24;i++)
-    printf("<td align=\"center\" valign=\"bottom\"><font size=\"-2\">%.1f%%</font><br><img src=\"v%d.png\" width=\"15\" height=\"%d\"></td>\n",(float)100*hours[i]/lines,i/6+1,1500*hours[i]/lines);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("TOPHOURS"));
+  max=-1;
+  for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
+  for (i=0;i<24;i++)
+  {
+    printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%.1f%%</font><br>",lines!=0?(float)100*hours[i]/lines:0);
+    if (hours[i]!=0) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",i/6+1,150*hours[i]/max);
+    printf("</td>\n");
+  }
   printf("</tr>\n<tr>\n");
   for (i=0;i<24;i++)
-    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",BGTABLE,i);
+    printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
   printf("</tr>\n</table>\n<br><br>\n\n");
   
   /* top users */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,TOPUSERS);
-  printf("<table>\n<tr><td></td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\" colspan=\"2\">%s</td><td bgcolor=\"%s\">%s</td>\n",BGTITLE,NICK,BGTITLE,NBLINES,BGTITLE,HOURS,BGTITLE,AVGLETTERS,BGTITLE,QUOTE);
-  for (i=1;i<=50;i++)
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERS"));
+  printf("<table>\n<tr><td></td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\" colspan=\"2\">%s</td><td bgcolor=\"%s\">%s</td>\n",T("BGTITLE"),L("NICK"),T("BGTITLE"),L("NBLINES"),T("BGTITLE"),L("HOURS"),T("BGTITLE"),L("AVGLETTERS"),T("BGTITLE"),L("QUOTE"));
+  for (i=1;i<=NBUSERS;i++)
   {
     user=-1;
-    max=-1;
+    max=0;
     for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
     if (user!=-1)
     {
-      printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\">",BGTABLE,i,BGTABLE,users[user].nick,BGTABLE,users[user].lines,BGTABLE);
-      for (j=0;j<4;j++)
-        printf("<img src=\"h%d.png\" width=\"%d\" height=\"15\">",j+1,100*users[user].hours[j]/users[user].lines);
-      printf("</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\"><img src=\"hm.png\" width=\"%d\" height=\"15\"></td><td bgcolor=\"%s\">\"%s\"</td></tr>\n",BGTABLE,users[user].letters/users[user].lines,BGTABLE,users[user].letters/users[user].lines,BGTABLE,users[user].quote);
+      printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\">",T("BGTABLE"),i,T("BGTABLE"),users[user].nick,T("BGTABLE"),users[user].lines,T("BGTABLE"));
+      for (j=0;j<4;j++) if (users[user].hours[j]!=0) printf("<img src=\"h%d.png\" width=\"%d\" height=\"15\">",j+1,100*users[user].hours[j]/users[user].lines);
+      printf("</td><td bgcolor=\"%s\">%d</td><td bgcolor=\"%s\"><img src=\"hm.png\" width=\"%d\" height=\"15\"></td><td bgcolor=\"%s\">\"",T("BGTABLE"),users[user].lines!=0?users[user].letters/users[user].lines:0,T("BGTABLE"),users[user].lines!=0?users[user].letters/users[user].lines:0,T("BGTABLE"));
+      printhtml(users[user].quote);
+      printf("\"</td></tr>\n");
       users[user].lines=-1;
     }    
   }
-  printf("</table><br>\n");
-  if (nbusers>50) printf(OTHERS,nbusers-50);
-  printf("<br>\n<br><br>\n\n");
+  printf("</table>\n");
+  temp=0;
+  for (i=0;i<=nbusers;i++) if (users[i].lines>=0) temp++;
+  if (temp>0)
+  {
+    printf("<br>");
+    printf(L("OTHERS"),temp);
+    printf("<br>\n");
+  }
+  printf("<br><br>\n\n");
+  
+  /* top users by time */
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERSTIME"));
+  printf("<table>\n<tr><td></td>");
+  for (i=0;i<4;i++) printf("<td bgcolor=\"%s\" colspan=\"2\">%s %d-%d</td>",T("BGTITLE"),L("HOURS"),i*6,i*6+5);
+  printf("</tr>\n");
+  for (i=1;i<=NBUSERSTIME;i++)
+  {
+    printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td>",T("BGTABLE"),i);
+    for (j=0;j<4;j++)
+    {
+      user=-1;
+      max=0;
+      for (k=0;k<nbusers;k++) if (users[k].hours[j]>max) max=users[user=k].hours[j];
+      if (user!=-1)
+      {
+        printf("<td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td>",T("BGTABLE"),users[user].nick,T("BGTABLE"),users[user].hours[j]);
+        users[user].hours[j]=-1;
+      }
+      else printf("<td></td><td></td>");
+    }
+    printf("</tr>\n");
+  }
+  printf("</table>\n<br><br>\n\n");
 
   /* random topics */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,RANDTOPICS);
-  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",BGTITLE,CHANGEDBY,BGTITLE,NEWTOPIC);
-  for (i=4;i>=0;i--)
-    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"%s\"</td></tr>\n",BGTABLE,topics[i].nick,BGTABLE,topics[i].topic);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDTOPICS"));
+  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("CHANGEDBY"),T("BGTITLE"),L("NEWTOPIC"));
+  for (i=nbtopics<NBTOPICS?nbtopics-1:NBTOPICS-1;i>=0;i--)
+  {
+    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"",T("BGTABLE"),topics[i].nick,T("BGTABLE"));
+    printhtml(topics[i].topic);
+    printf("\"</td></tr>\n");
+  }
   printf("</table>\n<br><br>\n\n");
   
   /* random urls */
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,RANDURLS);
-  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",BGTITLE,POSTEDBY,BGTITLE,POSTEDURL);
-  for (i=4;i>=0;i--)
-    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"<a href=\"%s\">%s</a>\"</td></tr>\n",BGTABLE,urls[i].nick,BGTABLE,urls[i].url,urls[i].url);
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDURLS"));
+  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("POSTEDBY"),T("BGTITLE"),L("POSTEDURL"));
+  for (i=nburls<NBURLS?nburls-1:NBURLS-1;i>=0;i--)
+  {
+    printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"<a href=\"%s\">",T("BGTABLE"),urls[i].nick,T("BGTABLE"),urls[i].url);
+    printhtml(urls[i].shorturl);
+    printf("</a>\"</td></tr>\n");
+  }
   printf("</table>\n<br><br>\n\n");
   
-  /* big numbers (todo...)
-  printf("<font color=\"%s\"><h3>%s</h3></font>\n",TITLE2,BIGNUMBERS);
-  */
+  /* top words */
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPWORDS"));
+  printf("<table>\n<tr><td></td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("WORD"),T("BGTITLE"),L("OCCURRENCES"));
+  for (i=0;i<NBWORDS;i++)
+    if (topwords[i].nb!=0) printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td><td bgcolor=\"%s\">\"%s\"</td><td bgcolor=\"%s\">%d</td></tr>\n",T("BGTABLE"),i+1,T("BGTABLE"),topwords[i].word,T("BGTABLE"),topwords[i].nb);
+  printf("</table>\n<br><br>\n\n");
+
+  /* big numbers */
+  printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("BIGNUMBERS"));
+  printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("NICK"),T("BGTITLE"),L("NUMBERS"),T("BGTITLE"),L("NBLINES"));
+  for (i=0;i<NBCOUNTERS;i++)
+  {
+    user=-1;
+    max=0;
+    for (j=0;j<nbusers;j++) if (users[j].counters[i]>max) max=users[user=j].counters[i];
+    if (user!=-1) printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td></tr>",T("BGTABLE"),users[user].nick,T("BGTABLE"),L(counters[i]),T("BGTABLE"),users[user].counters[i]);
+  }
+  printf("</table>\n<br><br>\n\n");
   
   /* footer */
-  printf(TIME,totallines,(int)(time(NULL)-debut));
-  printf("<br>\n%s <a href=\"%s\">irssistats %s</a>",FOOTER,URL,VERSION);
+  printf(L("TIME"),totallines,(int)(time(NULL)-debut));
+  printf("<br>\n%s <a href=\"%s\">irssistats %s</a>",L("FOOTER"),URL,VERSION);
   printf("\n\n</center>\n\n</body>\n\n</html>\n");
   
   return(0);