remove nested functions
[irssistats] / irssistats.c
index 4e637a5..2ac92a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * irssistats version 0.70
+ * irssistats version 0.75
  *
  * This tool generates IRC stats based on irssi logs.
  * Usage: irssistats [/path/to/file.conf]
@@ -28,6 +28,7 @@
 #include <stdlib.h>
 #include <time.h>
 #include <string.h>
+#include <locale.h>
 #ifdef __WIN32__
 #define GLOBALCONF "irssistats.conf"
 #else
@@ -48,7 +49,7 @@
 #define MINWORDLENGTH 5
 
 /* irssistats */
-#define VERSION "0.70"
+#define VERSION "0.75"
 #define URL "http://royale.zerezo.com/irssistats/"
 
 /* Counters */
@@ -70,7 +71,7 @@ char *counters[NBCOUNTERS]={"C_SMILE","C_FROWN","C_EXCLAM","C_QUESTION","C_ME","
 
 /* Languages */
 #define NBLANGUAGES 11
-#define NBKEYS 39
+#define NBKEYS 41
 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
 {
   { /* English language */
@@ -78,7 +79,9 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "CHARSET",      "ISO-8859-1" },
     { "HEADER",       "Statistics for %s by %s" },
     { "LEGEND",       "Legend" },
-    { "LASTDAYS",     "Lastdays statistics" },
+    { "LASTDAYS",     "Last days statistics" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Hourly statistics" },
     { "TOPUSERS",     "Most active people" },
     { "OTHERS",       "There are %d left not ranked..." },
@@ -121,6 +124,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statistiques de %s par %s" },
     { "LEGEND",       "L&eacute;gende" },
     { "LASTDAYS",     "Statistiques des derniers jours" },
+    { "LASTWEEKS",    "Statistiques des derni&egrave;res semaines" },
+    { "LASTMONTHS",   "Statistiques des derniers mois" },
     { "TOPHOURS",     "Statistiques horaires" },
     { "TOPUSERS",     "Personnes les plus actives" },
     { "OTHERS",       "Il reste %d personnes non class&eacute;es..." },
@@ -164,6 +169,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statistiken f&uuml;r %s von %s" },
     { "LEGEND",       "Legende" },
     { "LASTDAYS",     "Statistik der letzten Tage" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "St&uuml;ndliche Statistik" },
     { "TOPUSERS",     "Die aktivsten Personen" },
     { "OTHERS",       "Es bleiben noch %d uneingetragene" },
@@ -191,7 +198,7 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "C_EXCLAM",     "schreit oft !" },
     { "C_QUESTION",   "stellt viele Fragen ?" },
     { "C_ME",         "mag /me'en" },
-    { "C_TOPIC",      "aendert oft das Topico" },
+    { "C_TOPIC",      "aendert oft das Topic" },
     { "C_MODE",       "aendert oft die Modes" },
     { "C_KICK",       "mag /kick'en" },
     { "C_KICKED",     "wird oft gekickt"},
@@ -207,6 +214,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Estad&iacute;sticas de %s por %s" },
     { "LEGEND",       "Leyenda" },
     { "LASTDAYS",     "Estad&iacute;sticas de los &uacute;ltimos d&iacute;as" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Estad&iacute;sticas por horas" },
     { "TOPUSERS",     "Los que m&aacute;s escriben" },
     { "OTHERS",       "Hay %d m&aacute;s que no llegaron..." },
@@ -250,6 +259,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statystyki dla %s zebrane przez %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statystyki z ostatnich dni" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Statystyki godzinowe" },
     { "TOPUSERS",     "Najaktywniejsi" },
     { "OTHERS",       "Jest jeszcze %d nie sklasyfikowanych..." },
@@ -293,6 +304,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statystyki dla %s przez %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statystyki z ostatnich dni" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Statystyki godzinne" },
     { "TOPUSERS",     "Najaktywniejsi ludzie" },
     { "OTHERS",       "Zostalo jeszcze %d nie sklasyfikowanych..." },
@@ -336,6 +349,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Kanavan %s tilastot - %s" },
     { "LEGEND",       "Merkkien selitykset" },
     { "LASTDAYS",     "Viime päivien tilastot" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Tilastot tunneittain" },
     { "TOPUSERS",     "Aktiivisimmat ihmiset" },
     { "OTHERS",       "Jäljelle jäi %d joita ei listattu..." },
@@ -379,6 +394,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statistiche per il canale %s di %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statistiche degli ultimi giorni" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Statistiche in ore" },
     { "TOPUSERS",     "Utenti pi&ugrave; attivi" },
     { "OTHERS",       "Ci sono %d utenti non classificati..." },
@@ -423,6 +440,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statistieken voor %s door %s" },
     { "LEGEND",       "Legenda" },
     { "LASTDAYS",     "Statistieken van de laatste dagen" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Statistieken per uur" },
     { "TOPUSERS",     "Meest actieve mensen" },
     { "OTHERS",       "Er zijn nog %d mensen die de top niet haalden..." },
@@ -466,6 +485,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "óÔÁÔÉÓÔÉËÁ ÄÌÑ %s ÏÔ %s" },
     { "LEGEND",       "ïÂÏÚÎÁÞÅÎÉÑ" },
     { "LASTDAYS",     "óÔÁÔÉÓÔÉËÁ ÐÏÓÌÅÄÎÉÈ ÄÎÅÊ" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "ðÏÞÁÓÏ×ÁÑ ÓÔÁÔÉÓÔÉËÁ" },
     { "TOPUSERS",     "áËÔÉ×ÎÅÊÛÉÅ ÌÀÄÉ" },
     { "OTHERS",       "ïÓÔÁÌÏÓØ %d ÎÅÐÏÄÓÞÉÔÁÎÙÈ..." },
@@ -509,6 +530,8 @@ char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and ab
     { "HEADER",       "Statistika kanalile %s on koostanud %s" },
     { "LEGEND",       "Legend" },
     { "LASTDAYS",     "Viimaste päevade statistika" },
+    { "LASTWEEKS",    "Last weeks statistics" },
+    { "LASTMONTHS",   "Last months statistics" },
     { "TOPHOURS",     "Tunni statistika" },
     { "TOPUSERS",     "Kõige aktiivsemad inimesed" },
     { "OTHERS",       "%d inimest on rääkinud" },
@@ -573,6 +596,8 @@ time_t debut;
 int top_words=1; /* 0 = disabled */
 int ranking=0; /* 0 = lines ; 1 = words ; 2 = letters */
 int quarter=0; /* 1 = enabled */
+int months=0; /* 1 = enabled */
+int weeks=0; /* 1 = enabled */
 int photo_size=60;
 
 struct user
@@ -609,8 +634,10 @@ struct
 {
   int lines;
   int hours[4];
-} lastdays[31];
+} lastdays[31], lastweeks[31], lastmonths[31];
 int days=0;
+char currday[16];
+int currwday=-1, currmon=-1;
 
 int hours[24*4];
 int lines=0;
@@ -627,8 +654,89 @@ struct
   char word[MAXLINELENGTH];
 } topwords[NBWORDS];
 
+struct rusletter
+{
+  int nb;
+  struct rusletter *next[33];
+} ruswords;
+
 #define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
 #define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
+//#define lowercase(c) (c | 0x20)
+#define isrusletter(c) (memchr(koi,c,66)==NULL?0:1)
+
+const char koi[] = {
+193,194,215,199,196,197,163,214,218,201,202,203,204,205,206,207,208,210,
+211,212,213,198,200,195,222,219,221,216,223,217,220,192,209,
+225,226,247,231,228,229,179,246,250,233,234,235,236,237,238,239,240,242,
+243,244,245,230,232,227,254,251,253,248,255,249,252,224,241
+};
+int lowruscase(char c)
+{
+  char *ctmp_p;
+  int ch=0;
+  if (memchr(koi,c,66)==NULL) return koi[0];
+  ch=strlen(koi)-strlen(memchr(koi,c,66));
+  if (ch>33) return ch-33; else return ch;
+}
+/* cp1251 for encoding into koi8-r
+const char win[] = {
+224,225,226,227,228,229,184,230,231,232,233,234,235,236,237,238,239,240,
+241,242,243,244,245,246,247,248,249,252,250,251,253,254,255,
+192,193,194,195,196,197,168,198,199,200,201,202,203,204,205,206,207,208,
+209,210,211,212,213,214,215,216,217,220,218,219,221,222,223
+};*/
+
+
+
+int findruswords(char *message)
+{
+  int i,c,n=0;
+  //char *tmp_p;
+  struct rusletter *pos,*tmp;
+  for (;;)
+  {
+    while (!isrusletter(*message)) if (*message=='\0') return n; else message++;
+    pos=&ruswords;
+    while (isrusletter(*message))
+    {
+      c=lowruscase(*message);
+      /*tmp_p=memchr(koi,message[0],33);
+      if (tmp_p==NULL) return n;
+      c=strlen(koi)-strlen(tmp_p);*/
+      if (pos->next[(int)c]==NULL)
+      {
+        tmp=malloc(sizeof(struct rusletter));
+        if (tmp==NULL)
+        {
+          fprintf(stderr, "findruswords(): malloc failure\n");
+          exit(1);
+        }
+        tmp->nb=0;
+        for (i=0;i<33;i++) tmp->next[i]=NULL;
+        pos->next[(int)c]=tmp;
+      }
+      pos=pos->next[(int)c];
+      message++; 
+    }
+    pos->nb++;
+    n++;
+  }
+  return n;
+}
+
+
+void freeruswords(struct rusletter *pos)
+{
+  int i;
+  for (i=0;i<33;i++) if (pos->next[i]!=NULL)
+  {
+    freeruswords(pos->next[i]);
+    free(pos->next[i]);
+    (*pos).next[i]=NULL;
+  }
+}
+
 int findwords(char *message)
 {
   int i,c,n=0;
@@ -684,6 +792,28 @@ void bestwords(struct letter pos,int cur)
   tempword[cur]='\0';
 }
 
+void bestruswords(struct rusletter 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<33;i++) if (pos.next[i]!=NULL)
+  {
+    tempword[cur]=koi[i];
+    bestruswords(*(pos.next[i]),cur+1);
+  }
+  tempword[cur]='\0';
+}
+
 void freewords(struct letter *pos)
 {
   int i;
@@ -753,6 +883,71 @@ int dichotomic(char *nick)
   return(start);
 }
 
+void day_changed(char* date)
+{
+  int i,j;
+  char newday[16];
+  struct tm currdate;
+
+  memcpy(newday, date, 11);
+  if (date[13]==':')
+    memcpy(newday+11, date+20, 4);
+  else
+    memcpy(newday+11, date+11, 4);
+  newday[15]=0;
+  if (memcmp(currday, newday, 15)!=0)
+  {
+    /* we do not have a "current" day yet? */
+    if (currday[0]!=0)
+    {
+      for (i=30;i>0;i--)
+      {
+        lastdays[i].lines=lastdays[i-1].lines;
+        for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
+      }
+      lastdays[0].lines=0;
+      for (j=0;j<4;j++) lastdays[0].hours[j]=0;
+      days++;
+    }
+    memcpy(currday, newday, 15);
+    if (debug==2)
+      fprintf(stderr, "day %d changed to: %s\n", days, currday);
+    
+    /* try to parse the date for weeks/months stats */
+    if (strptime(currday, "%a %b %d %Y", &currdate))
+    {
+      /* each monday we change the week number */
+      if (currdate.tm_wday == 1)
+      {
+        for (i=30;i>0;i--)
+        {
+          lastweeks[i].lines=lastweeks[i-1].lines;
+          for (j=0;j<4;j++) lastweeks[i].hours[j]=lastweeks[i-1].hours[j];
+        }
+        lastweeks[0].lines=0;
+        for (j=0;j<4;j++) lastweeks[0].hours[j]=0;
+      }
+      /* if the month has changed */
+      if (currdate.tm_mon != currmon && currmon > 0)
+      {
+        for (i=30;i>0;i--)
+        {
+          lastmonths[i].lines=lastmonths[i-1].lines;
+          for (j=0;j<4;j++) lastmonths[i].hours[j]=lastmonths[i-1].hours[j];
+        }
+        lastmonths[0].lines=0;
+        for (j=0;j<4;j++) lastmonths[0].hours[j]=0;
+      }
+      currwday = currdate.tm_wday;
+      currmon = currdate.tm_mon;
+      
+    }
+  } else {
+    if (debug==2)
+      fprintf(stderr, "but day did not change\n");
+  }
+}
+
 void parse_log(char *logfile)
 {
   FILE *fic;
@@ -761,8 +956,9 @@ void parse_log(char *logfile)
   int i,j;
   char *nick,*message;
   int nickstart;
-  int mononick,monolines;
+  int mononick=-1,monolines=0;
   int temp,hour;
+  int timelen;
 
   if ((fic=fopen(logfile,"rt"))==NULL) { fprintf(stderr,"can't open log file \"%s\"\n",logfile); exit(1); }
   if (debug) printf("working on %s : ",channel);
@@ -786,170 +982,219 @@ void parse_log(char *logfile)
     pos=0;
     totallines++;
     if (totallines%10000==0 && debug) { printf("."); fflush(stdout); }
-    if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
+    if (strncmp("--- Log opened",line,14)==0) /* --- Log opened Wed May 01 00:00 2002 */
     {
-      for (i=30;i>0;i--)
-      {
-        lastdays[i].lines=lastdays[i-1].lines;
-        for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
-      }
-      lastdays[0].lines=0;
-      for (j=0;j<4;j++) lastdays[0].hours[j]=0;
-      days++;
+      if (debug==2)
+        fprintf(stderr, "log %s opened, ", logfile);
+      day_changed(line+15);
     }
-    else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
+    if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
     {
-      for (i=strlen(line);line[i]!=' ';i--);
-      nick=&line[i+1];
-      users[dichotomic(nick)].counters[D_MODE]++;
+      if (debug==2)
+        fprintf(stderr, "within log file, ");
+      day_changed(line+16);
     }
-    else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
+    else
     {
-      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];
-      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];
-        nbtopics++;
-        if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
-        {
-          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... */
+      /* timelen is number of characters occupied by time 00:00.. plus any space */
+      timelen = 5;
+      if (line[timelen] == ':' && isdigit(line[timelen+1]) && isdigit(line[timelen+2]))
+          timelen += 3;
+      if (line[timelen] == ' ')
+          timelen++;
+      if (strncmp("-!- mode/",&line[timelen],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
       {
-        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;
+        for (i=strlen(line);line[i]!=' ';i--);
+        nick=&line[i+1];
+        users[dichotomic(nick)].counters[D_MODE]++;
       }
-    }
-    else if ((line[6]=='<') || (line[7]=='*'))
-    {
-      line[2]='\0';
-      hour=atoi(line);
-      if (line[7]=='*') /* 00:00  * Nick the message */
+      else if (strncmp("-!-",&line[timelen],3)==0) /* 00:00 -!- Nick something... */
       {
-        for (i=9;line[i]!=' ' && i <= 9 + MAXNICKLENGTH;i++);
-        if(i > 9 + MAXNICKLENGTH) {
+        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; 
+          continue;
         }
-        nick=&line[9];
+        line[i]='\0';
+        nick=&line[timelen+4];
         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 <= 10 + MAXNICKLENGTH;i++);
-         if(i > 10 + MAXNICKLENGTH) { 
-          if(debug) {
-            fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+        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];
+          nbtopics++;
+          if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
+          {
+            temp=nbtopics<=NBTOPICS?nbtopics-1:rand()%NBTOPICS;
+            strcpy(topics[temp].nick,nick);
+            strncpy(topics[temp].topic,message,MAXQUOTELENGTH);
           }
-          continue; 
         }
-        nick=&line[10];
-        if (line[9]=='>') nick++;
-        message=&line[i+5];
+        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[timelen]=='<') || (line[timelen+1]=='*'))
       {
-
-        /* 
-         * Irssi doesn't log channel mode with show_nickmode = OFF    
-         * the following covers op, half-op, voice and show_nickmode_empty                 
-         */
-        if (line[7]=='@' || line[7]=='%' || line[7]=='+' || line[7]==' ') {
-            nickstart = 8;
-        } else {
-            nickstart = 7;
+        line[2]='\0';
+        hour=atoi(line);
+        if (line[timelen+1]=='*') /* 00:00  * Nick the message */
+        {
+          for (i=timelen+3;line[i]!=' ' && i <= timelen+3+MAXNICKLENGTH;i++);
+          if(i > timelen+3+MAXNICKLENGTH) {
+            if(debug) {
+              fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+            }
+            continue; 
+          }
+          nick=&line[timelen+3];
+          message=&line[i+1];
         }
-          
-        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); 
+        else if (line[timelen+1]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
+                               /* 00:00 <>>?Nick<<> the personal message */
+        {
+          for (i=timelen+4;line[i]!='<' && i <= timelen+4+MAXNICKLENGTH;i++);
+           if(i > timelen+4+MAXNICKLENGTH) { 
+            if(debug) {
+              fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
+            }
+            continue; 
           }
-          continue; 
+          nick=&line[timelen+4];
+          if (line[timelen+3]=='>') nick++;
+          message=&line[i+5];
         }
-        nick=&line[nickstart];
-        message=&line[i+2];
-      }
-      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++;
-      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]++;
-      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))
+        else /* 00:00 <?Nick> the message */
+        {
+
+          /* 
+           * Irssi doesn't log channel mode with show_nickmode = OFF    
+           * the following covers op, half-op, voice and show_nickmode_empty                 
+           */
+          switch (line[timelen+1])
+          {
+            case '@':
+            case '%':
+            case '+':
+            case '&':
+            case '~':
+            case ' ': 
+              nickstart = timelen+2; 
+              break;
+            default:
+              nickstart = timelen+1;
+              break;
+          }
+            
+          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];
+        }
+        /* remove identified character from nick (invalid nick character anyway) */
+        if (nick[0] == '+')
+          fprintf(stderr, "nick starts with +! %s\n", nick);
+        if (line[i-1] == '+' || line[i-1] == '*')
+          i--;
+        line[i]='\0';
+        i=dichotomic(nick);
+        if (line[timelen+1]=='*') 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++;
+        if (top_words || ranking==1) 
+        {
+          if (L("CHARSET")=="KOI8-R") users[i].words+=findwords(message)+findruswords(message);
+          else users[i].words+=findwords(message);
+        }
+        users[i].letters+=j;
+        users[i].hours[hour/6]++;
+        lastdays[0].lines++;
+        lastdays[0].hours[hour/6]++;
+        lastweeks[0].lines++;
+        lastweeks[0].hours[hour/6]++;
+        lastmonths[0].lines++;
+        lastmonths[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]++;
+        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]++;
+        }
+        // Fetch a random message, messages between 25 and 70 chars are
+        // preferred (pisg-style, gets "better" quotes)
+        //
+        if (rand()%users[i].lines==0) {
+            
+            int len = strlen(message);
+            // if we have a "good" quote, use it
+            if ( len > 25 && len < 70 )
+            {
+                strncpy(users[i].quote,message,MAXQUOTELENGTH);
+            } else {
+                int len2 = strlen(users[i].quote);
+                if ( !(len2 > 25 && len2 < 70 )) {
+                    strncpy(users[i].quote,message,MAXQUOTELENGTH);
+                } 
+            }
+        } 
+
+        if (strncmp("http://",message,7)==0)
         {
-          temp=nburls<=NBURLS?nburls-1:rand()%NBURLS;
-          strcpy(urls[temp].nick,nick);
-          strcpy(urls[temp].url,message);
-          strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
+          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))
+          {
+            temp=nburls<=NBURLS?nburls-1:rand()%NBURLS;
+            strcpy(urls[temp].nick,nick);
+            strcpy(urls[temp].url,message);
+            strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
+          }
         }
       }
     }
@@ -1048,7 +1293,8 @@ void gen_xhtml(char *xhtmlfile)
     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);
-    subtheme=strtok(theme,",");
+    strcpy(line, theme);
+    subtheme=strtok(line,",");
     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);
@@ -1070,6 +1316,42 @@ void gen_xhtml(char *xhtmlfile)
   for (i=0;i<4;i++) fprintf(fic,"<td><div class=\"h%d\" style=\"width: 40px\"></div></td><td>%s %d-%d</td>\n",i+1,L("HOURS"),i*6,i*6+5);
   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
   
+  if (months)
+  {
+    /* last months */
+    fprintf(fic,"<div id=\"irssistats_lastmonths\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LASTMONTHS"));
+    max=-1;
+    for (i=30;i>=0;i--) if (lastmonths[i].lines>max) max=lastmonths[i].lines;
+    for (i=30;i>=0;i--)
+    {
+      fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%d</small>",lastmonths[i].lines);
+      for (j=0;j<4;j++) if (lastmonths[i].hours[j]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",j+1,150*lastmonths[i].hours[j]/max);
+      fprintf(fic,"</td>\n");
+    }
+    fprintf(fic,"</tr>\n<tr>\n");
+    for (i=30;i>=0;i--)
+      fprintf(fic,"<th>%d</th>\n",i);
+    fprintf(fic,"</tr>\n</table>\n</div>\n\n");
+  }
+  
+  if (weeks)
+  {
+    /* last weeks */
+    fprintf(fic,"<div id=\"irssistats_lastweeks\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LASTWEEKS"));
+    max=-1;
+    for (i=30;i>=0;i--) if (lastweeks[i].lines>max) max=lastweeks[i].lines;
+    for (i=30;i>=0;i--)
+    {
+      fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%d</small>",lastweeks[i].lines);
+      for (j=0;j<4;j++) if (lastweeks[i].hours[j]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",j+1,150*lastweeks[i].hours[j]/max);
+      fprintf(fic,"</td>\n");
+    }
+    fprintf(fic,"</tr>\n<tr>\n");
+    for (i=30;i>=0;i--)
+      fprintf(fic,"<th>%d</th>\n",i);
+    fprintf(fic,"</tr>\n</table>\n</div>\n\n");
+  }
+  
   /* last days */
   fprintf(fic,"<div id=\"irssistats_lastdays\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LASTDAYS"));
   max=-1;
@@ -1160,7 +1442,7 @@ void gen_xhtml(char *xhtmlfile)
         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,"<td class=\"tdphoto\"><img src=\"%s\" class=\"imgphoto\" alt=\"\" /></td>",users[user].photo);
       }
       fprintf(fic,"</tr>\n");
       users[user].lines=-1;
@@ -1264,9 +1546,10 @@ void gen_xhtml(char *xhtmlfile)
   if (logo) fprintf(fic,"<div class=\"logo\"></div>\n\n");
   
   /* end */
+  fprintf(fic,"</div>\n\n");
   if (strcmp("none",footer)==0)
   {
-    fprintf(fic,"</div>\n\n</body>\n\n</html>\n");
+    fprintf(fic,"</body>\n\n</html>\n");
   }
   else
   {
@@ -1278,25 +1561,25 @@ void gen_xhtml(char *xhtmlfile)
   fclose(fic);
 }
 
-void parse_config(char *configfile)
+void expand(char *path)
 {
-  void expand(char *path)
+  char temp[MAXLINELENGTH];
+  if (*path=='~')
   {
-    char temp[MAXLINELENGTH];
-    if (*path=='~')
-    {
-      snprintf(temp,MAXLINELENGTH-1,"%s%s",getenv("HOME"),path+1);
-      temp[MAXLINELENGTH-1]='\0';
-      strcpy(path,temp);
-    }
+    snprintf(temp,MAXLINELENGTH-1,"%s%s",getenv("HOME"),path+1);
+    temp[MAXLINELENGTH-1]='\0';
+    strcpy(path,temp);
   }
-  
+}
+
+void parse_config(char *configfile)
+{
   FILE *fic;
   char line[MAXLINELENGTH];
   char keyword[MAXLINELENGTH];
   char value[MAXLINELENGTH];
   int configlines=0;
-  int i;
+  int i,j;
   
   if (configfile!=NULL)
   {
@@ -1453,6 +1736,7 @@ void parse_config(char *configfile)
         expand(value);
         if (debug==2) fprintf(stderr,"generating xhtml file \"%s\"\n",value);
         bestwords(words,0);
+        if (L("CHARSET")=="KOI8-R") bestruswords(ruswords,0);
         gen_xhtml(value);
         
         /* reset variables */
@@ -1460,9 +1744,24 @@ void parse_config(char *configfile)
         nburls=0;
         nbtopics=0;
         days=0;
+        currwday=-1;
+        currmon=-1;
         for (i=0;i<24*4;i++) hours[i]=0;
         lines=0;
+        for (i=0;i<31;i++)
+        {
+          lastdays[i].lines=0;
+          lastweeks[i].lines=0;
+          lastmonths[i].lines=0;
+          for (j=0;j<4;j++)
+          {
+            lastdays[i].hours[j]=0;
+            lastweeks[i].hours[j]=0;
+            lastmonths[i].hours[j]=0;
+          }
+        }
         freewords(&words);
+        freeruswords(&ruswords);
         for (i=0;i<NBWORDS;i++) topwords[i].nb=0;
         totallines=0;
         debut=time(NULL);
@@ -1501,8 +1800,27 @@ void parse_config(char *configfile)
         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); }        
+      else
+      
+      if (strcmp("months",keyword)==0)
+      {
+        if (debug==2) fprintf(stderr,"setting months to \"%s\"\n",value);
+        if (strcmp("no",value)==0) months=0;
+        else if (strcmp("yes",value)==0) months=1;
+        else { fprintf(stderr,"unknown value for \"months\" option, must be \"yes\" or \"no\"\n"); exit(1); }
+      }
+      else
+      
+      if (strcmp("weeks",keyword)==0)
+      {
+        if (debug==2) fprintf(stderr,"setting weeks to \"%s\"\n",value);
+        if (strcmp("no",value)==0) weeks=0;
+        else if (strcmp("yes",value)==0) weeks=1;
+        else { fprintf(stderr,"unknown value for \"weeks\" 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); }        
     }
   }
   fclose(fic);
@@ -1510,6 +1828,7 @@ void parse_config(char *configfile)
 
 int main(int argc,char *argv[])
 {
+  (void) setlocale(LC_ALL, "");
   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);
@@ -1522,3 +1841,4 @@ int main(int argc,char *argv[])
   }
   return(0);
 }
+