version 0.51
[irssistats] / irssistats.c
1 /* Usage: irssistats [/path/to/file.conf] */
2
3 #include <sys/types.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <time.h>
7 #include <string.h>
8 #include <regex.h>
9
10 /* Config */
11 #define MAXUSERS 10000
12 #define MAXNICKLENGTH 50
13 #define MAXLINELENGTH 2000
14 #define MAXQUOTELENGTH 100
15 #define NBUSERS 50
16 #define NBUSERSTIME 10
17 #define NBURLS 5
18 #define NBTOPICS 5
19 #define NBWORDS 20
20 #define MINWORDLENGTH 5
21
22 /* irssistats */
23 #define VERSION "0.51"
24 #define URL "http://royale.zerezo.com/irssistats/"
25
26 /* Counters */
27 #define D_SMILE     0
28 #define D_FROWN     1
29 #define D_EXCLAM    2
30 #define D_QUESTION  3
31 #define D_ME        4
32 #define D_TOPIC     5
33 #define D_MODE      6
34 #define D_KICK      7
35 #define D_KICKED    8
36 #define D_URL       9
37 #define D_JOIN      10
38 #define D_NICK      11
39 #define D_MONOLOGUE 12
40 #define NBCOUNTERS  13
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"};
42
43 /* Languages */
44 #define NBLANGUAGES 8
45 #define NBKEYS 39
46 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
47 {
48   { /* English language */
49     { "English",      "en" },
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" },
58     { "NICK",         "nick" },
59     { "AVGLETTERS",   "letters/lines" },
60     { "HOURS",        "hours" },
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" },
70     { "WORD",         "word" },
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" }
89   },
90   { /* French language */
91     { "French",       "fr" },
92     { "CHARSET",      "ISO-8859-1" },
93     { "HEADER",       "Statistiques de %s par %s" },
94     { "LEGEND",       "L&eacute;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&eacute;es..." },
99     { "NBLINES",      "lignes" },
100     { "NICK",         "nick" },
101     { "AVGLETTERS",   "lettres/lignes" },
102     { "HOURS",        "heures" },
103     { "QUOTE",        "message al&eacute;atoire" },
104     { "TOPUSERSTIME", "Personnes les plus actives par p&eacute;riode de la journ&eacute;e" },
105     { "RANDTOPICS",   "Quelques topics" },
106     { "CHANGEDBY",    "chang&eacute; par" },
107     { "NEWTOPIC",     "nouveau topic" },
108     { "RANDURLS",     "Quelques URLs" },
109     { "POSTEDBY",     "post&eacute;e par" },
110     { "POSTEDURL",    "URL" },
111     { "TOPWORDS",     "Mots les plus utilis&eacute;s" },
112     { "WORD",         "mot" },
113     { "OCCURRENCES",  "occurrences" },
114     { "BIGNUMBERS",   "Quelques grands nombres..." },
115     { "NUMBERS",      "nombres" },
116     { "TIME",         "%d lignes (%d jours) trait&eacute;es en %d secondes" },
117     { "FOOTER",       "Statistiques g&eacute;n&eacute;r&eacute;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&eacute;" },
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" }
131   },
132   { /* German language */
133     /* contributed by Valentin Gelhorn <valentin.gelhorn@web.de> */
134     { "German",       "de" },
135     { "CHARSET",      "ISO-8859-1" },
136     { "HEADER",       "Statistiken f&uuml;r %s von %s" },
137     { "LEGEND",       "Legende" },
138     { "LASTDAYS",     "Statistik der letzten Tage" },
139     { "TOPHOURS",     "St&uuml;ndliche Statistik" },
140     { "TOPUSERS",     "Die aktivsten Personen" },
141     { "OTHERS",       "Es bleiben noch %d uneingetragene" },
142     { "NBLINES",      "Zeilen" },
143     { "NICK",         "Nick" },
144     { "AVGLETTERS",   "Buchstaben/Zeile" },
145     { "HOURS",        "Stunden" },
146     { "QUOTE",        "Zuf&auml;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&auml;ufigsten benutze W&ouml;rter" },
155     { "WORD",         "Wort" },
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&uuml;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",       "&auml;ndert oft seinen Nick" },
173     { "C_MONOLOGUE",  "spricht oft Monologe" }
174   },
175   { /* Spanish language */
176     /* contributed by Alex <ainaker@gmx.net> */
177     { "Spanish",      "es" },
178     { "CHARSET",      "ISO-8859-1" },
179     { "HEADER",       "Estad&iacute;sticas de %s por %s" },
180     { "LEGEND",       "Leyenda" },
181     { "LASTDAYS",     "Estad&iacute;sticas de los &uacute;ltimos d&iacute;as" },
182     { "TOPHOURS",     "Estad&iacute;sticas por horas" },
183     { "TOPUSERS",     "Los que m&aacute;s escriben" },
184     { "OTHERS",       "Hay %d m&aacute;s que no llegaron..." },
185     { "NBLINES",      "l&iacute;neas" },
186     { "NICK",         "nick" },
187     { "AVGLETTERS",   "letras por l&iacute;nea" },
188     { "HOURS",        "horas" },
189     { "QUOTE",        "Frase aleatoria" },
190     { "TOPUSERSTIME", "Los que m&aacute;s escriben seg&uacute;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&aacute;s usadas" },
198     { "WORD",         "Palabra" },
199     { "OCCURRENCES",  "Frecuencia" },
200     { "BIGNUMBERS",   "Algunos datos..." },
201     { "NUMBERS",      "N&uacute;mero de veces" },
202     { "TIME",         "%d lineas (%d d&iacute;as) procesadas en %d segundos" },
203     { "FOOTER",       "Estad&iacute;sticas generadas por" },
204     { "C_SMILE",      "Suele estar fel&iacute;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" }
217   },
218   { /* Polish language */
219     /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
220     /* updated by Jakub Jankowski <shasta@atn.pl> */
221     { "Polish",       "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" },
230     { "NICK",         "nick" },
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" },
242     { "WORD",         "s³owo" },
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" }
261   },
262   { /* Finnish language */
263     /* contributed by Antti Huopana <ahuopana@ratol.fi> */
264     { "Finnish",      "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" },
273     { "NICK",         "nikki" },
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" },
285     { "WORD",         "sana" },
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" }
304   },
305   { /* Italian language */
306     /* contributed by Coviello Giuseppe <giuseppecoviello@tin.it> <http://coviello.altervista.org> */
307     { "Italian",      "it" },
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&ugrave; attivi" },
314     { "OTHERS",       "Ci sono %d utenti non classificati..." },
315     { "NBLINES",      "righe" },
316     { "NICK",         "nick" },
317     { "AVGLETTERS",   "lettere/righe" },
318     { "HOURS",        "ore" },
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" }
347   },
348   { /* Dutch language */
349     /* contributed by Jeroen Ubbink <crasp@blackbyte.nl> */
350     { "Dutch",        "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" },
359     { "NICK",         "nick" },
360     { "AVGLETTERS",   "letters/lijn" },
361     { "HOURS",        "uren" },
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" },
371     { "WORD",         "woord" },
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" }
390   }  
391 };
392
393 int language=0; /* default to english */
394
395 char *L(char *key)
396 {
397   int i;
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);
400   return("");
401 }
402
403 /* Variables */
404
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";
413 int totallines=0;
414 time_t debut;
415
416 struct
417 {
418   char nick[MAXNICKLENGTH];
419   int lines;
420   int letters;
421   int hours[4];
422   char quote[MAXQUOTELENGTH+1];
423   int counters[NBCOUNTERS];
424   int temp;
425 } users[MAXUSERS];
426 int nbusers=0;
427
428 struct
429 {
430   char nick[MAXNICKLENGTH];
431   char url[MAXLINELENGTH];
432   char shorturl[MAXQUOTELENGTH+1];
433 } urls[NBURLS];
434 int nburls=0;
435
436 struct
437
438   char nick[MAXNICKLENGTH];
439   char topic[MAXQUOTELENGTH+1];
440 } topics[NBTOPICS];
441 int nbtopics=0;
442
443 struct
444 {
445   int lines;
446   int hours[4];
447 } lastdays[31];
448 int days=0;
449
450 int hours[24];
451 int lines=0;
452
453 struct letter
454 {
455   int nb;
456   struct letter *next[26];
457 } words;
458
459 struct
460 {
461   int nb;
462   char word[MAXLINELENGTH];
463 } topwords[NBWORDS];
464
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)
468 {
469   int i,c;
470   struct letter *pos,*tmp;
471   for (;;)
472   {
473     while (!isletter(*message)) if (*message=='\0') return; else message++;
474     pos=&words;
475     while (isletter(*message))
476     {
477       c=lowercase(*message)-'a';
478       if (pos->next[(int)c]==NULL)
479       {
480         tmp=malloc(sizeof(struct letter));
481         if (tmp==NULL)
482         {
483           fprintf(stderr, "findwords(): malloc failure\n");
484           exit(1);
485         }
486         tmp->nb=0;
487         for (i=0;i<26;i++) tmp->next[i]=NULL;
488         pos->next[(int)c]=tmp;
489       }
490       pos=pos->next[(int)c];
491       message++; 
492     }
493     pos->nb++;
494   }
495   return;
496 }
497
498 char tempword[MAXLINELENGTH];
499 void bestwords(struct letter pos,int cur)
500 {
501   int i,j;
502   if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
503   {
504     for (i=0;pos.nb<topwords[i].nb;i++);
505     for (j=NBWORDS-1;j>i;j--)
506     {
507       topwords[j].nb=topwords[j-1].nb;
508       strcpy(topwords[j].word,topwords[j-1].word);
509     }
510     topwords[i].nb=pos.nb;
511     strcpy(topwords[i].word,tempword);
512   }
513   for (i=0;i<26;i++) if (pos.next[i]!=NULL)
514   {
515     tempword[cur]='a'+i;
516     bestwords(*(pos.next[i]),cur+1);
517   }
518   tempword[cur]='\0';
519 }
520
521 void freewords(struct letter *pos)
522 {
523   int i;
524   for (i=0;i<26;i++) if (pos->next[i]!=NULL)
525   {
526     freewords(pos->next[i]);
527     free(pos->next[i]);
528     (*pos).next[i]=NULL;
529   }
530 }
531
532 void printhtml(FILE *fic,char *string) /* replace < and > by &lt; and &gt; */
533 {
534   while (*string!='\0')
535   {
536     switch (*string)
537     {
538       case '<':fprintf(fic,"&lt;"); break;
539       case '>':fprintf(fic,"&gt;"); break;
540       case '&':fprintf(fic,"&amp;"); break;
541       default:fprintf(fic,"%c",*string); break;
542     }
543     string++;
544   }
545   return;
546 }
547
548 int dichotomic(char *nick)
549 {
550   int i,j,start=0,end=nbusers-1,middle;
551   while (start<=end)
552   {
553     middle=(start+end)/2;
554     if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
555   }
556   if (strcmp(nick,users[start].nick)!=0)
557   {
558     nbusers++;
559     if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
560     for (i=nbusers-1;i>start;i--)
561     {
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;
569     }
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;
576     users[start].temp=0;
577   }
578   return(start);
579 }
580
581 void parse_log(char *logfile)
582 {
583   FILE *fic;
584   char line[MAXLINELENGTH];
585   int pos;
586   int i,j;
587   char *nick,*message;
588   int nickstart;
589   int mononick,monolines;
590   int temp,hour;
591
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)
595   {
596     /* remove \n */
597     for (i=0;line[i]!=0;i++);
598     if (i>=MAXLINELENGTH-1) { fprintf(stderr,"line %d is too long\n",totallines); exit(1); }
599     line[i-1]='\0';
600     pos=0;
601     totallines++;
602     if (totallines%10000==0 && debug) { printf("."); fflush(stdout); }
603     if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
604     {
605       for (i=30;i>0;i--)
606       {
607         lastdays[i].lines=lastdays[i-1].lines;
608         for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
609       }
610       lastdays[0].lines=0;
611       for (j=0;j<4;j++) lastdays[0].hours[j]=0;
612       days++;
613     }
614     else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
615     {
616       for (i=strlen(line);line[i]!=' ';i--);
617       nick=&line[i+1];
618       users[dichotomic(nick)].counters[D_MODE]++;
619     }
620     else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
621     {
622       for (i=10;line[i]!=' ';i++);
623       line[i]='\0';
624       nick=&line[10];
625       message=&line[i+1];
626       if (strncmp("changed the topic of",message,20)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
627       {
628         users[dichotomic(nick)].counters[D_TOPIC]++;
629         for (i=21;message[i]!=':';i++);
630         message=&message[i+2];
631         nbtopics++;
632         if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
633         {
634           temp=nbtopics<=NBTOPICS?nbtopics-1:rand()%NBTOPICS;
635           strcpy(topics[temp].nick,nick);
636           strncpy(topics[temp].topic,message,MAXQUOTELENGTH);
637         }
638       }
639       else if (strncmp("was kicked from",message,15)==0) /* 00:00 -!- Nick was kicked from #channel by Nick [Reason] */
640       {
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++);
645         message[i]='\0';
646         users[dichotomic(message)].counters[D_KICK]++;
647       }
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... */
651       {
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] */
658         else;
659       }
660     }
661     else if ((line[6]=='<') || (line[7]=='*'))
662     {
663       line[2]='\0';
664       hour=atoi(line);
665       if (line[7]=='*') /* 00:00  * Nick the message */
666       {
667         for (i=9;line[i]!=' ';i++);
668         nick=&line[9];
669         message=&line[i+1];
670       }
671       else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
672                              /* 00:00 <>>?Nick<<> the personal message */
673       {
674         for (i=10;line[i]!='<';i++);
675         nick=&line[10];
676         if (line[9]=='>') nick++;
677         message=&line[i+5];
678       }
679       else /* 00:00 <?Nick> the message */
680       {
681
682         /* 
683          * Irssi doesn't log channel mode with show_nickmode = OFF    
684          * the following covers op, half-op, voice and show_nickmode_empty                 
685          */
686         if (line[7]=='@' || line[7]=='%' || line[7]=='+' || line[7]==' ') {
687             nickstart = 8;
688         } else {
689             nickstart = 7;
690         }
691           
692         for (i=nickstart;line[i]!='>';i++);
693         nick=&line[nickstart];
694         message=&line[i+2];
695       }
696       line[i]='\0';
697       i=dichotomic(nick);
698       if (line[7]=='*') users[i].counters[D_ME]++;
699       if (i==mononick)
700       {
701         monolines++;
702         if (monolines==5) users[i].counters[D_MONOLOGUE]++;
703       }
704       else
705       {
706         mononick=i;
707         monolines=1;
708       }
709       j=strlen(message);
710       users[i].lines++;
711       users[i].letters+=j;
712       users[i].hours[hour/6]++;
713       lastdays[0].lines++;
714       lastdays[0].hours[hour/6]++;
715       lines++;
716       hours[hour]++;
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]==':'))
720       {
721         if (message[j-1]==')') users[i].counters[D_SMILE]++;
722         else if (message[j-1]=='(') users[i].counters[D_FROWN]++;
723       }
724       if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
725       if (strncmp("http://",message,7)==0)
726       {
727         users[i].counters[D_URL]++;
728         for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
729         message[i]='\0';
730         nburls++;
731         if ((nburls<=NBURLS) || (rand()%(nburls/NBURLS)==0))
732         {
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);
737         }
738       }
739       findwords(message);
740     }
741     pos=0;
742   }
743   fclose(fic);
744   if (debug) printf(" done\n");
745 }
746
747 void parse_nick(char *nickfile)
748 {
749   FILE *fic;
750   int i,j;
751   regex_t preg;
752   char line[MAXLINELENGTH];
753   int user;
754   
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)
758   {
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))
763     {
764       if (users[i].temp>users[user].temp) /* for nick alias, keep the random quote of the most used nick */
765       {
766         strcpy(users[user].quote,users[i].quote);
767         users[user].temp=users[i].temp;
768       }
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 */
774       users[i].lines=-1;
775       users[i].letters=-1;
776       for (j=0;j<4;j++) users[i].hours[j]=-1;
777       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
778     }
779     regfree(&preg);
780   }
781   fclose(fic);
782   /* "remove" the ignored nicks */
783   i=dichotomic("<NULL>");
784   users[i].lines=-1;
785   users[i].letters=-1;
786   for (j=0;j<4;j++) users[i].hours[j]=-1;
787   for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
788 }
789
790 void gen_xhtml(char *xhtmlfile)
791 {
792   FILE *fic;
793   FILE *sfic;
794   int i,j,k;
795   int user,max,temp;
796   char line[MAXLINELENGTH];
797   
798   if ((fic=fopen(xhtmlfile,"wt"))==NULL) { fprintf(stderr,"can't open xhtml file \"%s\"\n",xhtmlfile); exit(1); }
799   
800   /* header */
801   if (strcmp("none",header)==0)
802   {
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"));
808     if (refresh_time)
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");
813   }
814   else
815   {
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);
818     fclose(sfic);
819   }
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));
823
824   /* legend */
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");
828   
829   /* last days */
830   fprintf(fic,"<div id=\"irssistats_lastdays\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LASTDAYS"));
831   max=-1;
832   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
833   for (i=30;i>=0;i--)
834   {
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");
838   }
839   fprintf(fic,"</tr>\n<tr>\n");
840   for (i=30;i>=0;i--)
841     fprintf(fic,"<th>%d</th>\n",i);
842   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
843   
844   /* top hours */
845   fprintf(fic,"<div id=\"irssistats_tophours\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("TOPHOURS"));
846   max=-1;
847   for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
848   for (i=0;i<24;i++)
849   {
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");
853   }
854   fprintf(fic,"</tr>\n<tr>\n");
855   for (i=0;i<24;i++)
856     fprintf(fic,"<th>%d</th>\n",i);
857   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
858   
859   /* top users */
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++)
863   {
864     user=-1;
865     max=0;
866     for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
867     if (user!=-1)
868     {
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;
875     }    
876   }
877   fprintf(fic,"</table>\n");
878   temp=0;
879   for (i=0;i<=nbusers;i++) if (users[i].lines>=0) temp++;
880   if (temp>0)
881   {
882     fprintf(fic,"<p>");
883     fprintf(fic,L("OTHERS"),temp);
884     fprintf(fic,"</p>\n");
885   }
886   fprintf(fic,"</div>\n\n");
887   
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++)
894   {
895     fprintf(fic,"<tr><td>%d</td>",i);
896     for (j=0;j<4;j++)
897     {
898       user=-1;
899       max=0;
900       for (k=0;k<nbusers;k++) if (users[k].hours[j]>max) max=users[user=k].hours[j];
901       if (user!=-1)
902       {
903         fprintf(fic,"<td>%s</td><td>%d</td>",users[user].nick,users[user].hours[j]);
904         users[user].hours[j]=-1;
905       }
906       else fprintf(fic,"<td></td><td></td>");
907     }
908     fprintf(fic,"</tr>\n");
909   }
910   fprintf(fic,"</table>\n</div>\n\n");
911
912   /* random topics */
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--)
916   {
917     fprintf(fic,"<tr><td>%s</td><td>\"",topics[i].nick);
918     printhtml(fic,topics[i].topic);
919     fprintf(fic,"\"</td></tr>\n");
920   }
921   fprintf(fic,"</table>\n</div>\n\n");
922   
923   /* random urls */
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--)
927   {
928     fprintf(fic,"<tr><td>%s</td><td>\"<a href=\"",urls[i].nick);
929     printhtml(fic,urls[i].url);
930     fprintf(fic,"\">");
931     printhtml(fic,urls[i].shorturl);
932     fprintf(fic,"</a>\"</td></tr>\n");
933   }
934   fprintf(fic,"</table>\n</div>\n\n");
935   
936   /* top words */
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");
942
943   /* big numbers */
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++)
947   {
948     user=-1;
949     max=0;
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]);
952   }
953   fprintf(fic,"</table>\n</div>\n\n");
954   
955   /* footer */
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);
959   if (w3c_link)
960   {
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");
963   }
964   fprintf(fic,"</div>\n\n</div>");
965   if (strcmp("none",header)==0)
966   {
967     fprintf(fic,"\n\n</body>\n\n</html>\n");
968   }
969   else
970   {
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);
973     fclose(sfic);
974   }
975   
976   fclose(fic);
977 }
978
979 void parse_config(char *configfile)
980 {
981   FILE *fic;
982   char line[MAXLINELENGTH];
983   char keyword[MAXLINELENGTH];
984   char value[MAXLINELENGTH];
985   int configlines=0;
986   int i;
987   
988   if (configfile!=NULL)
989   {
990     if ((fic=fopen(configfile,"rt"))==NULL)
991     {
992       fprintf(stderr,"can't open config file : \"%s\"\n",configfile);
993       exit(1);
994     }
995   }
996   else
997   {
998     sprintf(line,"%s/.irssistats",getenv("HOME"));
999     if ((fic=fopen(line,"rt"))==NULL)
1000       if ((fic=fopen("/etc/irssistats.conf","rt"))==NULL)
1001       {
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");
1004         exit(1);
1005       }
1006   }
1007
1008   while (fgets(line,MAXLINELENGTH,fic))
1009   {
1010     configlines++;
1011     if (*line!=';' && *line!='#' && *line!='/' && *line!='-' && *line!='\n')
1012     {
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); }
1014       
1015       if (strcmp("debug",keyword)==0)
1016       {
1017         if (strcmp("none",value)==0) debug=0;
1018         else
1019         if (strcmp("normal",value)==0) debug=1;
1020         else
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); }
1023       } else
1024       
1025       if (strcmp("channel",keyword)==0)
1026       {
1027         if (debug==2) fprintf(stderr,"setting channel name to \"%s\"\n",value);
1028         strcpy(channel,value);
1029       }
1030       else
1031       
1032       if (strcmp("maintainer",keyword)==0)
1033       {
1034         if (debug==2) fprintf(stderr,"setting maintainer to \"%s\"\n",value);
1035         strcpy(maintainer,value);
1036       }
1037       else
1038       
1039       if (strcmp("language",keyword)==0)
1040       {
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; }
1043         if (i==NBLANGUAGES)
1044         {
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]);
1048           exit(1);
1049         }
1050       }
1051       else
1052       
1053       if (strcmp("theme",keyword)==0)
1054       {
1055         if (debug==2) fprintf(stderr,"setting theme to \"%s\"\n",value);
1056         strcpy(theme,value);
1057       }
1058       else
1059       
1060       if (strcmp("refresh_time",keyword)==0)
1061       {
1062         refresh_time=atoi(value);
1063         if (debug==2) fprintf(stderr,"setting refresh_time to \"%d\"\n",refresh_time);
1064       }
1065       else
1066       
1067       if (strcmp("w3c_link",keyword)==0)
1068       {
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); }
1073       }
1074       else
1075       
1076       if (strcmp("header",keyword)==0)
1077       {
1078         if (debug==2) fprintf(stderr,"setting header to \"%s\"\n",value);
1079         strcpy(header,value);
1080       }
1081       else
1082       
1083       if (strcmp("footer",keyword)==0)
1084       {
1085         if (debug==2) fprintf(stderr,"setting footer to \"%s\"\n",value);
1086         strcpy(footer,value);
1087       }
1088       else
1089       
1090       if (strcmp("input",keyword)==0)
1091       {
1092         if (debug==2) fprintf(stderr,"parsing log file \"%s\"\n",value);
1093         parse_log(value);
1094       }
1095       else
1096       
1097       if (strcmp("nickfile",keyword)==0)
1098       {
1099         if (debug==2) fprintf(stderr,"nick alias using file \"%s\"\n",value);
1100         parse_nick(value);
1101       }
1102       else
1103       
1104       if (strcmp("output",keyword)==0)
1105       {
1106         if (debug==2) fprintf(stderr,"generating xhtml file \"%s\"\n",value);
1107         bestwords(words,0);
1108         gen_xhtml(value);
1109         
1110         /* reset variables */
1111         nbusers=0;
1112         nburls=0;
1113         nbtopics=0;
1114         days=0;
1115         for (i=0;i<24;i++) hours[i]=0;
1116         lines=0;
1117         freewords(&words);
1118         for (i=0;i<NBWORDS;i++) topwords[i].nb=0;
1119         totallines=0;
1120         debut=time(NULL);
1121       }
1122       
1123       else { fprintf(stderr,"error in config file : \"%s\" is an unknown keyword (line %d)\n",keyword,configlines); exit(1); }        
1124     }
1125   }
1126   fclose(fic);
1127 }
1128
1129 int main(int argc,char *argv[])
1130 {
1131   srand(debut=time(NULL));
1132   if (argc==1) parse_config(NULL);
1133   else if (argc==2) parse_config(argv[1]);
1134   else
1135   {
1136     fprintf(stderr,"Usage : %s [/path/to/file.conf]\n",argv[0]);
1137     fprintf(stderr,"Version : irssistats %s\n",VERSION);
1138     exit(1);
1139   }
1140   return(0);
1141 }