version 0.3
[irssistats] / irssistats.c
1 /* Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme > /path/to/file.html */
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <time.h>
6 #include <string.h>
7
8 /* Config */
9 #define MAXUSERS 5000
10 #define MAXNICKLENGTH 50
11 #define MAXLINELENGTH 2000
12 #define MAXQUOTELENGTH 100
13 #define NBUSERS 50
14 #define NBUSERSTIME 10
15 #define NBURLS 5
16 #define NBTOPICS 5
17 #define NBWORDS 20
18 #define MINWORDLENGTH 5
19
20 /* irssistats */
21 #define VERSION "0.3"
22 #define URL "http://royale.zerezo.com/programmation/irssistats/"
23
24 /* Counters */
25 #define D_SMILE     0
26 #define D_FROWN     1
27 #define D_EXCLAM    2
28 #define D_QUESTION  3
29 #define D_ME        4
30 #define D_TOPIC     5
31 #define D_MODE      6
32 #define D_KICK      7
33 #define D_KICKED    8
34 #define D_URL       9
35 #define D_JOIN      10
36 #define D_NICK      11
37 #define D_MONOLOGUE 12
38 #define NBCOUNTERS  13
39 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"};
40
41 /* Languages */
42 #define NBLANGUAGES 2
43 #define NBKEYS 38
44 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
45 {
46   { /* English language */
47     { "English",      "en" },
48     { "HEADER",       "Statistics for #%s by %s" },
49     { "LEGEND",       "Legend" },
50     { "LASTDAYS",     "Lastdays statistics" },
51     { "TOPHOURS",     "Hourly statistics" },
52     { "TOPUSERS",     "Most active people" },
53     { "OTHERS",       "There are %d left not ranked..." },
54     { "NBLINES",      "lines" },
55     { "NICK",         "nick" },
56     { "AVGLETTERS",   "letters/lines" },
57     { "HOURS",        "hours" },
58     { "QUOTE",        "random message" },
59     { "TOPUSERSTIME", "Most active people by time of day" },
60     { "RANDTOPICS",   "Some topics" },
61     { "CHANGEDBY",    "changed by" },
62     { "NEWTOPIC",     "new topic" },
63     { "RANDURLS",     "Some URLs" },
64     { "POSTEDBY",     "posted by" },
65     { "POSTEDURL",    "URL" },
66     { "TOPWORDS",     "Most used words" },
67     { "WORD",         "word" },
68     { "OCCURRENCES",  "occurrences" },
69     { "BIGNUMBERS",   "Some big numbers..." },
70     { "NUMBERS",      "numbers" },
71     { "TIME",         "%d lines parsed in %d seconds" },
72     { "FOOTER",       "Statistics generated by" },
73     { "C_SMILE",      "is often happy :)" },
74     { "C_FROWN",      "is often sad :(" },
75     { "C_EXCLAM",     "yells a lot !" },
76     { "C_QUESTION",   "asks a lot of questions ?" },
77     { "C_ME",         "likes /me command" },
78     { "C_TOPIC",      "often changes the topic" },
79     { "C_MODE",       "often changes the modes" },
80     { "C_KICK",       "likes to /kick" },
81     { "C_KICKED",     "is often kicked" },
82     { "C_URL",        "posts many URLs" },
83     { "C_JOIN",       "doesn't know wether to stay or quit" },
84     { "C_NICK",       "often changes his nick" },
85     { "C_MONOLOGUE",  "speaks a lot of monologues" }
86   },
87   { /* French language */
88     { "Fran├žais",     "fr" },
89     { "HEADER",       "Statistiques de #%s par %s" },
90     { "LEGEND",       "L&eacute;gende" },
91     { "LASTDAYS",     "Statistiques des derniers jours" },
92     { "TOPHOURS",     "Statistiques horaires" },
93     { "TOPUSERS",     "Personnes les plus actives" },
94     { "OTHERS",       "Il reste %d personnes non class&eacute;es..." },
95     { "NBLINES",      "lignes" },
96     { "NICK",         "nick" },
97     { "AVGLETTERS",   "lettres/lignes" },
98     { "HOURS",        "heures" },
99     { "QUOTE",        "message al&eacute;atoire" },
100     { "TOPUSERSTIME", "Personnes les plus actives par p&eacute;riode de la journ&eacutee" },
101     { "RANDTOPICS",   "Quelques topics" },
102     { "CHANGEDBY",    "chang&eacute; par" },
103     { "NEWTOPIC",     "nouveau topic" },
104     { "RANDURLS",     "Quelques URLs" },
105     { "POSTEDBY",     "post&eacute;e par" },
106     { "POSTEDURL",    "URL" },
107     { "TOPWORDS",     "Mots les plus utilis&eacute;s" },
108     { "WORD",         "mot" },
109     { "OCCURRENCES",  "occurrences" },
110     { "BIGNUMBERS",   "Quelques grands nombres..." },
111     { "NUMBERS",      "nombres" },
112     { "TIME",         "%d lignes trait&eacute;es en %d secondes" },
113     { "FOOTER",       "Statistiques g&eacute;n&eacute;r&eacute;es par" },
114     { "C_SMILE",      "est souvent heureux :)" },
115     { "C_FROWN",      "est souvent triste :(" },
116     { "C_EXCLAM",     "hurle beaucoup !" },
117     { "C_QUESTION",   "pose beaucoup de questions ?" },
118     { "C_ME",         "aime la commande /me" },
119     { "C_TOPIC",      "change souvent le topic" },
120     { "C_MODE",       "change souvent les modes" },
121     { "C_KICK",       "aime la commande /kick" },
122     { "C_KICKED",     "est souvent kick&eacute;" },
123     { "C_URL",        "poste beaucoup d'URLs" },
124     { "C_JOIN",       "ne sait pas s'il doit rester ou partir" },
125     { "C_NICK",       "change souvent de nick" },
126     { "C_MONOLOGUE",  "parle beaucoup de monologues" }
127   }
128 };
129
130 int language;
131
132 char *L(char *key)
133 {
134   int i;
135   for (i=1;i<=NBKEYS;i++) if (strcmp(key,keys[language][i][0])==0) return(keys[language][i][1]);
136   fprintf(stderr,"unknown language key: %s\n",key);
137   return("");
138 }
139
140 /* Themes */
141 #define NBTHEMES 5
142 #define NBCOLORS 9
143 char *colors[NBTHEMES][NBCOLORS+1][2]= /* first key used for theme name/description and abbreviation */
144 {
145   { /* Default theme */
146     { "White background", "default" },
147     { "BGCOLOR", "#FFFFFF" },
148     { "TEXT",    "#000000" },
149     { "LINK",    "#0000EE" },
150     { "VLINK",   "#551A8B" },
151     { "ALINK",   "#FF0000" },
152     { "TITLE1",  "#000000" },
153     { "TITLE2",  "#000000" },
154     { "BGTABLE", "#EEEEEE" },
155     { "BGTITLE", "#FFEEEE" }
156   },
157   { /* Dark theme */
158     { "Black background", "dark" },
159     { "BGCOLOR", "#000000" },
160     { "TEXT",    "#FFFFFF" },
161     { "LINK",    "#AAAAFF" },
162     { "VLINK",   "#CCCCDD" },
163     { "ALINK",   "#FFAAAA" },
164     { "TITLE1",  "#AAAAFF" },
165     { "TITLE2",  "#FFAAAA" },
166     { "BGTABLE", "#225522" },
167     { "BGTITLE", "#552222" }
168   },
169   { /* zeRezo theme */
170     { "Green theme...", "zerezo" },
171     { "BGCOLOR", "#000000" },
172     { "TEXT",    "#FFFFFF" },
173     { "LINK",    "#14F024" },
174     { "VLINK",   "#0CBA18" },
175     { "ALINK",   "#FFFFFF" },
176     { "TITLE1",  "#0CBA18" },
177     { "TITLE2",  "#84DB8C" },
178     { "BGTABLE", "#085D10" },
179     { "BGTITLE", "#0B810B" }
180   },
181   { /* tit-namour theme */
182     { "Purple and Pink", "namour" },
183     { "BGCOLOR", "#9933CC" },
184     { "TEXT",    "#DDAAFF" },
185     { "LINK",    "#CC99FF" },
186     { "VLINK",   "#999999" },
187     { "ALINK",   "#FFC8C8" },
188     { "TITLE1",  "#FFC8C8" },
189     { "TITLE2",  "#FFC8C8" },
190     { "BGTABLE", "#7711AA" },
191     { "BGTITLE", "#550088" }
192   },
193   { /* zeDuel theme */
194     { "Orange theme...", "zeduel" },
195     { "BGCOLOR", "#FFFFFF" },
196     { "TEXT",    "#000000" },
197     { "LINK",    "#FF7700" },
198     { "VLINK",   "#C05A00" },
199     { "ALINK",   "#FF9A41" },
200     { "TITLE1",  "#C05A00" },
201     { "TITLE2",  "#FF7700" },
202     { "BGTABLE", "#FFEEEE" },
203     { "BGTITLE", "#FF7700" }
204   }
205 };
206
207 int theme;
208
209 char *T(char *color)
210 {
211   int i;
212   for (i=1;i<=NBCOLORS;i++) if (strcmp(color,colors[theme][i][0])==0) return(colors[theme][i][1]);
213   fprintf(stderr,"unknown theme color: %s\n",color);
214   return("");
215 }
216
217 /* Variables */
218
219 char *channel;
220 char *maintainer;
221
222 struct 
223 {
224   char nick[MAXNICKLENGTH];
225   int lines;
226   int letters;
227   int hours[4];
228   char quote[MAXQUOTELENGTH+1];
229   int counters[NBCOUNTERS];
230 } users[MAXUSERS];
231 int nbusers=0;
232
233 struct
234 {
235   char nick[MAXNICKLENGTH];
236   char url[MAXLINELENGTH];
237   char shorturl[MAXQUOTELENGTH+1];
238 } urls[5];
239 int nburls=0;
240
241 struct
242
243   char nick[MAXNICKLENGTH];
244   char topic[MAXQUOTELENGTH+1];
245 } topics[5];
246 int nbtopics=0;
247
248 struct
249 {
250   int lines;
251   int hours[4];
252 } lastdays[31];
253 int days=0;
254
255 int hours[24];
256 int lines=0;
257
258 struct letter
259 {
260   int nb;
261   struct letter *next[26];
262 } words;
263
264 struct
265 {
266   int nb;
267   char word[MAXLINELENGTH];
268 } topwords[NBWORDS];
269
270 #define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
271 #define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
272 void findwords(char *message)
273 {
274   int i,c;
275   struct letter *pos,*tmp;
276   for (;;)
277   {
278     while (!isletter(*message)) if (*message=='\0') return; else message++;
279     pos=&words;
280     while (isletter(*message))
281     {
282       c=lowercase(*message)-'a';
283       if ((*pos).next[(int)c]==NULL)
284       {
285         tmp=malloc(sizeof(struct letter));
286         (*tmp).nb=0;
287         for (i=0;i<26;i++) (*tmp).next[i]=NULL;
288         (*pos).next[(int)c]=tmp;
289       }
290       pos=(*pos).next[(int)c];
291       message++; 
292     }
293     (*pos).nb++;
294   }
295   return;
296 }
297
298 char tempword[MAXLINELENGTH];
299 void bestwords(struct letter pos,int cur)
300 {
301   int i,j;
302   if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
303   {
304     for (i=0;pos.nb<topwords[i].nb;i++);
305     for (j=NBWORDS-1;j>i;j--)
306     {
307       topwords[j].nb=topwords[j-1].nb;
308       strcpy(topwords[j].word,topwords[j-1].word);
309     }
310     topwords[i].nb=pos.nb;
311     strcpy(topwords[i].word,tempword);
312   }
313   for (i=0;i<26;i++) if (pos.next[i]!=NULL)
314   {
315     tempword[cur]='a'+i;
316     bestwords(*(pos.next[i]),cur+1);
317   }
318   tempword[cur]='\0';
319 }
320
321 void printhtml(char *string) /* replace < and > by &lt; and &gt; */
322 {
323   while (*string!='\0')
324   {
325     switch (*string)
326     {
327       case '<':printf("&lt;"); break;
328       case '>':printf("&gt;"); break;
329       case '&':printf("&amp;"); break;
330       default:printf("%c",*string); break;
331     }
332     string++;
333   }
334   return;
335 }
336
337 int dichotomic(char *nick)
338 {
339   int i,j,start=0,end=nbusers-1,middle;
340   while (start<=end)
341   {
342     middle=(start+end)/2;
343     if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
344   }
345   if (strcmp(nick,users[start].nick)!=0)
346   {
347     nbusers++;
348     if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
349     for (i=nbusers-1;i>start;i--)
350     {
351       strcpy(users[i].nick,users[i-1].nick);
352       users[i].lines=users[i-1].lines;
353       users[i].letters=users[i-1].letters;
354       for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
355       strcpy(users[i].quote,users[i-1].quote);
356       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=users[i-1].counters[j];
357     }
358     strcpy(users[start].nick,nick);
359     users[start].lines=0;
360     users[start].letters=0;
361     for (j=0;j<4;j++) users[start].hours[j]=0;
362     users[start].quote[0]='\0';
363     for (j=0;j<NBCOUNTERS;j++) users[start].counters[j]=0;
364   }
365   return(start);
366 }
367
368 int main(int argc,char *argv[])
369 {
370   int i,j,k;
371   int max,user,hour;
372   int mononick,monolines;
373   time_t debut;
374   int totallines=0;
375   int pos=0;
376   char c;
377   char *nick,*message;
378   char line[MAXLINELENGTH];
379   
380   /*** INIT ***/
381   
382   if (argc!=5)
383   {
384     fprintf(stderr,"Usage: cat /path/to/file.log | ./irssistats channel maintainer language theme > /path/to/file.html\n\n");
385     fprintf(stderr,"Version :\nirssistats %s\n\n",VERSION);
386     fprintf(stderr,"Supported languages :\n");
387     for (i=0;i<NBLANGUAGES;i++) fprintf(stderr,"%s = %s\n",keys[i][0][1],keys[i][0][0]);
388     fprintf(stderr,"\nSupported themes :\n");
389     for (i=0;i<NBTHEMES;i++) fprintf(stderr,"%s = %s\n",colors[i][0][1],colors[i][0][0]);
390     return(1);
391   }
392   channel=argv[1];
393   maintainer=argv[2];
394   for (i=0;i<NBLANGUAGES;i++) if (strcmp(argv[3],keys[i][0][1])==0) { language=i; break; }
395   if (i==NBLANGUAGES)
396   {
397     fprintf(stderr,"Invalid language : %s\n",argv[3]);
398     return(1);
399   }
400   for (i=0;i<NBTHEMES;i++) if (strcmp(argv[4],colors[i][0][1])==0) { theme=i; break; }
401   if (i==NBTHEMES)
402   {
403     fprintf(stderr,"Invalid theme : %s\n",argv[4]);
404     return(1);
405   }
406
407   /*** LOG ***/
408   
409   srand(debut=time(NULL));
410   fprintf(stderr,"working:");
411   while (!feof(stdin))
412   {
413     c=getchar();
414     line[pos++]=c;
415     if (pos>=MAXLINELENGTH) { fprintf(stderr,"line too long\n"); exit(1); }
416     if (c=='\n')
417     {
418       line[pos-1]='\0';
419       totallines++;
420       if (totallines%10000==0) { fprintf(stderr,"."); fflush(stdout); }
421       if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
422       {
423         for (i=30;i>0;i--)
424         {
425           lastdays[i].lines=lastdays[i-1].lines;
426           for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
427         }
428         lastdays[0].lines=0;
429         for (j=0;j<4;j++) lastdays[0].hours[j]=0;
430         days++;
431       }
432       else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by Nick(, Nick2, Nick3...) */
433       {
434         for (i=15;(line[i]!=']')||(line[i+1]!=' ');i++);
435         nick=&line[i+5];
436         for (i=0;(nick[i]!='\0')&&(nick[i]!=',');i++);
437         nick[i]='\0';
438         users[dichotomic(nick)].counters[D_MODE]++;
439       }
440       else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
441       {
442         for (i=10;line[i]!=' ';i++);
443         line[i]='\0';
444         nick=&line[10];
445         message=&line[i+1];
446         if (strncmp("changed the topic of",message,20)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
447         {
448           users[dichotomic(nick)].counters[D_TOPIC]++;
449           for (i=21;message[i]!=':';i++);
450           message=&message[i+2];
451           nbtopics++;
452           if ((nbtopics<=5) || (rand()%nbtopics==0))
453           {
454             for (i=4;i>0;i--)
455             {
456               strcpy(topics[i].nick,topics[i-1].nick);
457               strcpy(topics[i].topic,topics[i-1].topic);
458             }
459             strcpy(topics[0].nick,nick);
460             strncpy(topics[0].topic,message,MAXQUOTELENGTH);
461           }
462         }
463         else if (strncmp("was kicked from",message,15)==0) /* 00:00 -!- Nick was kicked from #channel by Nick [Reason] */
464         {
465           users[dichotomic(nick)].counters[D_KICKED]++;
466           for (i=16;message[i]!=' ';i++);
467           message=&message[i+4];
468           for (i=0;message[i]!=' ';i++);
469           message[i]='\0';
470           users[dichotomic(message)].counters[D_KICK]++;
471         }
472         else if (strncmp("is now known as",message,15)==0) /* 00:00 -!- Nick is now known as Nick */
473           users[dichotomic(nick)].counters[D_NICK]++;
474         else if (message[0]=='[') /* 00:00 -!- Nick [user@host] something... */
475         {
476           for (i=0;message[i]!=']';i++);
477           message=&message[i+2];
478           if (strncmp("has joined",message,10)==0) /* 00:00 -!- Nick [user@host] has joined #channel */
479             users[dichotomic(nick)].counters[D_JOIN]++;
480           else if (strncmp("has quit",message,8)==0); /* 00:00 -!- Nick [user@host] has quit [Reason] */
481           else if (strncmp("has left",message,8)==0); /* 00:00 -!- Nick [user@host] has left #channel [Reason] */
482           else;
483         }
484       }
485       else if ((line[6]=='<') || (line[7]=='*'))
486       {
487         line[2]='\0';
488         hour=atoi(line);
489         if (line[7]=='*') /* 00:00  * Nick the message */
490         {
491           for (i=9;line[i]!=' ';i++);
492           nick=&line[9];
493           message=&line[i+1];
494         }
495         else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
496         {
497           for (i=11;line[i]!='<';i++);
498           nick=&line[11];
499           message=&line[i+5];
500         }
501         else /* 00:00 <?Nick> the message */
502         {
503           for (i=8;line[i]!='>';i++);
504           nick=&line[8];
505           message=&line[i+2];
506         }
507         line[i]='\0';
508         i=dichotomic(nick);
509         if (line[7]=='*') users[i].counters[D_ME]++;
510         if (i==mononick)
511         {
512           monolines++;
513           if (monolines==5) users[i].counters[D_MONOLOGUE]++;
514         }
515         else
516         {
517           mononick=i;
518           monolines=1;
519         }
520         j=strlen(message);
521         users[i].lines++;
522         users[i].letters+=j;
523         users[i].hours[hour/6]++;
524         lastdays[0].lines++;
525         lastdays[0].hours[hour/6]++;
526         lines++;
527         hours[hour]++;
528         if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
529         else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
530         else if ((message[j-3]==' ')&&(message[j-2]==':'))
531         {
532           if (message[j-1]==')') users[i].counters[D_SMILE]++;
533           else if (message[j-1]=='(') users[i].counters[D_FROWN]++;
534         }
535         if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
536         if (strncmp("http://",message,7)==0)
537         {
538           users[i].counters[D_URL]++;
539           for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
540           message[i]='\0';
541           nburls++;
542           if ((nburls<=5) || (rand()%nburls==0))
543           {
544             for (i=4;i>0;i--)
545             {
546               strcpy(urls[i].nick,urls[i-1].nick);
547               strcpy(urls[i].url,urls[i-1].url);
548               strcpy(urls[i].shorturl,urls[i-1].shorturl);
549             }
550             strcpy(urls[0].nick,nick);
551             strcpy(urls[0].url,message);
552             strncpy(urls[0].shorturl,message,MAXQUOTELENGTH);
553           }
554         }
555         findwords(message);
556       }
557       pos=0;
558     }
559   }
560   fprintf(stderr,"done\n");
561
562   bestwords(words,0);
563
564   /*** HTML ***/
565
566   /* header */
567   printf("<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
568   printf("<html>\n\n<head>\n<base target=\"_blank\">\n<title>");
569   printf(L("HEADER"),channel,maintainer);
570   printf("</title>\n</head>\n\n");
571   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"));
572   printf(L("HEADER"),channel,maintainer);
573   printf("</h1></font>\n%s<br>\n<br><br>\n\n",ctime(&debut));
574
575   /* legend */
576   printf("<font color=\"%s\"><h3>%s</h3></font>\n<table bgcolor=%s>\n<tr>\n",T("TITLE2"),L("LEGEND"),T("BGTABLE"));
577   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);
578   printf("</tr>\n</table>\n<br><br>\n\n");
579   
580   /* last days */
581   printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("LASTDAYS"));
582   max=-1;
583   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
584   for (i=30;i>=0;i--)
585   {
586     printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%d</font><br>",lastdays[i].lines);
587     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);
588     printf("</td>\n");
589   }
590   printf("</tr>\n<tr>\n");
591   for (i=30;i>=0;i--)
592     printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
593   printf("</tr>\n</table>\n<br><br>\n\n");
594   
595   /* top hours */
596   printf("<font color=\"%s\"><h3>%s</h3></font>\n<table>\n<tr>\n",T("TITLE2"),L("TOPHOURS"));
597   max=-1;
598   for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
599   for (i=0;i<24;i++)
600   {
601     printf("<td width=\"15\" align=\"center\" valign=\"bottom\"><font size=\"-2\">%.1f%%</font><br>",lines!=0?(float)100*hours[i]/lines:0);
602     if (hours[i]!=0) printf("<img src=\"v%d.png\" width=\"15\" height=\"%d\"><br>",i/6+1,150*hours[i]/max);
603     printf("</td>\n");
604   }
605   printf("</tr>\n<tr>\n");
606   for (i=0;i<24;i++)
607     printf("<td align=\"center\" valign=\"center\" bgcolor=\"%s\">%d</td>\n",T("BGTABLE"),i);
608   printf("</tr>\n</table>\n<br><br>\n\n");
609   
610   /* top users */
611   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERS"));
612   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"));
613   for (i=1;i<=NBUSERS;i++)
614   {
615     user=-1;
616     max=-1;
617     for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
618     if (user!=-1)
619     {
620       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"));
621       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);
622       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].letters/users[user].lines,T("BGTABLE"),users[user].letters/users[user].lines,T("BGTABLE"));
623       printhtml(users[user].quote);
624       printf("\"</td></tr>\n");
625       users[user].lines=-1;
626     }    
627   }
628   printf("</table>\n");
629   if (nbusers>NBUSERS)
630   {
631     printf("<br>");
632     printf(L("OTHERS"),nbusers-50);
633     printf("<br>\n");
634   }
635   printf("<br><br>\n\n");
636   
637   /* top users by time */
638   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPUSERSTIME"));
639   printf("<table>\n<tr><td></td>");
640   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);
641   printf("</tr>\n");
642   for (i=1;i<=NBUSERSTIME;i++)
643   {
644     printf("<tr><td bgcolor=\"%s\" align=\"right\">%d</td>",T("BGTABLE"),i);
645     for (j=0;j<4;j++)
646     {
647       user=-1;
648       max=-1;
649       for (k=0;k<nbusers;k++) if (users[k].hours[j]>max) max=users[user=k].hours[j];
650       if (user!=-1)
651       {
652         printf("<td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%d</td>",T("BGTABLE"),users[user].nick,T("BGTABLE"),users[user].hours[j]);
653         users[user].hours[j]=-1;
654       }
655       else printf("<td></td><td></td>");
656     }
657     printf("</tr>\n");
658   }
659   printf("</table>\n<br><br>\n\n");
660
661   /* random topics */
662   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDTOPICS"));
663   printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("CHANGEDBY"),T("BGTITLE"),L("NEWTOPIC"));
664   for (i=nbtopics<NBTOPICS?nbtopics-1:NBTOPICS-1;i>=0;i--)
665   {
666     printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"",T("BGTABLE"),topics[i].nick,T("BGTABLE"));
667     printhtml(topics[i].topic);
668     printf("\"</td></tr>\n");
669   }
670   printf("</table>\n<br><br>\n\n");
671   
672   /* random urls */
673   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("RANDURLS"));
674   printf("<table>\n<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">%s</td></tr>\n",T("BGTITLE"),L("POSTEDBY"),T("BGTITLE"),L("POSTEDURL"));
675   for (i=nburls<NBURLS?nburls-1:NBURLS-1;i>=0;i--)
676   {
677     printf("<tr><td bgcolor=\"%s\">%s</td><td bgcolor=\"%s\">\"<a href=\"%s\">",T("BGTABLE"),urls[i].nick,T("BGTABLE"),urls[i].url);
678     printhtml(urls[i].shorturl);
679     printf("</a>\"</td></tr>\n");
680   }
681   printf("</table>\n<br><br>\n\n");
682   
683   /* top words */
684   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("TOPWORDS"));
685   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"));
686   for (i=0;i<NBWORDS;i++)
687     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);
688   printf("</table>\n<br><br>\n\n");
689
690   /* big numbers */
691   printf("<font color=\"%s\"><h3>%s</h3></font>\n",T("TITLE2"),L("BIGNUMBERS"));
692   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"));
693   for (i=0;i<NBCOUNTERS;i++)
694   {
695     user=-1;
696     max=0;
697     for (j=0;j<nbusers;j++) if (users[j].counters[i]>max) max=users[user=j].counters[i];
698     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]);
699   }
700   printf("</table>\n<br><br>\n\n");
701   
702   /* footer */
703   printf(L("TIME"),totallines,(int)(time(NULL)-debut));
704   printf("<br>\n%s <a href=\"%s\">irssistats %s</a>",L("FOOTER"),URL,VERSION);
705   printf("\n\n</center>\n\n</body>\n\n</html>\n");
706   
707   return(0);
708 }