version 0.4
[irssistats] / irssistats.c
1 /* Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme [nickfile] > /path/to/file.html */
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <time.h>
6 #include <string.h>
7 #include <regex.h>
8
9 /* Config */
10 #define MAXUSERS 5000
11 #define MAXNICKLENGTH 50
12 #define MAXLINELENGTH 2000
13 #define MAXQUOTELENGTH 100
14 #define NBUSERS 50
15 #define NBUSERSTIME 10
16 #define NBURLS 5
17 #define NBTOPICS 5
18 #define NBWORDS 20
19 #define MINWORDLENGTH 5
20
21 /* irssistats */
22 #define VERSION "0.4"
23 #define URL "http://royale.zerezo.com/programmation/irssistats/"
24
25 /* Counters */
26 #define D_SMILE     0
27 #define D_FROWN     1
28 #define D_EXCLAM    2
29 #define D_QUESTION  3
30 #define D_ME        4
31 #define D_TOPIC     5
32 #define D_MODE      6
33 #define D_KICK      7
34 #define D_KICKED    8
35 #define D_URL       9
36 #define D_JOIN      10
37 #define D_NICK      11
38 #define D_MONOLOGUE 12
39 #define NBCOUNTERS  13
40 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"};
41
42 /* Languages */
43 #define NBLANGUAGES 4
44 #define NBKEYS 38
45 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
46 {
47   { /* English language */
48     { "English",      "en" },
49     { "HEADER",       "Statistics for #%s by %s" },
50     { "LEGEND",       "Legend" },
51     { "LASTDAYS",     "Lastdays statistics" },
52     { "TOPHOURS",     "Hourly statistics" },
53     { "TOPUSERS",     "Most active people" },
54     { "OTHERS",       "There are %d left not ranked..." },
55     { "NBLINES",      "lines" },
56     { "NICK",         "nick" },
57     { "AVGLETTERS",   "letters/lines" },
58     { "HOURS",        "hours" },
59     { "QUOTE",        "random message" },
60     { "TOPUSERSTIME", "Most active people by time of day" },
61     { "RANDTOPICS",   "Some topics" },
62     { "CHANGEDBY",    "changed by" },
63     { "NEWTOPIC",     "new topic" },
64     { "RANDURLS",     "Some URLs" },
65     { "POSTEDBY",     "posted by" },
66     { "POSTEDURL",    "URL" },
67     { "TOPWORDS",     "Most used words" },
68     { "WORD",         "word" },
69     { "OCCURRENCES",  "occurrences" },
70     { "BIGNUMBERS",   "Some big numbers..." },
71     { "NUMBERS",      "numbers" },
72     { "TIME",         "%d lines parsed in %d seconds" },
73     { "FOOTER",       "Statistics generated by" },
74     { "C_SMILE",      "is often happy :)" },
75     { "C_FROWN",      "is often sad :(" },
76     { "C_EXCLAM",     "yells a lot !" },
77     { "C_QUESTION",   "asks a lot of questions ?" },
78     { "C_ME",         "likes /me command" },
79     { "C_TOPIC",      "often changes the topic" },
80     { "C_MODE",       "often changes the modes" },
81     { "C_KICK",       "likes to /kick" },
82     { "C_KICKED",     "is often kicked" },
83     { "C_URL",        "posts many URLs" },
84     { "C_JOIN",       "doesn't know wether to stay or quit" },
85     { "C_NICK",       "often changes his nick" },
86     { "C_MONOLOGUE",  "speaks a lot of monologues" }
87   },
88   { /* French language */
89     { "Fran├žais",     "fr" },
90     { "HEADER",       "Statistiques de #%s par %s" },
91     { "LEGEND",       "L&eacute;gende" },
92     { "LASTDAYS",     "Statistiques des derniers jours" },
93     { "TOPHOURS",     "Statistiques horaires" },
94     { "TOPUSERS",     "Personnes les plus actives" },
95     { "OTHERS",       "Il reste %d personnes non class&eacute;es..." },
96     { "NBLINES",      "lignes" },
97     { "NICK",         "nick" },
98     { "AVGLETTERS",   "lettres/lignes" },
99     { "HOURS",        "heures" },
100     { "QUOTE",        "message al&eacute;atoire" },
101     { "TOPUSERSTIME", "Personnes les plus actives par p&eacute;riode de la journ&eacutee" },
102     { "RANDTOPICS",   "Quelques topics" },
103     { "CHANGEDBY",    "chang&eacute; par" },
104     { "NEWTOPIC",     "nouveau topic" },
105     { "RANDURLS",     "Quelques URLs" },
106     { "POSTEDBY",     "post&eacute;e par" },
107     { "POSTEDURL",    "URL" },
108     { "TOPWORDS",     "Mots les plus utilis&eacute;s" },
109     { "WORD",         "mot" },
110     { "OCCURRENCES",  "occurrences" },
111     { "BIGNUMBERS",   "Quelques grands nombres..." },
112     { "NUMBERS",      "nombres" },
113     { "TIME",         "%d lignes trait&eacute;es en %d secondes" },
114     { "FOOTER",       "Statistiques g&eacute;n&eacute;r&eacute;es par" },
115     { "C_SMILE",      "est souvent heureux :)" },
116     { "C_FROWN",      "est souvent triste :(" },
117     { "C_EXCLAM",     "hurle beaucoup !" },
118     { "C_QUESTION",   "pose beaucoup de questions ?" },
119     { "C_ME",         "aime la commande /me" },
120     { "C_TOPIC",      "change souvent le topic" },
121     { "C_MODE",       "change souvent les modes" },
122     { "C_KICK",       "aime la commande /kick" },
123     { "C_KICKED",     "est souvent kick&eacute;" },
124     { "C_URL",        "poste beaucoup d'URLs" },
125     { "C_JOIN",       "ne sait pas s'il doit rester ou partir" },
126     { "C_NICK",       "change souvent de nick" },
127     { "C_MONOLOGUE",  "parle beaucoup de monologues" }
128   },
129   { /* German language */
130     /* contributed by Valentin Gelhorn <valentin.gelhorn@web.de> */
131     { "German",       "de" },
132     { "HEADER",       "Statistiken f&uuml;r #%s von %s" },
133     { "LEGEND",       "Legende" },
134     { "LASTDAYS",     "Statistik der letzten Tage" },
135     { "TOPHOURS",     "St&uuml;ndliche Statistik" },
136     { "TOPUSERS",     "Die aktivsten Personen" },
137     { "OTHERS",       "Es bleiben noch %d uneingetragene" },
138     { "NBLINES",      "Zeilen" },
139     { "NICK",         "Nick" },
140     { "AVGLETTERS",   "Buchstaben/Zeile" },
141     { "HOURS",        "Stunden" },
142     { "QUOTE",        "Zuf&auml;llig ausgewaehlte Zitate" },
143     { "TOPUSERSTIME", "Die aktivsten Personen zur bestimmten Tageszeit" },
144     { "RANDTOPICS",   "Ein paar Topics" },
145     { "CHANGEDBY",    "Gesetzt von" },
146     { "NEWTOPIC",     "Neues topic" },
147     { "RANDURLS",     "Ein paar URLs" },
148     { "POSTEDBY",     "Geschrieben von" },
149     { "POSTEDURL",    "URL" },
150     { "TOPWORDS",     "Am h&auml;ufigsten benutze W&ouml;rter" },
151     { "WORD",         "Wort" },
152     { "OCCURRENCES",  "Vorkommen" },
153     { "BIGNUMBERS",   "Ein paar grosse Zahlen" },
154     { "NUMBERS",      "Zahlen" },
155     { "TIME",         "%d Zeilen analysiert in %d Sekunden" },
156     { "FOOTER",       "Statistiken wurden erstellt von" },
157     { "C_SMILE",      "ist oft gl&uuml;klich :)" },
158     { "C_FROWN",      "ist oft traurig :(" },
159     { "C_EXCLAM",     "schreit oft !" },
160     { "C_QUESTION",   "stellt viele Fragen ?" },
161     { "C_ME",         "mag /me'en" },
162     { "C_TOPIC",      "aendert oft das Topico" },
163     { "C_MODE",       "aendert oft die Modes" },
164     { "C_KICK",       "mag /kick'en" },
165     { "C_KICKED",     "wird oft gekickt"},
166     { "C_URL",        "schreibt viele URLs"},
167     { "C_JOIN",       "kann sich nicht entscheiden ob er bleiben oder gehen soll" },
168     { "C_NICK",       "&auml;ndert oft seinen Nick" },
169     { "C_MONOLOGUE",  "spricht oft Monologe" }
170   },
171   { /* Spanish language */
172     /* contributed by Alex <ainaker@gmx.net> */
173     { "Spanish",      "es" },
174     { "HEADER",       "Estad&iacute;sticas de #%s por %s" },
175     { "LEGEND",       "Leyenda" },
176     { "LASTDAYS",     "Estad&iacute;sticas de los &uacute;ltimos d&iacute;as" },
177     { "TOPHOURS",     "Estad&iacute;sticas por horas" },
178     { "TOPUSERS",     "Los que m&aacute;s escriben" },
179     { "OTHERS",       "Hay %d m&aacute;s que no llegaron..." },
180     { "NBLINES",      "l&iacute;neas" },
181     { "NICK",         "nick" },
182     { "AVGLETTERS",   "letras por l&iacute;nea" },
183     { "HOURS",        "horas" },
184     { "QUOTE",        "Frase aleatoria" },
185     { "TOPUSERSTIME", "Los que m&aacute;s escriben seg&uacute;n la hora" },
186     { "RANDTOPICS",   "Algunos topics" },
187     { "CHANGEDBY",    "Puestos por" },
188     { "NEWTOPIC",     "topic" },
189     { "RANDURLS",     "Algunas URLs" },
190     { "POSTEDBY",     "puestas por" },
191     { "POSTEDURL",    "URL" },
192     { "TOPWORDS",     "Palabras m&aacute;s usadas" },
193     { "WORD",         "Palabra" },
194     { "OCCURRENCES",  "Frecuencia" },
195     { "BIGNUMBERS",   "Algunos datos..." },
196     { "NUMBERS",      "N&uacute;mero de veces" },
197     { "TIME",         "%d lineas procesadas en %d segundos" },
198     { "FOOTER",       "Estad&iacute;sticas generadas por" },
199     { "C_SMILE",      "Suele estar fel&iacute;z :)" },
200     { "C_FROWN",      "Suele estar triste :(" },
201     { "C_EXCLAM",     "Grita mucho !" },
202     { "C_QUESTION",   "Hace muchas preguntas ?" },
203     { "C_ME",         "Abusa del comando /me" },
204     { "C_TOPIC",      "Suele cambiar el topic" },
205     { "C_MODE",       "Cambia a veces los modos del canal" },
206     { "C_KICK",       "Le gusta patear" },
207     { "C_KICKED",     "Es pateado con frecuencia" },
208     { "C_URL",        "Pone muchas URLs" },
209     { "C_JOIN",       "No sabe si irse o quedarse" },
210     { "C_NICK",       "Cambia mucho de nick" },
211     { "C_MONOLOGUE",  "Habla solo" }
212   }
213 };
214
215 int language;
216
217 char *L(char *key)
218 {
219   int i;
220   for (i=1;i<=NBKEYS;i++) if (strcmp(key,keys[language][i][0])==0) return(keys[language][i][1]);
221   fprintf(stderr,"unknown language key: %s\n",key);
222   return("");
223 }
224
225 /* Themes */
226 #define NBTHEMES 5
227 #define NBCOLORS 9
228 char *colors[NBTHEMES][NBCOLORS+1][2]= /* first key used for theme name/description and abbreviation */
229 {
230   { /* Default theme */
231     { "White background", "default" },
232     { "BGCOLOR", "#FFFFFF" },
233     { "TEXT",    "#000000" },
234     { "LINK",    "#0000EE" },
235     { "VLINK",   "#551A8B" },
236     { "ALINK",   "#FF0000" },
237     { "TITLE1",  "#000000" },
238     { "TITLE2",  "#000000" },
239     { "BGTABLE", "#EEEEEE" },
240     { "BGTITLE", "#FFEEEE" }
241   },
242   { /* Dark theme */
243     { "Black background", "dark" },
244     { "BGCOLOR", "#000000" },
245     { "TEXT",    "#FFFFFF" },
246     { "LINK",    "#AAAAFF" },
247     { "VLINK",   "#CCCCDD" },
248     { "ALINK",   "#FFAAAA" },
249     { "TITLE1",  "#AAAAFF" },
250     { "TITLE2",  "#FFAAAA" },
251     { "BGTABLE", "#225522" },
252     { "BGTITLE", "#552222" }
253   },
254   { /* zeRezo theme */
255     { "Green theme...", "zerezo" },
256     { "BGCOLOR", "#000000" },
257     { "TEXT",    "#FFFFFF" },
258     { "LINK",    "#14F024" },
259     { "VLINK",   "#0CBA18" },
260     { "ALINK",   "#FFFFFF" },
261     { "TITLE1",  "#0CBA18" },
262     { "TITLE2",  "#84DB8C" },
263     { "BGTABLE", "#085D10" },
264     { "BGTITLE", "#0B810B" }
265   },
266   { /* tit-namour theme */
267     { "Purple and Pink", "namour" },
268     { "BGCOLOR", "#9933CC" },
269     { "TEXT",    "#DDAAFF" },
270     { "LINK",    "#CC99FF" },
271     { "VLINK",   "#999999" },
272     { "ALINK",   "#FFC8C8" },
273     { "TITLE1",  "#FFC8C8" },
274     { "TITLE2",  "#FFC8C8" },
275     { "BGTABLE", "#7711AA" },
276     { "BGTITLE", "#550088" }
277   },
278   { /* zeDuel theme */
279     { "Orange theme...", "zeduel" },
280     { "BGCOLOR", "#FFFFFF" },
281     { "TEXT",    "#000000" },
282     { "LINK",    "#FF7700" },
283     { "VLINK",   "#C05A00" },
284     { "ALINK",   "#FF9A41" },
285     { "TITLE1",  "#C05A00" },
286     { "TITLE2",  "#FF7700" },
287     { "BGTABLE", "#FFEEEE" },
288     { "BGTITLE", "#FF7700" }
289   }
290 };
291
292 int theme;
293
294 char *T(char *color)
295 {
296   int i;
297   for (i=1;i<=NBCOLORS;i++) if (strcmp(color,colors[theme][i][0])==0) return(colors[theme][i][1]);
298   fprintf(stderr,"unknown theme color: %s\n",color);
299   return("");
300 }
301
302 /* Variables */
303
304 char *channel;
305 char *maintainer;
306
307 struct
308 {
309   char nick[MAXNICKLENGTH];
310   int lines;
311   int letters;
312   int hours[4];
313   char quote[MAXQUOTELENGTH+1];
314   int counters[NBCOUNTERS];
315 } users[MAXUSERS];
316 int nbusers=0;
317
318 struct
319 {
320   char nick[MAXNICKLENGTH];
321   char url[MAXLINELENGTH];
322   char shorturl[MAXQUOTELENGTH+1];
323 } urls[5];
324 int nburls=0;
325
326 struct
327
328   char nick[MAXNICKLENGTH];
329   char topic[MAXQUOTELENGTH+1];
330 } topics[5];
331 int nbtopics=0;
332
333 struct
334 {
335   int lines;
336   int hours[4];
337 } lastdays[31];
338 int days=0;
339
340 int hours[24];
341 int lines=0;
342
343 struct letter
344 {
345   int nb;
346   struct letter *next[26];
347 } words;
348
349 struct
350 {
351   int nb;
352   char word[MAXLINELENGTH];
353 } topwords[NBWORDS];
354
355 #define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
356 #define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
357 void findwords(char *message)
358 {
359   int i,c;
360   struct letter *pos,*tmp;
361   for (;;)
362   {
363     while (!isletter(*message)) if (*message=='\0') return; else message++;
364     pos=&words;
365     while (isletter(*message))
366     {
367       c=lowercase(*message)-'a';
368       if (pos->next[(int)c]==NULL)
369       {
370         tmp=malloc(sizeof(struct letter));
371         tmp->nb=0;
372         for (i=0;i<26;i++) tmp->next[i]=NULL;
373         pos->next[(int)c]=tmp;
374       }
375       pos=pos->next[(int)c];
376       message++; 
377     }
378     pos->nb++;
379   }
380   return;
381 }
382
383 char tempword[MAXLINELENGTH];
384 void bestwords(struct letter pos,int cur)
385 {
386   int i,j;
387   if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
388   {
389     for (i=0;pos.nb<topwords[i].nb;i++);
390     for (j=NBWORDS-1;j>i;j--)
391     {
392       topwords[j].nb=topwords[j-1].nb;
393       strcpy(topwords[j].word,topwords[j-1].word);
394     }
395     topwords[i].nb=pos.nb;
396     strcpy(topwords[i].word,tempword);
397   }
398   for (i=0;i<26;i++) if (pos.next[i]!=NULL)
399   {
400     tempword[cur]='a'+i;
401     bestwords(*(pos.next[i]),cur+1);
402   }
403   tempword[cur]='\0';
404 }
405
406 void printhtml(char *string) /* replace < and > by &lt; and &gt; */
407 {
408   while (*string!='\0')
409   {
410     switch (*string)
411     {
412       case '<':printf("&lt;"); break;
413       case '>':printf("&gt;"); break;
414       case '&':printf("&amp;"); break;
415       default:printf("%c",*string); break;
416     }
417     string++;
418   }
419   return;
420 }
421
422 int dichotomic(char *nick)
423 {
424   int i,j,start=0,end=nbusers-1,middle;
425   while (start<=end)
426   {
427     middle=(start+end)/2;
428     if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
429   }
430   if (strcmp(nick,users[start].nick)!=0)
431   {
432     nbusers++;
433     if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
434     for (i=nbusers-1;i>start;i--)
435     {
436       strcpy(users[i].nick,users[i-1].nick);
437       users[i].lines=users[i-1].lines;
438       users[i].letters=users[i-1].letters;
439       for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
440       strcpy(users[i].quote,users[i-1].quote);
441       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=users[i-1].counters[j];
442     }
443     strcpy(users[start].nick,nick);
444     users[start].lines=0;
445     users[start].letters=0;
446     for (j=0;j<4;j++) users[start].hours[j]=0;
447     users[start].quote[0]='\0';
448     for (j=0;j<NBCOUNTERS;j++) users[start].counters[j]=0;
449   }
450   return(start);
451 }
452
453 int main(int argc,char *argv[])
454 {
455   int i,j,k;
456   int max,user,temp,hour;
457   int mononick,monolines;
458   time_t debut;
459   int totallines=0;
460   int pos=0;
461   char c;
462   char *nick,*message;
463   char line[MAXLINELENGTH];
464   FILE *fic;
465   regex_t preg;
466   
467   /*** INIT ***/
468   
469   if ((argc<5) || (argc>6))
470   {
471     fprintf(stderr,"Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme [nickfile] > /path/to/file.html\n\n");
472     fprintf(stderr,"Version :\nirssistats %s\n\n",VERSION);
473     fprintf(stderr,"Supported languages :\n");
474     for (i=0;i<NBLANGUAGES;i++) fprintf(stderr,"%s = %s\n",keys[i][0][1],keys[i][0][0]);
475     fprintf(stderr,"\nSupported themes :\n");
476     for (i=0;i<NBTHEMES;i++) fprintf(stderr,"%s = %s\n",colors[i][0][1],colors[i][0][0]);
477     return(1);
478   }
479   channel=argv[1];
480   maintainer=argv[2];
481   for (i=0;i<NBLANGUAGES;i++) if (strcmp(argv[3],keys[i][0][1])==0) { language=i; break; }
482   if (i==NBLANGUAGES)
483   {
484     fprintf(stderr,"Invalid language : %s\n",argv[3]);
485     return(1);
486   }
487   for (i=0;i<NBTHEMES;i++) if (strcmp(argv[4],colors[i][0][1])==0) { theme=i; break; }
488   if (i==NBTHEMES)
489   {
490     fprintf(stderr,"Invalid theme : %s\n",argv[4]);
491     return(1);
492   }
493
494   /*** LOG ***/
495   
496   srand(debut=time(NULL));
497   fprintf(stderr,"working:");
498   while (!feof(stdin))
499   {
500     c=getchar();
501     line[pos++]=c;
502     if (pos>=MAXLINELENGTH) { fprintf(stderr,"line too long\n"); exit(1); }
503     if (c=='\n')
504     {
505       line[pos-1]='\0';
506       totallines++;
507       if (totallines%10000==0) { fprintf(stderr,"."); fflush(stdout); }
508       if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
509       {
510         for (i=30;i>0;i--)
511         {
512           lastdays[i].lines=lastdays[i-1].lines;
513           for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
514         }
515         lastdays[0].lines=0;
516         for (j=0;j<4;j++) lastdays[0].hours[j]=0;
517         days++;
518       }
519       else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
520       {
521         for (i=strlen(line);line[i]!=' ';i--);
522         nick=&line[i+1];
523         users[dichotomic(nick)].counters[D_MODE]++;
524       }
525       else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
526       {
527         for (i=10;line[i]!=' ';i++);
528         line[i]='\0';
529         nick=&line[10];
530         message=&line[i+1];
531         if (strncmp("changed the topic of",message,20)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
532         {
533           users[dichotomic(nick)].counters[D_TOPIC]++;
534           for (i=21;message[i]!=':';i++);
535           message=&message[i+2];
536           nbtopics++;
537           if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
538           {
539             temp=nbtopics<=NBTOPICS?nbtopics-1:rand()%NBTOPICS;
540             strcpy(topics[temp].nick,nick);
541             strncpy(topics[temp].topic,message,MAXQUOTELENGTH);
542           }
543         }
544         else if (strncmp("was kicked from",message,15)==0) /* 00:00 -!- Nick was kicked from #channel by Nick [Reason] */
545         {
546           users[dichotomic(nick)].counters[D_KICKED]++;
547           for (i=16;message[i]!=' ';i++);
548           message=&message[i+4];
549           for (i=0;message[i]!=' ';i++);
550           message[i]='\0';
551           users[dichotomic(message)].counters[D_KICK]++;
552         }
553         else if (strncmp("is now known as",message,15)==0) /* 00:00 -!- Nick is now known as Nick */
554           users[dichotomic(nick)].counters[D_NICK]++;
555         else if (message[0]=='[') /* 00:00 -!- Nick [user@host] something... */
556         {
557           for (i=0;message[i]!=']';i++);
558           message=&message[i+2];
559           if (strncmp("has joined",message,10)==0) /* 00:00 -!- Nick [user@host] has joined #channel */
560             users[dichotomic(nick)].counters[D_JOIN]++;
561           else if (strncmp("has quit",message,8)==0); /* 00:00 -!- Nick [user@host] has quit [Reason] */
562           else if (strncmp("has left",message,8)==0); /* 00:00 -!- Nick [user@host] has left #channel [Reason] */
563           else;
564         }
565       }
566       else if ((line[6]=='<') || (line[7]=='*'))
567       {
568         line[2]='\0';
569         hour=atoi(line);
570         if (line[7]=='*') /* 00:00  * Nick the message */
571         {
572           for (i=9;line[i]!=' ';i++);
573           nick=&line[9];
574           message=&line[i+1];
575         }
576         else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
577                                /* 00:00 <>>?Nick<<> the personal message */
578         {
579           for (i=10;line[i]!='<';i++);
580           nick=&line[10];
581           if (line[9]=='>') nick++;
582           message=&line[i+5];
583         }
584         else /* 00:00 <?Nick> the message */
585         {
586           for (i=8;line[i]!='>';i++);
587           nick=&line[8];
588           message=&line[i+2];
589         }
590         line[i]='\0';
591         i=dichotomic(nick);
592         if (line[7]=='*') users[i].counters[D_ME]++;
593         if (i==mononick)
594         {
595           monolines++;
596           if (monolines==5) users[i].counters[D_MONOLOGUE]++;
597         }
598         else
599         {
600           mononick=i;
601           monolines=1;
602         }
603         j=strlen(message);
604         users[i].lines++;
605         users[i].letters+=j;
606         users[i].hours[hour/6]++;
607         lastdays[0].lines++;
608         lastdays[0].hours[hour/6]++;
609         lines++;
610         hours[hour]++;
611         if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
612         else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
613         else if ((message[j-3]==' ')&&(message[j-2]==':'))
614         {
615           if (message[j-1]==')') users[i].counters[D_SMILE]++;
616           else if (message[j-1]=='(') users[i].counters[D_FROWN]++;
617         }
618         if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
619         if (strncmp("http://",message,7)==0)
620         {
621           users[i].counters[D_URL]++;
622           for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
623           message[i]='\0';
624           nburls++;
625           if ((nburls<=NBURLS) || (rand()%(nburls/NBURLS)==0))
626           {
627             temp=nburls<=NBURLS?nburls-1:rand()%NBURLS;
628             strcpy(urls[temp].nick,nick);
629             strcpy(urls[temp].url,message);
630             strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
631           }
632         }
633         findwords(message);
634       }
635       pos=0;
636     }
637   }
638   fprintf(stderr,"done\n");
639
640   bestwords(words,0);
641
642   /*** ALIAS ***/
643   
644   if (argc==6)
645   {
646     if ((fic=fopen(argv[5],"rt"))==NULL) { fprintf(stderr,"can't open nick file\n"); exit(1); }
647     while (fscanf(fic,"%s",line)==1)
648     {
649       user=dichotomic(line);
650       fscanf(fic,"%s",line);
651       if (regcomp(&preg,line,0)!=0) { fprintf(stderr,"error in nick file"); exit(1); }
652       temp=users[user].lines;
653       for (i=0;i<nbusers;i++) if ((i!=user) && (regexec(&preg,users[i].nick,0,0,0)==0) && (users[i].lines>=0))
654       {
655         if (users[i].lines>temp) /* for nick alias, keep the random quote of the most used nick */
656         {
657           strcpy(users[user].quote,users[i].quote);
658           temp=users[i].lines;
659         }
660         users[user].lines+=users[i].lines;
661         users[user].letters+=users[i].letters;
662         for (j=0;j<4;j++) users[user].hours[j]+=users[i].hours[j];
663         for (j=0;j<NBCOUNTERS;j++) users[user].counters[j]+=users[i].counters[j];
664         /* "remove" old user */
665         users[i].lines=-1;
666         users[i].letters=-1;
667         for (j=0;j<4;j++) users[i].hours[j]=-1;
668         for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
669       }
670       regfree(&preg);
671     }
672     fclose(fic);
673   }
674   
675   /*** HTML ***/
676
677   /* header */
678   printf("<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
679   printf("<html>\n\n<head>\n<base target=\"_blank\">\n<title>");
680   printf(L("HEADER"),channel,maintainer);
681   printf("</title>\n</head>\n\n");
682   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"));
683   printf(L("HEADER"),channel,maintainer);
684   printf("</h1></font>\n%s<br>\n<br><br>\n\n",ctime(&debut));
685
686   /* legend */
687   printf("<font color=\"%s\"><h3>%s</h3></font>\n<table bgcolor=%s>\n<tr>\n",T("TITLE2"),L("LEGEND"),T("BGTABLE"));
688   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);
689   printf("</tr>\n</table>\n<br><br>\n\n");
690   
691   /* last days */
692   printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("LASTDAYS"));
693   max=-1;
694   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
695   for (i=30;i>=0;i--)
696   {
697     printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%d</font><br>",lastdays[i].lines);
698     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);
699     printf("</td>\n");
700   }
701   printf("</tr>\n<tr>\n");
702   for (i=30;i>=0;i--)
703     printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
704   printf("</tr>\n</table>\n<br><br>\n\n");
705   
706   /* top hours */
707   printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("TOPHOURS"));
708   max=-1;
709   for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
710   for (i=0;i<24;i++)
711   {
712     printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%.1f%%</font><br>",lines!=0?(float)100*hours[i]/lines:0);
713     if (hours[i]!=0) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",i/6+1,150*hours[i]/max);
714     printf("</td>\n");
715   }
716   printf("</tr>\n<tr>\n");
717   for (i=0;i<24;i++)
718     printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
719   printf("</tr>\n</table>\n<br><br>\n\n");
720   
721   /* top users */
722   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERS"));
723   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"));
724   for (i=1;i<=NBUSERS;i++)
725   {
726     user=-1;
727     max=0;
728     for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
729     if (user!=-1)
730     {
731       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"));
732       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);
733       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"));
734       printhtml(users[user].quote);
735       printf("\"</td></tr>\n");
736       users[user].lines=-1;
737     }    
738   }
739   printf("</table>\n");
740   temp=0;
741   for (i=0;i<=nbusers;i++) if (users[i].lines>=0) temp++;
742   if (temp>0)
743   {
744     printf("<br>");
745     printf(L("OTHERS"),temp);
746     printf("<br>\n");
747   }
748   printf("<br><br>\n\n");
749   
750   /* top users by time */
751   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERSTIME"));
752   printf("<table>\n<tr><td></td>");
753   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);
754   printf("</tr>\n");
755   for (i=1;i<=NBUSERSTIME;i++)
756   {
757     printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td>",T("BGTABLE"),i);
758     for (j=0;j<4;j++)
759     {
760       user=-1;
761       max=0;
762       for (k=0;k<nbusers;k++) if (users[k].hours[j]>max) max=users[user=k].hours[j];
763       if (user!=-1)
764       {
765         printf("<td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td>",T("BGTABLE"),users[user].nick,T("BGTABLE"),users[user].hours[j]);
766         users[user].hours[j]=-1;
767       }
768       else printf("<td></td><td></td>");
769     }
770     printf("</tr>\n");
771   }
772   printf("</table>\n<br><br>\n\n");
773
774   /* random topics */
775   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDTOPICS"));
776   printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("CHANGEDBY"),T("BGTITLE"),L("NEWTOPIC"));
777   for (i=nbtopics<NBTOPICS?nbtopics-1:NBTOPICS-1;i>=0;i--)
778   {
779     printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"",T("BGTABLE"),topics[i].nick,T("BGTABLE"));
780     printhtml(topics[i].topic);
781     printf("\"</td></tr>\n");
782   }
783   printf("</table>\n<br><br>\n\n");
784   
785   /* random urls */
786   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDURLS"));
787   printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("POSTEDBY"),T("BGTITLE"),L("POSTEDURL"));
788   for (i=nburls<NBURLS?nburls-1:NBURLS-1;i>=0;i--)
789   {
790     printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"<a href=\"%s\">",T("BGTABLE"),urls[i].nick,T("BGTABLE"),urls[i].url);
791     printhtml(urls[i].shorturl);
792     printf("</a>\"</td></tr>\n");
793   }
794   printf("</table>\n<br><br>\n\n");
795   
796   /* top words */
797   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPWORDS"));
798   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"));
799   for (i=0;i<NBWORDS;i++)
800     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);
801   printf("</table>\n<br><br>\n\n");
802
803   /* big numbers */
804   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("BIGNUMBERS"));
805   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"));
806   for (i=0;i<NBCOUNTERS;i++)
807   {
808     user=-1;
809     max=0;
810     for (j=0;j<nbusers;j++) if (users[j].counters[i]>max) max=users[user=j].counters[i];
811     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]);
812   }
813   printf("</table>\n<br><br>\n\n");
814   
815   /* footer */
816   printf(L("TIME"),totallines,(int)(time(NULL)-debut));
817   printf("<br>\n%s <a href=\"%s\">irssistats %s</a>",L("FOOTER"),URL,VERSION);
818   printf("\n\n</center>\n\n</body>\n\n</html>\n");
819   
820   return(0);
821 }