1 /* Usage: irssistats [/path/to/file.conf] */
11 #define MAXUSERS 10000
12 #define MAXNICKLENGTH 50
13 #define MAXLINELENGTH 2000
14 #define MAXQUOTELENGTH 100
16 #define NBUSERSTIME 10
20 #define MINWORDLENGTH 5
23 #define VERSION "0.51"
24 #define URL "http://royale.zerezo.com/irssistats/"
39 #define D_MONOLOGUE 12
41 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"};
46 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
48 { /* English language */
50 { "CHARSET", "ISO-8859-1" },
51 { "HEADER", "Statistics for %s by %s" },
52 { "LEGEND", "Legend" },
53 { "LASTDAYS", "Lastdays statistics" },
54 { "TOPHOURS", "Hourly statistics" },
55 { "TOPUSERS", "Most active people" },
56 { "OTHERS", "There are %d left not ranked..." },
57 { "NBLINES", "lines" },
59 { "AVGLETTERS", "letters/lines" },
61 { "QUOTE", "random message" },
62 { "TOPUSERSTIME", "Most active people by time of day" },
63 { "RANDTOPICS", "Some topics" },
64 { "CHANGEDBY", "changed by" },
65 { "NEWTOPIC", "new topic" },
66 { "RANDURLS", "Some URLs" },
67 { "POSTEDBY", "posted by" },
68 { "POSTEDURL", "URL" },
69 { "TOPWORDS", "Most used words" },
71 { "OCCURRENCES", "occurrences" },
72 { "BIGNUMBERS", "Some big numbers..." },
73 { "NUMBERS", "numbers" },
74 { "TIME", "%d lines (%d days) parsed in %d seconds" },
75 { "FOOTER", "Statistics generated by" },
76 { "C_SMILE", "is often happy :)" },
77 { "C_FROWN", "is often sad :(" },
78 { "C_EXCLAM", "yells a lot !" },
79 { "C_QUESTION", "asks a lot of questions ?" },
80 { "C_ME", "likes /me command" },
81 { "C_TOPIC", "often changes the topic" },
82 { "C_MODE", "often changes the modes" },
83 { "C_KICK", "likes to /kick" },
84 { "C_KICKED", "is often kicked" },
85 { "C_URL", "posts many URLs" },
86 { "C_JOIN", "doesn't know wether to stay or quit" },
87 { "C_NICK", "often changes his nick" },
88 { "C_MONOLOGUE", "speaks a lot of monologues" }
90 { /* French language */
92 { "CHARSET", "ISO-8859-1" },
93 { "HEADER", "Statistiques de %s par %s" },
94 { "LEGEND", "Légende" },
95 { "LASTDAYS", "Statistiques des derniers jours" },
96 { "TOPHOURS", "Statistiques horaires" },
97 { "TOPUSERS", "Personnes les plus actives" },
98 { "OTHERS", "Il reste %d personnes non classées..." },
99 { "NBLINES", "lignes" },
101 { "AVGLETTERS", "lettres/lignes" },
102 { "HOURS", "heures" },
103 { "QUOTE", "message aléatoire" },
104 { "TOPUSERSTIME", "Personnes les plus actives par période de la journée" },
105 { "RANDTOPICS", "Quelques topics" },
106 { "CHANGEDBY", "changé par" },
107 { "NEWTOPIC", "nouveau topic" },
108 { "RANDURLS", "Quelques URLs" },
109 { "POSTEDBY", "postée par" },
110 { "POSTEDURL", "URL" },
111 { "TOPWORDS", "Mots les plus utilisés" },
113 { "OCCURRENCES", "occurrences" },
114 { "BIGNUMBERS", "Quelques grands nombres..." },
115 { "NUMBERS", "nombres" },
116 { "TIME", "%d lignes (%d jours) traitées en %d secondes" },
117 { "FOOTER", "Statistiques générées par" },
118 { "C_SMILE", "est souvent heureux :)" },
119 { "C_FROWN", "est souvent triste :(" },
120 { "C_EXCLAM", "hurle beaucoup !" },
121 { "C_QUESTION", "pose beaucoup de questions ?" },
122 { "C_ME", "aime la commande /me" },
123 { "C_TOPIC", "change souvent le topic" },
124 { "C_MODE", "change souvent les modes" },
125 { "C_KICK", "aime la commande /kick" },
126 { "C_KICKED", "est souvent kické" },
127 { "C_URL", "poste beaucoup d'URLs" },
128 { "C_JOIN", "ne sait pas s'il doit rester ou partir" },
129 { "C_NICK", "change souvent de nick" },
130 { "C_MONOLOGUE", "parle beaucoup de monologues" }
132 { /* German language */
133 /* contributed by Valentin Gelhorn <valentin.gelhorn@web.de> */
135 { "CHARSET", "ISO-8859-1" },
136 { "HEADER", "Statistiken für %s von %s" },
137 { "LEGEND", "Legende" },
138 { "LASTDAYS", "Statistik der letzten Tage" },
139 { "TOPHOURS", "Stündliche Statistik" },
140 { "TOPUSERS", "Die aktivsten Personen" },
141 { "OTHERS", "Es bleiben noch %d uneingetragene" },
142 { "NBLINES", "Zeilen" },
144 { "AVGLETTERS", "Buchstaben/Zeile" },
145 { "HOURS", "Stunden" },
146 { "QUOTE", "Zufällig ausgewaehlte Zitate" },
147 { "TOPUSERSTIME", "Die aktivsten Personen zur bestimmten Tageszeit" },
148 { "RANDTOPICS", "Ein paar Topics" },
149 { "CHANGEDBY", "Gesetzt von" },
150 { "NEWTOPIC", "Neues topic" },
151 { "RANDURLS", "Ein paar URLs" },
152 { "POSTEDBY", "Geschrieben von" },
153 { "POSTEDURL", "URL" },
154 { "TOPWORDS", "Am häufigsten benutze Wörter" },
156 { "OCCURRENCES", "Vorkommen" },
157 { "BIGNUMBERS", "Ein paar grosse Zahlen" },
158 { "NUMBERS", "Zahlen" },
159 { "TIME", "%d Zeilen (%d Tage) analysiert in %d Sekunden" },
160 { "FOOTER", "Statistiken wurden erstellt von" },
161 { "C_SMILE", "ist oft glüklich :)" },
162 { "C_FROWN", "ist oft traurig :(" },
163 { "C_EXCLAM", "schreit oft !" },
164 { "C_QUESTION", "stellt viele Fragen ?" },
165 { "C_ME", "mag /me'en" },
166 { "C_TOPIC", "aendert oft das Topico" },
167 { "C_MODE", "aendert oft die Modes" },
168 { "C_KICK", "mag /kick'en" },
169 { "C_KICKED", "wird oft gekickt"},
170 { "C_URL", "schreibt viele URLs"},
171 { "C_JOIN", "kann sich nicht entscheiden ob er bleiben oder gehen soll" },
172 { "C_NICK", "ändert oft seinen Nick" },
173 { "C_MONOLOGUE", "spricht oft Monologe" }
175 { /* Spanish language */
176 /* contributed by Alex <ainaker@gmx.net> */
178 { "CHARSET", "ISO-8859-1" },
179 { "HEADER", "Estadísticas de %s por %s" },
180 { "LEGEND", "Leyenda" },
181 { "LASTDAYS", "Estadísticas de los últimos días" },
182 { "TOPHOURS", "Estadísticas por horas" },
183 { "TOPUSERS", "Los que más escriben" },
184 { "OTHERS", "Hay %d más que no llegaron..." },
185 { "NBLINES", "líneas" },
187 { "AVGLETTERS", "letras por línea" },
188 { "HOURS", "horas" },
189 { "QUOTE", "Frase aleatoria" },
190 { "TOPUSERSTIME", "Los que más escriben según la hora" },
191 { "RANDTOPICS", "Algunos topics" },
192 { "CHANGEDBY", "Puestos por" },
193 { "NEWTOPIC", "topic" },
194 { "RANDURLS", "Algunas URLs" },
195 { "POSTEDBY", "puestas por" },
196 { "POSTEDURL", "URL" },
197 { "TOPWORDS", "Palabras más usadas" },
198 { "WORD", "Palabra" },
199 { "OCCURRENCES", "Frecuencia" },
200 { "BIGNUMBERS", "Algunos datos..." },
201 { "NUMBERS", "Número de veces" },
202 { "TIME", "%d lineas (%d días) procesadas en %d segundos" },
203 { "FOOTER", "Estadísticas generadas por" },
204 { "C_SMILE", "Suele estar felíz :)" },
205 { "C_FROWN", "Suele estar triste :(" },
206 { "C_EXCLAM", "Grita mucho !" },
207 { "C_QUESTION", "Hace muchas preguntas ?" },
208 { "C_ME", "Abusa del comando /me" },
209 { "C_TOPIC", "Suele cambiar el topic" },
210 { "C_MODE", "Cambia a veces los modos del canal" },
211 { "C_KICK", "Le gusta patear" },
212 { "C_KICKED", "Es pateado con frecuencia" },
213 { "C_URL", "Pone muchas URLs" },
214 { "C_JOIN", "No sabe si irse o quedarse" },
215 { "C_NICK", "Cambia mucho de nick" },
216 { "C_MONOLOGUE", "Habla solo" }
218 { /* Polish language */
219 /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
220 /* updated by Jakub Jankowski <shasta@atn.pl> */
222 { "CHARSET", "ISO-8859-2" },
223 { "HEADER", "Statystyki dla %s zebrane przez %s" },
224 { "LEGEND", "Legenda" },
225 { "LASTDAYS", "Statystyki z ostatnich dni" },
226 { "TOPHOURS", "Statystyki godzinowe" },
227 { "TOPUSERS", "Najaktywniejsi" },
228 { "OTHERS", "Jest jeszcze %d nie sklasyfikowanych..." },
229 { "NBLINES", "linii" },
231 { "AVGLETTERS", "liter/liniê" },
232 { "HOURS", "godziny" },
233 { "QUOTE", "losowa wypowied¼" },
234 { "TOPUSERSTIME", "Najaktywniejsi wed³ug pory dnia" },
235 { "RANDTOPICS", "Kilka topików" },
236 { "CHANGEDBY", "ustawiony przez" },
237 { "NEWTOPIC", "topik" },
238 { "RANDURLS", "Kilka URL-i" },
239 { "POSTEDBY", "poda³(a)" },
240 { "POSTEDURL", "URL" },
241 { "TOPWORDS", "Najczê¶ciej wystêpuj±ce s³owa" },
243 { "OCCURRENCES", "wyst±pieñ" },
244 { "BIGNUMBERS", "Kilka wielkopomnych liczb..." },
245 { "NUMBERS", "kategorie" },
246 { "TIME", "Dokonano analizy %d linii (obejmuj±cych %d dni) w czasie %d sekund" },
247 { "FOOTER", "Statystyki wygenerowane przez" },
248 { "C_SMILE", "jest czêsto szczesliwy(a) :)" },
249 { "C_FROWN", "jest czêsto smutny(a) :(" },
250 { "C_EXCLAM", "czêsto KRZYCZY!" },
251 { "C_QUESTION", "zadaje du¿o pytañ?" },
252 { "C_ME", "lubi u¿ywaæ /me" },
253 { "C_TOPIC", "czêsto zmienia topik" },
254 { "C_MODE", "czêsto zmienia tryby kana³u" },
255 { "C_KICK", "lubi kopaæ" },
256 { "C_KICKED", "czêsto wykopywany(a)" },
257 { "C_URL", "podaje du¿o URL-i" },
258 { "C_JOIN", "nie wie czy zostaæ, czy wyj¶æ" },
259 { "C_NICK", "czêsto zmienia nick" },
260 { "C_MONOLOGUE", "uwielbia monologi" }
262 { /* Finnish language */
263 /* contributed by Antti Huopana <ahuopana@ratol.fi> */
265 { "CHARSET", "ISO-8859-1" },
266 { "HEADER", "Kanavan %s tilastot - %s" },
267 { "LEGEND", "Merkkien selitykset" },
268 { "LASTDAYS", "Viime päivien tilastot" },
269 { "TOPHOURS", "Tilastot tunneittain" },
270 { "TOPUSERS", "Aktiivisimmat ihmiset" },
271 { "OTHERS", "Jäljelle jäi %d joita ei listattu..." },
272 { "NBLINES", "rivit" },
274 { "AVGLETTERS", "kirjainta/rivi" },
275 { "HOURS", "tunnit" },
276 { "QUOTE", "satunnainen viesti" },
277 { "TOPUSERSTIME", "Vuorokauden ajan mukaan aktiivisimmat" },
278 { "RANDTOPICS", "Joitakin aiheita" },
279 { "CHANGEDBY", "vaihtaja" },
280 { "NEWTOPIC", "aihe" },
281 { "RANDURLS", "Joitakin URLeja" },
282 { "POSTEDBY", "lähettäjä" },
283 { "POSTEDURL", "URL" },
284 { "TOPWORDS", "Eniten käytettyjä sanoja" },
286 { "OCCURRENCES", "käytetty" },
287 { "BIGNUMBERS", "Joitakin isoja lukuja..." },
288 { "NUMBERS", "luvut" },
289 { "TIME", "%d riviä (%d päivää) parsittu %d sekunnissa" },
290 { "FOOTER", "Tilastot on generoinut" },
291 { "C_SMILE", "on usein iloinen :)" },
292 { "C_FROWN", "on usein surullinen :(" },
293 { "C_EXCLAM", "möykkää paljon !" },
294 { "C_QUESTION", "kyselee liikaa ?" },
295 { "C_ME", "pitää itsestään" },
296 { "C_TOPIC", "vaihtaa usein aihetta" },
297 { "C_MODE", "haluaa elää muuttuvassa maailmassa" },
298 { "C_KICK", "pitää potkimisesta" },
299 { "C_KICKED", "tykkää tulla potkituksi" },
300 { "C_URL", "surffailee liikaa" },
301 { "C_JOIN", "ei tiedä ollakko vai eikö olla" },
302 { "C_NICK", "kärsii identiteettiongelmista" },
303 { "C_MONOLOGUE", "höpöttää paljon itsekseen" }
305 { /* Italian language */
306 /* contributed by Coviello Giuseppe <giuseppecoviello@tin.it> <http://coviello.altervista.org> */
308 { "CHARSET", "ISO-8859-1" },
309 { "HEADER", "Statistiche per il canale %s di %s" },
310 { "LEGEND", "Legenda" },
311 { "LASTDAYS", "Statistiche degli ultimi giorni" },
312 { "TOPHOURS", "Statistiche in ore" },
313 { "TOPUSERS", "Utenti più attivi" },
314 { "OTHERS", "Ci sono %d utenti non classificati..." },
315 { "NBLINES", "righe" },
317 { "AVGLETTERS", "lettere/righe" },
319 { "QUOTE", "messaggio casuale" },
320 { "TOPUSERSTIME", "Utenti più attivi del giorno (divisi per fasce orarie)" },
321 { "RANDTOPICS", "Alcuni topic" },
322 { "CHANGEDBY", "cambiato da" },
323 { "NEWTOPIC", "nuovo topic" },
324 { "RANDURLS", "ALcuni URL" },
325 { "POSTEDBY", "postato da" },
326 { "POSTEDURL", "URL" },
327 { "TOPWORDS", "Le parole più usate" },
328 { "WORD", "parola" },
329 { "OCCURRENCES", "usata" },
330 { "BIGNUMBERS", "Alcuni numeri ..." },
331 { "NUMBERS", "numeri" },
332 { "TIME", "%d righe (%d giorni) esaminate in %d secondi" },
333 { "FOOTER", "Statistiche generate da" },
334 { "C_SMILE", "è spesso felice :)" },
335 { "C_FROWN", "è spesso triste :(" },
336 { "C_EXCLAM", "esclama molto !" },
337 { "C_QUESTION", "fa molte domande ?" },
338 { "C_ME", "ama il comando /me" },
339 { "C_TOPIC", "cambia spesso il topic" },
340 { "C_MODE", "cambia spesso i mode" },
341 { "C_KICK", "ama /kick(are)" },
342 { "C_KICKED", "è kickato spesso" },
343 { "C_URL", "posta molti URL" },
344 { "C_JOIN", "non sa se è ora di andare o restare" },
345 { "C_NICK", "cambia spesso il nick" },
346 { "C_MONOLOGUE", "fa molti monologhi" }
348 { /* Dutch language */
349 /* contributed by Jeroen Ubbink <crasp@blackbyte.nl> */
351 { "CHARSET", "ISO-8859-1" },
352 { "HEADER", "Statistieken voor %s door %s" },
353 { "LEGEND", "Legenda" },
354 { "LASTDAYS", "Statistieken van de laatste dagen" },
355 { "TOPHOURS", "Statistieken per uur" },
356 { "TOPUSERS", "Meest actieve mensen" },
357 { "OTHERS", "Er zijn nog %d niet in de top..." },
358 { "NBLINES", "regels" },
360 { "AVGLETTERS", "letters/lijn" },
362 { "QUOTE", "Willekeurige regel" },
363 { "TOPUSERSTIME", "Meest actieve mensen per tijdstip per dag" },
364 { "RANDTOPICS", "Enkele topics" },
365 { "CHANGEDBY", "gewijzigd door" },
366 { "NEWTOPIC", "nieuwe topic" },
367 { "RANDURLS", "Enkele URLs" },
368 { "POSTEDBY", "Geplaatst door" },
369 { "POSTEDURL", "URL" },
370 { "TOPWORDS", "Meest gebruikte woorden" },
372 { "OCCURRENCES", "aantal" },
373 { "BIGNUMBERS", "Enkele grote aantallen..." },
374 { "NUMBERS", "numbers" },
375 { "TIME", "%d regels (%d dagen) verwerkt in %d seconden" },
376 { "FOOTER", "Statistieken gegenereert door" },
377 { "C_SMILE", "is vaak vrolijk :)" },
378 { "C_FROWN", "is vaak droevig :(" },
379 { "C_EXCLAM", "schreeuwt veel !" },
380 { "C_QUESTION", "stelt veel vragen ?" },
381 { "C_ME", "vindt /me een leuk commando" },
382 { "C_TOPIC", "verandert vaak de topic" },
383 { "C_MODE", "verandert vaak de modes" },
384 { "C_KICK", "vindt /kick erg leuk" },
385 { "C_KICKED", "wordt vaak gekickt" },
386 { "C_URL", "plaatst veel URLs" },
387 { "C_JOIN", "twijfelt tussen blijven of gaan" },
388 { "C_NICK", "verandert vaak van nick" },
389 { "C_MONOLOGUE", "spreekt veel monologen" }
393 int language=0; /* default to english */
398 for (i=1;i<=NBKEYS;i++) if (strcmp(key,keys[language][i][0])==0) return(keys[language][i][1]);
399 fprintf(stderr,"unknown language key: %s\n",key);
405 int debug=1; /* 0 = none ; 1 = normal ; 2 = verbose */
406 char channel[MAXLINELENGTH]="set_channel_in_config_file";
407 char maintainer[MAXLINELENGTH]="set_maintainer_in_config_file";
408 char theme[MAXLINELENGTH]="default";
409 int refresh_time=0; /* 0 = disabled */
410 int w3c_link=1; /* 0 = disabled */
411 char header[MAXLINELENGTH]="none";
412 char footer[MAXLINELENGTH]="none";
418 char nick[MAXNICKLENGTH];
422 char quote[MAXQUOTELENGTH+1];
423 int counters[NBCOUNTERS];
430 char nick[MAXNICKLENGTH];
431 char url[MAXLINELENGTH];
432 char shorturl[MAXQUOTELENGTH+1];
438 char nick[MAXNICKLENGTH];
439 char topic[MAXQUOTELENGTH+1];
456 struct letter *next[26];
462 char word[MAXLINELENGTH];
465 #define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
466 #define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
467 void findwords(char *message)
470 struct letter *pos,*tmp;
473 while (!isletter(*message)) if (*message=='\0') return; else message++;
475 while (isletter(*message))
477 c=lowercase(*message)-'a';
478 if (pos->next[(int)c]==NULL)
480 tmp=malloc(sizeof(struct letter));
483 fprintf(stderr, "findwords(): malloc failure\n");
487 for (i=0;i<26;i++) tmp->next[i]=NULL;
488 pos->next[(int)c]=tmp;
490 pos=pos->next[(int)c];
498 char tempword[MAXLINELENGTH];
499 void bestwords(struct letter pos,int cur)
502 if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
504 for (i=0;pos.nb<topwords[i].nb;i++);
505 for (j=NBWORDS-1;j>i;j--)
507 topwords[j].nb=topwords[j-1].nb;
508 strcpy(topwords[j].word,topwords[j-1].word);
510 topwords[i].nb=pos.nb;
511 strcpy(topwords[i].word,tempword);
513 for (i=0;i<26;i++) if (pos.next[i]!=NULL)
516 bestwords(*(pos.next[i]),cur+1);
521 void freewords(struct letter *pos)
524 for (i=0;i<26;i++) if (pos->next[i]!=NULL)
526 freewords(pos->next[i]);
532 void printhtml(FILE *fic,char *string) /* replace < and > by < and > */
534 while (*string!='\0')
538 case '<':fprintf(fic,"<"); break;
539 case '>':fprintf(fic,">"); break;
540 case '&':fprintf(fic,"&"); break;
541 default:fprintf(fic,"%c",*string); break;
548 int dichotomic(char *nick)
550 int i,j,start=0,end=nbusers-1,middle;
553 middle=(start+end)/2;
554 if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
556 if (strcmp(nick,users[start].nick)!=0)
559 if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
560 for (i=nbusers-1;i>start;i--)
562 strcpy(users[i].nick,users[i-1].nick);
563 users[i].lines=users[i-1].lines;
564 users[i].letters=users[i-1].letters;
565 for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
566 strcpy(users[i].quote,users[i-1].quote);
567 for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=users[i-1].counters[j];
568 users[i].temp=users[i-1].temp;
570 strcpy(users[start].nick,nick);
571 users[start].lines=0;
572 users[start].letters=0;
573 for (j=0;j<4;j++) users[start].hours[j]=0;
574 users[start].quote[0]='\0';
575 for (j=0;j<NBCOUNTERS;j++) users[start].counters[j]=0;
581 void parse_log(char *logfile)
584 char line[MAXLINELENGTH];
589 int mononick,monolines;
592 if ((fic=fopen(logfile,"rt"))==NULL) { fprintf(stderr,"can't open log file \"%s\"\n",logfile); exit(1); }
593 if (debug) printf("working on %s : ",channel);
594 while (fgets(line,MAXLINELENGTH,fic)!=NULL)
597 for (i=0;line[i]!=0;i++);
598 if (i>=MAXLINELENGTH-1) { fprintf(stderr,"line %d is too long\n",totallines); exit(1); }
602 if (totallines%10000==0 && debug) { printf("."); fflush(stdout); }
603 if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
607 lastdays[i].lines=lastdays[i-1].lines;
608 for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
611 for (j=0;j<4;j++) lastdays[0].hours[j]=0;
614 else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
616 for (i=strlen(line);line[i]!=' ';i--);
618 users[dichotomic(nick)].counters[D_MODE]++;
620 else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
622 for (i=10;line[i]!=' ';i++);
626 if (strncmp("changed the topic of",message,20)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
628 users[dichotomic(nick)].counters[D_TOPIC]++;
629 for (i=21;message[i]!=':';i++);
630 message=&message[i+2];
632 if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
634 temp=nbtopics<=NBTOPICS?nbtopics-1:rand()%NBTOPICS;
635 strcpy(topics[temp].nick,nick);
636 strncpy(topics[temp].topic,message,MAXQUOTELENGTH);
639 else if (strncmp("was kicked from",message,15)==0) /* 00:00 -!- Nick was kicked from #channel by Nick [Reason] */
641 users[dichotomic(nick)].counters[D_KICKED]++;
642 for (i=16;message[i]!=' ';i++);
643 message=&message[i+4];
644 for (i=0;message[i]!=' ';i++);
646 users[dichotomic(message)].counters[D_KICK]++;
648 else if (strncmp("is now known as",message,15)==0) /* 00:00 -!- Nick is now known as Nick */
649 users[dichotomic(nick)].counters[D_NICK]++;
650 else if (message[0]=='[') /* 00:00 -!- Nick [user@host] something... */
652 for (i=0;message[i]!=']';i++);
653 message=&message[i+2];
654 if (strncmp("has joined",message,10)==0) /* 00:00 -!- Nick [user@host] has joined #channel */
655 users[dichotomic(nick)].counters[D_JOIN]++;
656 else if (strncmp("has quit",message,8)==0); /* 00:00 -!- Nick [user@host] has quit [Reason] */
657 else if (strncmp("has left",message,8)==0); /* 00:00 -!- Nick [user@host] has left #channel [Reason] */
661 else if ((line[6]=='<') || (line[7]=='*'))
665 if (line[7]=='*') /* 00:00 * Nick the message */
667 for (i=9;line[i]!=' ';i++);
671 else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
672 /* 00:00 <>>?Nick<<> the personal message */
674 for (i=10;line[i]!='<';i++);
676 if (line[9]=='>') nick++;
679 else /* 00:00 <?Nick> the message */
683 * Irssi doesn't log channel mode with show_nickmode = OFF
684 * the following covers op, half-op, voice and show_nickmode_empty
686 if (line[7]=='@' || line[7]=='%' || line[7]=='+' || line[7]==' ') {
692 for (i=nickstart;line[i]!='>';i++);
693 nick=&line[nickstart];
698 if (line[7]=='*') users[i].counters[D_ME]++;
702 if (monolines==5) users[i].counters[D_MONOLOGUE]++;
712 users[i].hours[hour/6]++;
714 lastdays[0].hours[hour/6]++;
717 if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
718 else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
719 else if ((message[j-3]==' ')&&(message[j-2]==':'))
721 if (message[j-1]==')') users[i].counters[D_SMILE]++;
722 else if (message[j-1]=='(') users[i].counters[D_FROWN]++;
724 if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
725 if (strncmp("http://",message,7)==0)
727 users[i].counters[D_URL]++;
728 for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
731 if ((nburls<=NBURLS) || (rand()%(nburls/NBURLS)==0))
733 temp=nburls<=NBURLS?nburls-1:rand()%NBURLS;
734 strcpy(urls[temp].nick,nick);
735 strcpy(urls[temp].url,message);
736 strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
744 if (debug) printf(" done\n");
747 void parse_nick(char *nickfile)
752 char line[MAXLINELENGTH];
755 for (i=0;i<nbusers;i++) users[i].temp=users[i].lines;
756 if ((fic=fopen(nickfile,"rt"))==NULL) { fprintf(stderr,"can't open nick file \"%s\"\n",nickfile); exit(1); }
757 while (fscanf(fic,"%s",line)==1)
759 user=dichotomic(line);
760 fscanf(fic,"%s",line);
761 if (regcomp(&preg,line,0)!=0) { fprintf(stderr,"error in nick file"); exit(1); }
762 for (i=0;i<nbusers;i++) if ((i!=user) && (regexec(&preg,users[i].nick,0,0,0)==0) && (users[i].lines>=0))
764 if (users[i].temp>users[user].temp) /* for nick alias, keep the random quote of the most used nick */
766 strcpy(users[user].quote,users[i].quote);
767 users[user].temp=users[i].temp;
769 users[user].lines+=users[i].lines;
770 users[user].letters+=users[i].letters;
771 for (j=0;j<4;j++) users[user].hours[j]+=users[i].hours[j];
772 for (j=0;j<NBCOUNTERS;j++) users[user].counters[j]+=users[i].counters[j];
773 /* "remove" old user */
776 for (j=0;j<4;j++) users[i].hours[j]=-1;
777 for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
782 /* "remove" the ignored nicks */
783 i=dichotomic("<NULL>");
786 for (j=0;j<4;j++) users[i].hours[j]=-1;
787 for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
790 void gen_xhtml(char *xhtmlfile)
796 char line[MAXLINELENGTH];
798 if ((fic=fopen(xhtmlfile,"wt"))==NULL) { fprintf(stderr,"can't open xhtml file \"%s\"\n",xhtmlfile); exit(1); }
801 if (strcmp("none",header)==0)
803 fprintf(fic,"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n");
804 fprintf(fic,"<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
805 fprintf(fic,"<html>\n\n<head>\n<title>");
806 fprintf(fic,L("HEADER"),channel,maintainer);
807 fprintf(fic,"</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n",L("CHARSET"));
809 fprintf(fic,"<meta http-equiv=\"Refresh\" content=\"%d\" />\n",refresh_time);
810 fprintf(fic,"<link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\" />\n",theme);
811 fprintf(fic,"</head>\n\n");
812 fprintf(fic,"<body>\n\n");
816 if ((sfic=fopen(header,"rt"))==NULL) { fprintf(stderr,"can't open header file \"%s\"\n",header); exit(1); }
817 while ((temp=fread(line,1,MAXLINELENGTH,sfic))) fwrite(line,temp,1,fic);
820 fprintf(fic,"<div id=\"irssistats\">\n\n<div id=\"irssistats_header\">\n<h1>");
821 fprintf(fic,L("HEADER"),channel,maintainer);
822 fprintf(fic,"</h1>\n<p>\n%s</p>\n</div>\n\n",ctime(&debut));
825 fprintf(fic,"<div id=\"irssistats_legend\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LEGEND"));
826 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);
827 fprintf(fic,"</tr>\n</table>\n</div>\n\n");
830 fprintf(fic,"<div id=\"irssistats_lastdays\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LASTDAYS"));
832 for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
835 fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%d</small>",lastdays[i].lines);
836 for (j=0;j<4;j++) if (lastdays[i].hours[j]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",j+1,150*lastdays[i].hours[j]/max);
837 fprintf(fic,"</td>\n");
839 fprintf(fic,"</tr>\n<tr>\n");
841 fprintf(fic,"<th>%d</th>\n",i);
842 fprintf(fic,"</tr>\n</table>\n</div>\n\n");
845 fprintf(fic,"<div id=\"irssistats_tophours\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("TOPHOURS"));
847 for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
850 fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%.1f%%</small>",lines!=0?(float)100*hours[i]/lines:0);
851 if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",i/6+1,150*hours[i]/max);
852 fprintf(fic,"</td>\n");
854 fprintf(fic,"</tr>\n<tr>\n");
856 fprintf(fic,"<th>%d</th>\n",i);
857 fprintf(fic,"</tr>\n</table>\n</div>\n\n");
860 fprintf(fic,"<div id=\"irssistats_topusers\">\n<h2>%s</h2>\n",L("TOPUSERS"));
861 fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th><th>%s</th><th colspan=\"2\">%s</th><th>%s</th></tr>\n",L("NICK"),L("NBLINES"),L("HOURS"),L("AVGLETTERS"),L("QUOTE"));
862 for (i=1;i<=NBUSERS;i++)
866 for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
869 fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].lines);
870 for (j=0;j<4;j++) if (users[user].hours[j]!=0) fprintf(fic,"<div class=\"h%d\" style=\"width:%dpx\"></div>",j+1,100*users[user].hours[j]/users[user].lines);
871 fprintf(fic,"</td><td>%d</td><td><div class=\"hm\" style=\"width:%dpx\"></div></td><td>\"",users[user].lines!=0?users[user].letters/users[user].lines:0,users[user].lines!=0?users[user].letters/users[user].lines:0);
872 printhtml(fic,users[user].quote);
873 fprintf(fic,"\"</td></tr>\n");
874 users[user].lines=-1;
877 fprintf(fic,"</table>\n");
879 for (i=0;i<=nbusers;i++) if (users[i].lines>=0) temp++;
883 fprintf(fic,L("OTHERS"),temp);
884 fprintf(fic,"</p>\n");
886 fprintf(fic,"</div>\n\n");
888 /* top users by time */
889 fprintf(fic,"<div id=\"irssistats_topuserstime\">\n<h2>%s</h2>\n",L("TOPUSERSTIME"));
890 fprintf(fic,"<table>\n<tr><th></th>");
891 for (i=0;i<4;i++) fprintf(fic,"<th colspan=\"2\">%s %d-%d</th>",L("HOURS"),i*6,i*6+5);
892 fprintf(fic,"</tr>\n");
893 for (i=1;i<=NBUSERSTIME;i++)
895 fprintf(fic,"<tr><td>%d</td>",i);
900 for (k=0;k<nbusers;k++) if (users[k].hours[j]>max) max=users[user=k].hours[j];
903 fprintf(fic,"<td>%s</td><td>%d</td>",users[user].nick,users[user].hours[j]);
904 users[user].hours[j]=-1;
906 else fprintf(fic,"<td></td><td></td>");
908 fprintf(fic,"</tr>\n");
910 fprintf(fic,"</table>\n</div>\n\n");
913 fprintf(fic,"<div id=\"irssistats_randtopics\">\n<h2>%s</h2>\n",L("RANDTOPICS"));
914 fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th></tr>\n",L("CHANGEDBY"),L("NEWTOPIC"));
915 for (i=nbtopics<NBTOPICS?nbtopics-1:NBTOPICS-1;i>=0;i--)
917 fprintf(fic,"<tr><td>%s</td><td>\"",topics[i].nick);
918 printhtml(fic,topics[i].topic);
919 fprintf(fic,"\"</td></tr>\n");
921 fprintf(fic,"</table>\n</div>\n\n");
924 fprintf(fic,"<div id=\"irssistats_randurls\">\n<h2>%s</h2>\n",L("RANDURLS"));
925 fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th></tr>\n",L("POSTEDBY"),L("POSTEDURL"));
926 for (i=nburls<NBURLS?nburls-1:NBURLS-1;i>=0;i--)
928 fprintf(fic,"<tr><td>%s</td><td>\"<a href=\"",urls[i].nick);
929 printhtml(fic,urls[i].url);
931 printhtml(fic,urls[i].shorturl);
932 fprintf(fic,"</a>\"</td></tr>\n");
934 fprintf(fic,"</table>\n</div>\n\n");
937 fprintf(fic,"<div id=\"irssistats_topwords\">\n<h2>%s</h2>\n",L("TOPWORDS"));
938 fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th></tr>\n",L("WORD"),L("OCCURRENCES"));
939 for (i=0;i<NBWORDS;i++)
940 if (topwords[i].nb!=0) fprintf(fic,"<tr><td>%d</td><td>\"%s\"</td><td>%d</td></tr>\n",i+1,topwords[i].word,topwords[i].nb);
941 fprintf(fic,"</table>\n</div>\n\n");
944 fprintf(fic,"<div id=\"irssistats_bignumbers\">\n<h2>%s</h2>\n",L("BIGNUMBERS"));
945 fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th><th>%s</th></tr>\n",L("NICK"),L("NUMBERS"),L("NBLINES"));
946 for (i=0;i<NBCOUNTERS;i++)
950 for (j=0;j<nbusers;j++) if (users[j].counters[i]>max) max=users[user=j].counters[i];
951 if (user!=-1) fprintf(fic,"<tr><td>%s</td><td>%s</td><td>%d</td></tr>",users[user].nick,L(counters[i]),users[user].counters[i]);
953 fprintf(fic,"</table>\n</div>\n\n");
956 fprintf(fic,"<div id=\"irssistats_footer\">\n<p>");
957 fprintf(fic,L("TIME"),totallines,days,(int)(time(NULL)-debut));
958 fprintf(fic,"</p>\n<p>%s <a href=\"%s\">irssistats %s</a></p>\n",L("FOOTER"),URL,VERSION);
961 fprintf(fic,"<p>\n<a href=\"http://validator.w3.org/check/referer\"><img src=\"valid-xhtml10.png\" height=\"31\" width=\"88\" alt=\"Valid XHTML 1.0!\" /></a>\n");
962 fprintf(fic,"<a href=\"http://jigsaw.w3.org/css-validator/check/referer\"><img src=\"valid-css.png\" height=\"31\" width=\"88\" alt=\"Valid CSS!\" /></a>\n</p>\n");
964 fprintf(fic,"</div>\n\n</div>");
965 if (strcmp("none",header)==0)
967 fprintf(fic,"\n\n</body>\n\n</html>\n");
971 if ((sfic=fopen(footer,"rt"))==NULL) { fprintf(stderr,"can't open footer file \"%s\"\n",footer); exit(1); }
972 while ((temp=fread(line,1,MAXLINELENGTH,sfic))) fwrite(line,temp,1,fic);
979 void parse_config(char *configfile)
982 char line[MAXLINELENGTH];
983 char keyword[MAXLINELENGTH];
984 char value[MAXLINELENGTH];
988 if (configfile!=NULL)
990 if ((fic=fopen(configfile,"rt"))==NULL)
992 fprintf(stderr,"can't open config file : \"%s\"\n",configfile);
998 sprintf(line,"%s/.irssistats",getenv("HOME"));
999 if ((fic=fopen(line,"rt"))==NULL)
1000 if ((fic=fopen("/etc/irssistats.conf","rt"))==NULL)
1002 fprintf(stderr,"can't find config file : \"%s\" nor \"/etc/irssistats.conf\"\n",line);
1003 fprintf(stderr,"please give the path to the config file in argument\n");
1008 while (fgets(line,MAXLINELENGTH,fic))
1011 if (*line!=';' && *line!='#' && *line!='/' && *line!='-' && *line!='\n')
1013 if ((sscanf(line,"%s : %s\n",(char *)&keyword,(char *)&value))!=2) { fprintf(stderr,"error in config file : each line must have the format \"keyword : value\" (line %d)\n",configlines); exit(1); }
1015 if (strcmp("debug",keyword)==0)
1017 if (strcmp("none",value)==0) debug=0;
1019 if (strcmp("normal",value)==0) debug=1;
1021 if (strcmp("verbose",value)==0) { debug=2; fprintf(stderr,"switching to verbose output\n"); }
1022 else { fprintf(stderr,"unknown value for \"debug\" option, must be \"normal\", \"verbose\" or \"none\"\n"); exit(1); }
1025 if (strcmp("channel",keyword)==0)
1027 if (debug==2) fprintf(stderr,"setting channel name to \"%s\"\n",value);
1028 strcpy(channel,value);
1032 if (strcmp("maintainer",keyword)==0)
1034 if (debug==2) fprintf(stderr,"setting maintainer to \"%s\"\n",value);
1035 strcpy(maintainer,value);
1039 if (strcmp("language",keyword)==0)
1041 if (debug==2) fprintf(stderr,"setting language to \"%s\"\n",value);
1042 for (i=0;i<NBLANGUAGES;i++) if (strcmp(value,keys[i][0][1])==0) { language=i; break; }
1045 fprintf(stderr,"Invalid language : %s\n",value);
1046 fprintf(stderr,"Supported languages :\n");
1047 for (i=0;i<NBLANGUAGES;i++) fprintf(stderr,"%s = %s\n",keys[i][0][1],keys[i][0][0]);
1053 if (strcmp("theme",keyword)==0)
1055 if (debug==2) fprintf(stderr,"setting theme to \"%s\"\n",value);
1056 strcpy(theme,value);
1060 if (strcmp("refresh_time",keyword)==0)
1062 refresh_time=atoi(value);
1063 if (debug==2) fprintf(stderr,"setting refresh_time to \"%d\"\n",refresh_time);
1067 if (strcmp("w3c_link",keyword)==0)
1069 if (debug==2) fprintf(stderr,"setting w3c_link to \"%s\"\n",value);
1070 if (strcmp("no",value)==0) w3c_link=0;
1071 else if (strcmp("yes",value)==0) w3c_link=1;
1072 else { fprintf(stderr,"unknown value for \"w3c_link\" option, must be \"yes\" or \"no\"\n"); exit(1); }
1076 if (strcmp("header",keyword)==0)
1078 if (debug==2) fprintf(stderr,"setting header to \"%s\"\n",value);
1079 strcpy(header,value);
1083 if (strcmp("footer",keyword)==0)
1085 if (debug==2) fprintf(stderr,"setting footer to \"%s\"\n",value);
1086 strcpy(footer,value);
1090 if (strcmp("input",keyword)==0)
1092 if (debug==2) fprintf(stderr,"parsing log file \"%s\"\n",value);
1097 if (strcmp("nickfile",keyword)==0)
1099 if (debug==2) fprintf(stderr,"nick alias using file \"%s\"\n",value);
1104 if (strcmp("output",keyword)==0)
1106 if (debug==2) fprintf(stderr,"generating xhtml file \"%s\"\n",value);
1110 /* reset variables */
1115 for (i=0;i<24;i++) hours[i]=0;
1118 for (i=0;i<NBWORDS;i++) topwords[i].nb=0;
1123 else { fprintf(stderr,"error in config file : \"%s\" is an unknown keyword (line %d)\n",keyword,configlines); exit(1); }
1129 int main(int argc,char *argv[])
1131 srand(debut=time(NULL));
1132 if (argc==1) parse_config(NULL);
1133 else if (argc==2) parse_config(argv[1]);
1136 fprintf(stderr,"Usage : %s [/path/to/file.conf]\n",argv[0]);
1137 fprintf(stderr,"Version : irssistats %s\n",VERSION);