version 0.70
[irssistats] / irssistats.c
1 /*
2  * irssistats version 0.70
3  *
4  * This tool generates IRC stats based on irssi logs.
5  * Usage: irssistats [/path/to/file.conf]
6  *
7  * Copyright (C) 2002-2004  Antoine Jacquet <royale@zerezo.com>
8  * http://royale.zerezo.com/irssistats/
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24
25
26 #include <sys/types.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <time.h>
30 #include <string.h>
31 #ifdef __WIN32__
32 #define GLOBALCONF "irssistats.conf"
33 #else
34 #define GLOBALCONF "/etc/irssistats.conf"
35 #include <regex.h>
36 #endif
37
38 /* Config */
39 #define BASEUSERS 1000
40 #define MAXNICKLENGTH 50
41 #define MAXLINELENGTH 2000
42 #define MAXQUOTELENGTH 100
43 #define NBUSERS 50
44 #define NBUSERSTIME 10
45 #define NBURLS 5
46 #define NBTOPICS 5
47 #define NBWORDS 20
48 #define MINWORDLENGTH 5
49
50 /* irssistats */
51 #define VERSION "0.70"
52 #define URL "http://royale.zerezo.com/irssistats/"
53
54 /* Counters */
55 #define D_SMILE     0
56 #define D_FROWN     1
57 #define D_EXCLAM    2
58 #define D_QUESTION  3
59 #define D_ME        4
60 #define D_TOPIC     5
61 #define D_MODE      6
62 #define D_KICK      7
63 #define D_KICKED    8
64 #define D_URL       9
65 #define D_JOIN      10
66 #define D_NICK      11
67 #define D_MONOLOGUE 12
68 #define NBCOUNTERS  13
69 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"};
70
71 /* Languages */
72 #define NBLANGUAGES 11
73 #define NBKEYS 39
74 char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
75 {
76   { /* English language */
77     { "English",      "en" },
78     { "CHARSET",      "ISO-8859-1" },
79     { "HEADER",       "Statistics for %s by %s" },
80     { "LEGEND",       "Legend" },
81     { "LASTDAYS",     "Lastdays statistics" },
82     { "TOPHOURS",     "Hourly statistics" },
83     { "TOPUSERS",     "Most active people" },
84     { "OTHERS",       "There are %d left not ranked..." },
85     { "NBLINES",      "lines" },
86     { "NICK",         "nick" },
87     { "AVGLETTERS",   "letters/lines" },
88     { "HOURS",        "hours" },
89     { "QUOTE",        "random message" },
90     { "TOPUSERSTIME", "Most active people by time of day" },
91     { "RANDTOPICS",   "Some topics" },
92     { "CHANGEDBY",    "changed by" },
93     { "NEWTOPIC",     "new topic" },
94     { "RANDURLS",     "Some URLs" },
95     { "POSTEDBY",     "posted by" },
96     { "POSTEDURL",    "URL" },
97     { "TOPWORDS",     "Most used words" },
98     { "WORD",         "word" },
99     { "OCCURRENCES",  "occurrences" },
100     { "BIGNUMBERS",   "Some big numbers..." },
101     { "NUMBERS",      "numbers" },
102     { "TIME",         "%d lines (%d days) parsed in %d seconds" },
103     { "FOOTER",       "Statistics generated by" },
104     { "C_SMILE",      "is often happy :)" },
105     { "C_FROWN",      "is often sad :(" },
106     { "C_EXCLAM",     "yells a lot !" },
107     { "C_QUESTION",   "asks a lot of questions ?" },
108     { "C_ME",         "likes /me command" },
109     { "C_TOPIC",      "often changes the topic" },
110     { "C_MODE",       "often changes the modes" },
111     { "C_KICK",       "likes to /kick" },
112     { "C_KICKED",     "is often kicked" },
113     { "C_URL",        "posts many URLs" },
114     { "C_JOIN",       "doesn't know wether to stay or quit" },
115     { "C_NICK",       "often changes his nick" },
116     { "C_MONOLOGUE",  "speaks a lot of monologues" }
117   },
118   { /* French language */
119     { "French",       "fr" },
120     { "CHARSET",      "ISO-8859-1" },
121     { "HEADER",       "Statistiques de %s par %s" },
122     { "LEGEND",       "L&eacute;gende" },
123     { "LASTDAYS",     "Statistiques des derniers jours" },
124     { "TOPHOURS",     "Statistiques horaires" },
125     { "TOPUSERS",     "Personnes les plus actives" },
126     { "OTHERS",       "Il reste %d personnes non class&eacute;es..." },
127     { "NBLINES",      "lignes" },
128     { "NICK",         "nick" },
129     { "AVGLETTERS",   "lettres/lignes" },
130     { "HOURS",        "heures" },
131     { "QUOTE",        "message al&eacute;atoire" },
132     { "TOPUSERSTIME", "Personnes les plus actives par p&eacute;riode de la journ&eacute;e" },
133     { "RANDTOPICS",   "Quelques topics" },
134     { "CHANGEDBY",    "chang&eacute; par" },
135     { "NEWTOPIC",     "nouveau topic" },
136     { "RANDURLS",     "Quelques URLs" },
137     { "POSTEDBY",     "post&eacute;e par" },
138     { "POSTEDURL",    "URL" },
139     { "TOPWORDS",     "Mots les plus utilis&eacute;s" },
140     { "WORD",         "mot" },
141     { "OCCURRENCES",  "occurrences" },
142     { "BIGNUMBERS",   "Quelques grands nombres..." },
143     { "NUMBERS",      "nombres" },
144     { "TIME",         "%d lignes (%d jours) trait&eacute;es en %d secondes" },
145     { "FOOTER",       "Statistiques g&eacute;n&eacute;r&eacute;es par" },
146     { "C_SMILE",      "est souvent heureux :)" },
147     { "C_FROWN",      "est souvent triste :(" },
148     { "C_EXCLAM",     "hurle beaucoup !" },
149     { "C_QUESTION",   "pose beaucoup de questions ?" },
150     { "C_ME",         "aime la commande /me" },
151     { "C_TOPIC",      "change souvent le topic" },
152     { "C_MODE",       "change souvent les modes" },
153     { "C_KICK",       "aime la commande /kick" },
154     { "C_KICKED",     "est souvent kick&eacute;" },
155     { "C_URL",        "poste beaucoup d'URLs" },
156     { "C_JOIN",       "ne sait pas s'il doit rester ou partir" },
157     { "C_NICK",       "change souvent de nick" },
158     { "C_MONOLOGUE",  "parle beaucoup de monologues" }
159   },
160   { /* German language */
161     /* contributed by Valentin Gelhorn <valentin.gelhorn@web.de> */
162     { "German",       "de" },
163     { "CHARSET",      "ISO-8859-1" },
164     { "HEADER",       "Statistiken f&uuml;r %s von %s" },
165     { "LEGEND",       "Legende" },
166     { "LASTDAYS",     "Statistik der letzten Tage" },
167     { "TOPHOURS",     "St&uuml;ndliche Statistik" },
168     { "TOPUSERS",     "Die aktivsten Personen" },
169     { "OTHERS",       "Es bleiben noch %d uneingetragene" },
170     { "NBLINES",      "Zeilen" },
171     { "NICK",         "Nick" },
172     { "AVGLETTERS",   "Buchstaben/Zeile" },
173     { "HOURS",        "Stunden" },
174     { "QUOTE",        "Zuf&auml;llig ausgewaehlte Zitate" },
175     { "TOPUSERSTIME", "Die aktivsten Personen zur bestimmten Tageszeit" },
176     { "RANDTOPICS",   "Ein paar Topics" },
177     { "CHANGEDBY",    "Gesetzt von" },
178     { "NEWTOPIC",     "Neues topic" },
179     { "RANDURLS",     "Ein paar URLs" },
180     { "POSTEDBY",     "Geschrieben von" },
181     { "POSTEDURL",    "URL" },
182     { "TOPWORDS",     "Am h&auml;ufigsten benutze W&ouml;rter" },
183     { "WORD",         "Wort" },
184     { "OCCURRENCES",  "Vorkommen" },
185     { "BIGNUMBERS",   "Ein paar grosse Zahlen" },
186     { "NUMBERS",      "Zahlen" },
187     { "TIME",         "%d Zeilen (%d Tage) analysiert in %d Sekunden" },
188     { "FOOTER",       "Statistiken wurden erstellt von" },
189     { "C_SMILE",      "ist oft gl&uuml;klich :)" },
190     { "C_FROWN",      "ist oft traurig :(" },
191     { "C_EXCLAM",     "schreit oft !" },
192     { "C_QUESTION",   "stellt viele Fragen ?" },
193     { "C_ME",         "mag /me'en" },
194     { "C_TOPIC",      "aendert oft das Topico" },
195     { "C_MODE",       "aendert oft die Modes" },
196     { "C_KICK",       "mag /kick'en" },
197     { "C_KICKED",     "wird oft gekickt"},
198     { "C_URL",        "schreibt viele URLs"},
199     { "C_JOIN",       "kann sich nicht entscheiden ob er bleiben oder gehen soll" },
200     { "C_NICK",       "&auml;ndert oft seinen Nick" },
201     { "C_MONOLOGUE",  "spricht oft Monologe" }
202   },
203   { /* Spanish language */
204     /* contributed by Alex <ainaker@gmx.net> */
205     { "Spanish",      "es" },
206     { "CHARSET",      "ISO-8859-1" },
207     { "HEADER",       "Estad&iacute;sticas de %s por %s" },
208     { "LEGEND",       "Leyenda" },
209     { "LASTDAYS",     "Estad&iacute;sticas de los &uacute;ltimos d&iacute;as" },
210     { "TOPHOURS",     "Estad&iacute;sticas por horas" },
211     { "TOPUSERS",     "Los que m&aacute;s escriben" },
212     { "OTHERS",       "Hay %d m&aacute;s que no llegaron..." },
213     { "NBLINES",      "l&iacute;neas" },
214     { "NICK",         "nick" },
215     { "AVGLETTERS",   "letras por l&iacute;nea" },
216     { "HOURS",        "horas" },
217     { "QUOTE",        "Frase aleatoria" },
218     { "TOPUSERSTIME", "Los que m&aacute;s escriben seg&uacute;n la hora" },
219     { "RANDTOPICS",   "Algunos topics" },
220     { "CHANGEDBY",    "Puestos por" },
221     { "NEWTOPIC",     "topic" },
222     { "RANDURLS",     "Algunas URLs" },
223     { "POSTEDBY",     "puestas por" },
224     { "POSTEDURL",    "URL" },
225     { "TOPWORDS",     "Palabras m&aacute;s usadas" },
226     { "WORD",         "Palabra" },
227     { "OCCURRENCES",  "Frecuencia" },
228     { "BIGNUMBERS",   "Algunos datos..." },
229     { "NUMBERS",      "N&uacute;mero de veces" },
230     { "TIME",         "%d lineas (%d d&iacute;as) procesadas en %d segundos" },
231     { "FOOTER",       "Estad&iacute;sticas generadas por" },
232     { "C_SMILE",      "Suele estar fel&iacute;z :)" },
233     { "C_FROWN",      "Suele estar triste :(" },
234     { "C_EXCLAM",     "Grita mucho !" },
235     { "C_QUESTION",   "Hace muchas preguntas ?" },
236     { "C_ME",         "Abusa del comando /me" },
237     { "C_TOPIC",      "Suele cambiar el topic" },
238     { "C_MODE",       "Cambia a veces los modos del canal" },
239     { "C_KICK",       "Le gusta patear" },
240     { "C_KICKED",     "Es pateado con frecuencia" },
241     { "C_URL",        "Pone muchas URLs" },
242     { "C_JOIN",       "No sabe si irse o quedarse" },
243     { "C_NICK",       "Cambia mucho de nick" },
244     { "C_MONOLOGUE",  "Habla solo" }
245   },
246   { /* Polish language */
247     /* contributed by Jakub Jankowski <shasta@atn.pl> */
248     { "Polish",       "pl" },
249     { "CHARSET",      "ISO-8859-2" },
250     { "HEADER",       "Statystyki dla %s zebrane przez %s" },
251     { "LEGEND",       "Legenda" },
252     { "LASTDAYS",     "Statystyki z ostatnich dni" },
253     { "TOPHOURS",     "Statystyki godzinowe" },
254     { "TOPUSERS",     "Najaktywniejsi" },
255     { "OTHERS",       "Jest jeszcze %d nie sklasyfikowanych..." },
256     { "NBLINES",      "linii" },
257     { "NICK",         "nick" },
258     { "AVGLETTERS",   "liter/liniê" },
259     { "HOURS",        "godziny" },
260     { "QUOTE",        "losowa wypowied¼" },
261     { "TOPUSERSTIME", "Najaktywniejsi wed³ug pory dnia" },
262     { "RANDTOPICS",   "Kilka topików" },
263     { "CHANGEDBY",    "ustawiony przez" },
264     { "NEWTOPIC",     "topik" },
265     { "RANDURLS",     "Kilka URL-i" },
266     { "POSTEDBY",     "poda³(a)" },
267     { "POSTEDURL",    "URL" },
268     { "TOPWORDS",     "Najczê¶ciej wystêpuj±ce s³owa" },
269     { "WORD",         "s³owo" },
270     { "OCCURRENCES",  "wyst±pieñ" },
271     { "BIGNUMBERS",   "Kilka wielkopomnych liczb..." },
272     { "NUMBERS",      "kategorie" },
273     { "TIME",         "Dokonano analizy %d linii (obejmuj±cych %d dni) w czasie %d sekund" },
274     { "FOOTER",       "Statystyki wygenerowane przez" },
275     { "C_SMILE",      "jest czêsto szczesliwy(a) :)" },
276     { "C_FROWN",      "jest czêsto smutny(a) :(" },
277     { "C_EXCLAM",     "czêsto KRZYCZY!" },
278     { "C_QUESTION",   "zadaje du¿o pytañ?" },
279     { "C_ME",         "lubi u¿ywaæ /me" },
280     { "C_TOPIC",      "czêsto zmienia topik" },
281     { "C_MODE",       "czêsto zmienia tryby kana³u" },
282     { "C_KICK",       "lubi kopaæ" },
283     { "C_KICKED",     "czêsto wykopywany(a)" },
284     { "C_URL",        "podaje du¿o URL-i" },
285     { "C_JOIN",       "nie wie czy zostaæ, czy wyj¶æ" },
286     { "C_NICK",       "czêsto zmienia nick" },
287     { "C_MONOLOGUE",  "uwielbia monologi" }
288   },
289   { /* Polish language */
290     /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
291     { "Polish",       "pl-old" },
292     { "CHARSET",      "ISO-8859-1" },
293     { "HEADER",       "Statystyki dla %s przez %s" },
294     { "LEGEND",       "Legenda" },
295     { "LASTDAYS",     "Statystyki z ostatnich dni" },
296     { "TOPHOURS",     "Statystyki godzinne" },
297     { "TOPUSERS",     "Najaktywniejsi ludzie" },
298     { "OTHERS",       "Zostalo jeszcze %d nie sklasyfikowanych..." },
299     { "NBLINES",      "linie" },
300     { "NICK",         "nick" },
301     { "AVGLETTERS",   "litery/linie" },
302     { "HOURS",        "godziny" },
303     { "QUOTE",        "przypadkowa wiadomosc" },
304     { "TOPUSERSTIME", "Najaktywniejsi ludzie wedlug czasu dnia" },
305     { "RANDTOPICS",   "Pare tematow" },
306     { "CHANGEDBY",    "zmienione przez" },
307     { "NEWTOPIC",     "nowy temat" },
308     { "RANDURLS",     "Pare URL-i" },
309     { "POSTEDBY",     "wyslane przez" },
310     { "POSTEDURL",    "URL" },
311     { "TOPWORDS",     "Najczestsze slowa" },
312     { "WORD",         "slowo" },
313     { "OCCURRENCES",  "wystapienia" },
314     { "BIGNUMBERS",   "Pare wielkich liczb..." },
315     { "NUMBERS",      "liczby" },
316     { "TIME",         "%d linii (%d dni) sparsowanych w %d sekund" },
317     { "FOOTER",       "Statystyki wygenerowane przez" },
318     { "C_SMILE",      "jest czesto szczesliwy :)" },
319     { "C_FROWN",      "jest czesto smutny :(" },
320     { "C_EXCLAM",     "duzo krzyczy !" },
321     { "C_QUESTION",   "zadaje duzo pytan ?" },
322     { "C_ME",         "lubi /mnie polecenie" },
323     { "C_TOPIC",      "czesto zmienia temat" },
324     { "C_MODE",       "czesto zmienia tryb" },
325     { "C_KICK",       "lubi /kopac" },
326     { "C_KICKED",     "czesto go wykopuja" },
327     { "C_URL",        "wysyla duzo URL-i" },
328     { "C_JOIN",       "nie wie czy zostac czy wyjsc" },
329     { "C_NICK",       "czesto zmienia swojego nicka" },
330     { "C_MONOLOGUE",  "czesto mowi monologiem" }
331   },
332   { /* Finnish language */
333     /* contributed by Antti Huopana <ahuopana@ratol.fi> */
334     { "Finnish",      "fi" },
335     { "CHARSET",      "ISO-8859-1" },
336     { "HEADER",       "Kanavan %s tilastot - %s" },
337     { "LEGEND",       "Merkkien selitykset" },
338     { "LASTDAYS",     "Viime päivien tilastot" },
339     { "TOPHOURS",     "Tilastot tunneittain" },
340     { "TOPUSERS",     "Aktiivisimmat ihmiset" },
341     { "OTHERS",       "Jäljelle jäi %d joita ei listattu..." },
342     { "NBLINES",      "rivit" },
343     { "NICK",         "nikki" },
344     { "AVGLETTERS",   "kirjainta/rivi" },
345     { "HOURS",        "tunnit" },
346     { "QUOTE",        "satunnainen viesti" },
347     { "TOPUSERSTIME", "Vuorokauden ajan mukaan aktiivisimmat" },
348     { "RANDTOPICS",   "Joitakin aiheita" },
349     { "CHANGEDBY",    "vaihtaja" },
350     { "NEWTOPIC",     "aihe" },
351     { "RANDURLS",     "Joitakin URLeja" },
352     { "POSTEDBY",     "lähettäjä" },
353     { "POSTEDURL",    "URL" },
354     { "TOPWORDS",     "Eniten käytettyjä sanoja" },
355     { "WORD",         "sana" },
356     { "OCCURRENCES",  "käytetty" },
357     { "BIGNUMBERS",   "Joitakin isoja lukuja..." },
358     { "NUMBERS",      "luvut" },
359     { "TIME",         "%d riviä (%d päivää) parsittu %d sekunnissa" },
360     { "FOOTER",       "Tilastot on generoinut" },
361     { "C_SMILE",      "on usein iloinen :)" },
362     { "C_FROWN",      "on usein surullinen :(" },
363     { "C_EXCLAM",     "möykkää paljon !" },
364     { "C_QUESTION",   "kyselee liikaa ?" },
365     { "C_ME",         "pitää itsestään" },
366     { "C_TOPIC",      "vaihtaa usein aihetta" },
367     { "C_MODE",       "haluaa elää muuttuvassa maailmassa" },
368     { "C_KICK",       "pitää potkimisesta" },
369     { "C_KICKED",     "tykkää tulla potkituksi" },
370     { "C_URL",        "surffailee liikaa" },
371     { "C_JOIN",       "ei tiedä ollakko vai eikö olla" },
372     { "C_NICK",       "kärsii identiteettiongelmista" },
373     { "C_MONOLOGUE",  "höpöttää paljon itsekseen" }
374   },
375   { /* Italian language */
376     /* contributed by Coviello Giuseppe <giuseppecoviello@tin.it> <http://coviello.altervista.org> */
377     { "Italian",      "it" },
378     { "CHARSET",      "ISO-8859-1" },
379     { "HEADER",       "Statistiche per il canale %s di %s" },
380     { "LEGEND",       "Legenda" },
381     { "LASTDAYS",     "Statistiche degli ultimi giorni" },
382     { "TOPHOURS",     "Statistiche in ore" },
383     { "TOPUSERS",     "Utenti pi&ugrave; attivi" },
384     { "OTHERS",       "Ci sono %d utenti non classificati..." },
385     { "NBLINES",      "righe" },
386     { "NICK",         "nick" },
387     { "AVGLETTERS",   "lettere/righe" },
388     { "HOURS",        "ore" },
389     { "QUOTE",        "messaggio casuale" },
390     { "TOPUSERSTIME", "Utenti più attivi del giorno (divisi per fasce orarie)" },
391     { "RANDTOPICS",   "Alcuni topic" },
392     { "CHANGEDBY",    "cambiato da" },
393     { "NEWTOPIC",     "nuovo topic" },
394     { "RANDURLS",     "ALcuni URL" },
395     { "POSTEDBY",     "postato da" },
396     { "POSTEDURL",    "URL" },
397     { "TOPWORDS",     "Le parole più usate" },
398     { "WORD",         "parola" },
399     { "OCCURRENCES",  "usata" },
400     { "BIGNUMBERS",   "Alcuni numeri ..." },
401     { "NUMBERS",      "numeri" },
402     { "TIME",         "%d righe (%d giorni) esaminate in %d secondi" },
403     { "FOOTER",       "Statistiche generate da" },
404     { "C_SMILE",      "è spesso felice :)" },
405     { "C_FROWN",      "è spesso triste :(" },
406     { "C_EXCLAM",     "esclama molto !" },
407     { "C_QUESTION",   "fa molte domande ?" },
408     { "C_ME",         "ama il comando /me" },
409     { "C_TOPIC",      "cambia spesso il topic" },
410     { "C_MODE",       "cambia spesso i mode" },
411     { "C_KICK",       "ama /kick(are)" },
412     { "C_KICKED",     "è kickato spesso" },
413     { "C_URL",        "posta molti URL" },
414     { "C_JOIN",       "non sa se è ora di andare o restare" },
415     { "C_NICK",       "cambia spesso il nick" },
416     { "C_MONOLOGUE",  "fa molti monologhi" }
417   },
418   { /* Dutch language */
419     /* contributed by Jeroen Ubbink <crasp@blackbyte.nl> */
420     /* updated by Wouter Horré <wouter@ligezin.be> */
421     { "Dutch",        "nl" },
422     { "CHARSET",      "ISO-8859-1" },
423     { "HEADER",       "Statistieken voor %s door %s" },
424     { "LEGEND",       "Legenda" },
425     { "LASTDAYS",     "Statistieken van de laatste dagen" },
426     { "TOPHOURS",     "Statistieken per uur" },
427     { "TOPUSERS",     "Meest actieve mensen" },
428     { "OTHERS",       "Er zijn nog %d mensen die de top niet haalden..." },
429     { "NBLINES",      "regels" },
430     { "NICK",         "nick" },
431     { "AVGLETTERS",   "letters/lijn" },
432     { "HOURS",        "uren" },
433     { "QUOTE",        "Willekeurige regel" },
434     { "TOPUSERSTIME", "Meest actieve mensen per tijdstip per dag" },
435     { "RANDTOPICS",   "Enkele topics" },
436     { "CHANGEDBY",    "gewijzigd door" },
437     { "NEWTOPIC",     "nieuwe topic" },
438     { "RANDURLS",     "Enkele URLs" },
439     { "POSTEDBY",     "Geplaatst door" },
440     { "POSTEDURL",    "URL" },
441     { "TOPWORDS",     "Meest gebruikte woorden" },
442     { "WORD",         "woord" },
443     { "OCCURRENCES",  "aantal" },
444     { "BIGNUMBERS",   "Enkele grote aantallen..." },
445     { "NUMBERS",      "numbers" },
446     { "TIME",         "%d regels (%d dagen) verwerkt in %d seconden" },
447     { "FOOTER",       "Statistieken gegenereerd door" },
448     { "C_SMILE",      "is vaak vrolijk :)" },
449     { "C_FROWN",      "is vaak droevig :(" },
450     { "C_EXCLAM",     "schreeuwt veel !" },
451     { "C_QUESTION",   "stelt veel vragen ?" },
452     { "C_ME",         "vindt /me een leuk commando" },
453     { "C_TOPIC",      "verandert vaak de topic" },
454     { "C_MODE",       "verandert vaak de modes" },
455     { "C_KICK",       "vindt /kick erg leuk" },
456     { "C_KICKED",     "wordt vaak gekickt" },
457     { "C_URL",        "plaatst veel URLs" },
458     { "C_JOIN",       "twijfelt tussen blijven of gaan" },
459     { "C_NICK",       "verandert vaak van nick" },
460     { "C_MONOLOGUE",  "spreekt veel monologen" }
461   },
462   { /* Russian language */
463     /* contributed by kamikaze <kamikaze@rss.lv> */
464     { "Russian",      "ru" },
465     { "CHARSET",      "KOI8-R" },
466     { "HEADER",       "óÔÁÔÉÓÔÉËÁ ÄÌÑ %s ÏÔ %s" },
467     { "LEGEND",       "ïÂÏÚÎÁÞÅÎÉÑ" },
468     { "LASTDAYS",     "óÔÁÔÉÓÔÉËÁ ÐÏÓÌÅÄÎÉÈ ÄÎÅÊ" },
469     { "TOPHOURS",     "ðÏÞÁÓÏ×ÁÑ ÓÔÁÔÉÓÔÉËÁ" },
470     { "TOPUSERS",     "áËÔÉ×ÎÅÊÛÉÅ ÌÀÄÉ" },
471     { "OTHERS",       "ïÓÔÁÌÏÓØ %d ÎÅÐÏÄÓÞÉÔÁÎÙÈ..." },
472     { "NBLINES",      "ÓÔÒÏËÉ" },
473     { "NICK",         "ÎÉË" },
474     { "AVGLETTERS",   "ÂÕË×Ù/ÓÔÒÏËÉ" },
475     { "HOURS",        "ÞÁÓÙ" },
476     { "QUOTE",        "ÓÌÕÞÁÊÎÏÅ ÓÏÏÂÝÅÎÉÅ" },
477     { "TOPUSERSTIME", "áËÔÉ×ÎÅÊÛÉÅ ÌÀÄÉ ÐÏ ×ÒÅÍÅÎÉ ÄÎÑ" },
478     { "RANDTOPICS",   "îÅÓËÏÌØËÏ ÔÏÐÉËÏ×" },
479     { "CHANGEDBY",    "ÉÚÍÅΣÎ" },
480     { "NEWTOPIC",     "ÎÏ×ÙÊ ÔÏÐÉË" },
481     { "RANDURLS",     "îÅÓËÏÌØËÏ URLÏ×" },
482     { "POSTEDBY",     "ÏÐÕÂÌÉËÏ×ÁÌ" },
483     { "POSTEDURL",    "URL" },
484     { "TOPWORDS",     "þÁÓÔÏ ÉÓÐÏÌØÚÕÅÍÙÅ ÓÌÏ×Á" },
485     { "WORD",         "ÓÌÏ×Ï" },
486     { "OCCURRENCES",  "ÐÒÏÉÛÅÓÔ×ÉÑ" },
487     { "BIGNUMBERS",   "îÅÓËÏÌØËÏ ÂÏÌØÛÉÈ ÞÉÓÅÌ..." },
488     { "NUMBERS",      "ÞÉÓÌÁ" },
489     { "TIME",         "%d ÓÔÒÏË (%d ÄÎÅÊ) ÏÂÒÁÂÏÔÁÎÏ ÚÁ %d ÓÅËÕÎÄ" },
490     { "FOOTER",       "óÔÁÔÉÓÔÉËÁ ÓÇÅÎÅÒÉÒÏ×ÁÎÁ" },
491     { "C_SMILE",      "ÞÁÓÔÏ ÓÞÁÓÌÉ× :)" },
492     { "C_FROWN",      "ÞÁÓÔÏ ÎÅÓÞÁÓÔÅΠ:(" },
493     { "C_EXCLAM",     "ÍÎÏÇÏ ×ÏÓËÌÉÃÁÅÔ !" },
494     { "C_QUESTION",   "ÚÁÄÁ£Ô ÍÎÏÇÏ ×ÏÐÒÏÓÏ× ?" },
495     { "C_ME",         "ÌÀÂÉÔ /me command" },
496     { "C_TOPIC",      "ÞÁÓÔÏ ÍÅÎÑÅÔ ÔÏÐÉË" },
497     { "C_MODE",       "ÞÁÓÔÏ ÍÅÎÑÅÔ ÒÅÖÉÍÙ" },
498     { "C_KICK",       "ÌÀÂÉÔ /kick" },
499     { "C_KICKED",     "ÞÁÓÔÏ ×ÙËÉÄÙ×ÁÀÔ" },
500     { "C_URL",        "ÐÕÂÌÉËÕÅÔ ÍÎÏÇÏ URLÏ×" },
501     { "C_JOIN",       "ÎÅ ÚÎÁÅÔ - ÏÓÔÁÔØÓÑ ÉÌÉ ÕÊÔÉ" },
502     { "C_NICK",       "ÞÁÓÔÏ ÍÅÎÑÅÔ Ó×ÏÊ ÎÉË" },
503     { "C_MONOLOGUE",  "éÓÐÏÌØÚÕÅÔ ÍÎÏÇÏ ÍÏÎÏÌÏÇÏ×" }
504   },
505   { /* Estonian language */
506     /* contributed by Martin Vool <mardicas@hot.ee> */
507     { "Estonian",     "et" },
508     { "CHARSET",      "ISO-8859-4" },
509     { "HEADER",       "Statistika kanalile %s on koostanud %s" },
510     { "LEGEND",       "Legend" },
511     { "LASTDAYS",     "Viimaste päevade statistika" },
512     { "TOPHOURS",     "Tunni statistika" },
513     { "TOPUSERS",     "Kõige aktiivsemad inimesed" },
514     { "OTHERS",       "%d inimest on rääkinud" },
515     { "NBLINES",      "rida" },
516     { "NICK",         "nimi" },
517     { "AVGLETTERS",   "tähte/rida" },
518     { "HOURS",        "kell" },
519     { "QUOTE",        "suvaline teade" },
520     { "TOPUSERSTIME", "Kõige aktiivsemad inimesed päeva aja järgi" },
521     { "RANDTOPICS",   "Mõned topicud" },
522     { "CHANGEDBY",    "muutis" },
523     { "NEWTOPIC",     "topicud" },
524     { "RANDURLS",     "Mõned aadressid" },
525     { "POSTEDBY",     "postitas" },
526     { "POSTEDURL",    "URL" },
527     { "TOPWORDS",     "Enim kasutatud sõnad" },
528     { "WORD",         "sõna" },
529     { "OCCURRENCES",  "sagedus" },
530     { "BIGNUMBERS",   "Mõned suured numbrid" },
531     { "NUMBERS",      "iseloom" },
532     { "TIME",         "%d rida (%d päeva) on möödunud %d sekundit" },
533     { "FOOTER",       "Statistika on koostanud" },
534     { "C_SMILE",      "on tihti õnnelik :)" },
535     { "C_FROWN",      "on tihti kurb :(" },
536     { "C_EXCLAM",     "põrnitseb palju" },
537     { "C_QUESTION",   "küsib palju küsimusi" },
538     { "C_ME",         "/me manjakk" },
539     { "C_TOPIC",      "vahetab tihti topicut" },
540     { "C_MODE",       "vahetab tihti modesid" },
541     { "C_KICK",       "kickib palju" },
542     { "C_KICKED",     "saab tihti kicke" },
543     { "C_URL",        "reklaamib palju" },
544     { "C_JOIN",       "sõelub sisse ja välja" },
545     { "C_NICK",       "vahetab pidevalt nime" },
546     { "C_MONOLOGUE",  "räägib palju monolooge" }
547   }
548 };
549
550 int language=0; /* default to english */
551
552 char *L(char *key)
553 {
554   int i;
555   for (i=1;i<=NBKEYS;i++) if (strcmp(key,keys[language][i][0])==0) return(keys[language][i][1]);
556   fprintf(stderr,"unknown language key: %s\n",key);
557   return("");
558 }
559
560 /* Variables */
561
562 int debug=1; /* 0 = none ; 1 = normal ; 2 = verbose */
563 char channel[MAXLINELENGTH]="set_channel_in_config_file";
564 char maintainer[MAXLINELENGTH]="set_maintainer_in_config_file";
565 char theme[MAXLINELENGTH]="default,biseau,blue,dark,damier,grayscale,namour,niflheim,pisg,zeduel,zerezo";
566 int refresh_time=0; /* 0 = disabled */
567 int w3c_link=1; /* 0 = disabled */
568 int logo=1; /* 0 = disabled */
569 char header[MAXLINELENGTH]="none";
570 char footer[MAXLINELENGTH]="none";
571 int totallines=0;
572 time_t debut;
573 int top_words=1; /* 0 = disabled */
574 int ranking=0; /* 0 = lines ; 1 = words ; 2 = letters */
575 int quarter=0; /* 1 = enabled */
576 int photo_size=60;
577
578 struct user
579 {
580   char nick[MAXNICKLENGTH];
581   int lines;
582   int words;
583   int letters;
584   int hours[4];
585   char quote[MAXQUOTELENGTH+1];
586   int counters[NBCOUNTERS];
587   char *photo;
588   int temp;
589 } *users;
590 int nbusers=0;
591 int maxusers=BASEUSERS;
592
593 struct
594 {
595   char nick[MAXNICKLENGTH];
596   char url[MAXLINELENGTH];
597   char shorturl[MAXQUOTELENGTH+1];
598 } urls[NBURLS];
599 int nburls=0;
600
601 struct
602
603   char nick[MAXNICKLENGTH];
604   char topic[MAXQUOTELENGTH+1];
605 } topics[NBTOPICS];
606 int nbtopics=0;
607
608 struct
609 {
610   int lines;
611   int hours[4];
612 } lastdays[31];
613 int days=0;
614
615 int hours[24*4];
616 int lines=0;
617
618 struct letter
619 {
620   int nb;
621   struct letter *next[26];
622 } words;
623
624 struct
625 {
626   int nb;
627   char word[MAXLINELENGTH];
628 } topwords[NBWORDS];
629
630 #define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
631 #define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
632 int findwords(char *message)
633 {
634   int i,c,n=0;
635   struct letter *pos,*tmp;
636   for (;;)
637   {
638     while (!isletter(*message)) if (*message=='\0') return n; else message++;
639     pos=&words;
640     while (isletter(*message))
641     {
642       c=lowercase(*message)-'a';
643       if (pos->next[(int)c]==NULL)
644       {
645         tmp=malloc(sizeof(struct letter));
646         if (tmp==NULL)
647         {
648           fprintf(stderr, "findwords(): malloc failure\n");
649           exit(1);
650         }
651         tmp->nb=0;
652         for (i=0;i<26;i++) tmp->next[i]=NULL;
653         pos->next[(int)c]=tmp;
654       }
655       pos=pos->next[(int)c];
656       message++; 
657     }
658     pos->nb++;
659     n++;
660   }
661   return n;
662 }
663
664 char tempword[MAXLINELENGTH];
665 void bestwords(struct letter pos,int cur)
666 {
667   int i,j;
668   if ((cur>=MINWORDLENGTH)&&(pos.nb>topwords[NBWORDS-1].nb))
669   {
670     for (i=0;pos.nb<topwords[i].nb;i++);
671     for (j=NBWORDS-1;j>i;j--)
672     {
673       topwords[j].nb=topwords[j-1].nb;
674       strcpy(topwords[j].word,topwords[j-1].word);
675     }
676     topwords[i].nb=pos.nb;
677     strcpy(topwords[i].word,tempword);
678   }
679   for (i=0;i<26;i++) if (pos.next[i]!=NULL)
680   {
681     tempword[cur]='a'+i;
682     bestwords(*(pos.next[i]),cur+1);
683   }
684   tempword[cur]='\0';
685 }
686
687 void freewords(struct letter *pos)
688 {
689   int i;
690   for (i=0;i<26;i++) if (pos->next[i]!=NULL)
691   {
692     freewords(pos->next[i]);
693     free(pos->next[i]);
694     (*pos).next[i]=NULL;
695   }
696 }
697
698 void printhtml(FILE *fic,char *string) /* replace < and > by &lt; and &gt; */
699 {
700   while (*string!='\0')
701   {
702     switch (*string)
703     {
704       case '<':fprintf(fic,"&lt;"); break;
705       case '>':fprintf(fic,"&gt;"); break;
706       case '&':fprintf(fic,"&amp;"); break;
707       default:fprintf(fic,"%c",*string); break;
708     }
709     string++;
710   }
711   return;
712 }
713
714 int dichotomic(char *nick)
715 {
716   int i,j,start=0,end=nbusers-1,middle;
717   while (start<=end)
718   {
719     middle=(start+end)/2;
720     if (strcmp(nick,users[middle].nick)>0) start=middle+1; else end=middle-1;
721   }
722   if (strcmp(nick,users[start].nick)!=0)
723   {
724     nbusers++;
725     if (nbusers>=maxusers)
726     {
727       maxusers+=BASEUSERS;
728       if (debug==2) fprintf(stderr,"allocating more users : %d\n",maxusers);
729       if ((users=realloc(users,maxusers*sizeof(struct user)))==NULL) { fprintf(stderr,"too many users : unable to realloc memory\n"); exit(1); }
730     }
731     for (i=nbusers-1;i>start;i--)
732     {
733       strcpy(users[i].nick,users[i-1].nick);
734       users[i].lines=users[i-1].lines;
735       users[i].words=users[i-1].words;
736       users[i].letters=users[i-1].letters;
737       for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
738       strcpy(users[i].quote,users[i-1].quote);
739       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=users[i-1].counters[j];
740       users[i].temp=users[i-1].temp;
741       users[i].photo=users[i-1].photo;
742     }
743     strcpy(users[start].nick,nick);
744     users[start].lines=0;
745     users[start].words=0;
746     users[start].letters=0;
747     for (j=0;j<4;j++) users[start].hours[j]=0;
748     users[start].quote[0]='\0';
749     for (j=0;j<NBCOUNTERS;j++) users[start].counters[j]=0;
750     users[start].temp=0;
751     users[start].photo=NULL;
752   }
753   return(start);
754 }
755
756 void parse_log(char *logfile)
757 {
758   FILE *fic;
759   char line[MAXLINELENGTH];
760   int pos;
761   int i,j;
762   char *nick,*message;
763   int nickstart;
764   int mononick,monolines;
765   int temp,hour;
766
767   if ((fic=fopen(logfile,"rt"))==NULL) { fprintf(stderr,"can't open log file \"%s\"\n",logfile); exit(1); }
768   if (debug) printf("working on %s : ",channel);
769   while (fgets(line,MAXLINELENGTH,fic)!=NULL)
770   {
771     /* remove \n */
772     for (i=0;line[i]!=0 && i<MAXLINELENGTH-1;i++);
773     if (i>=MAXLINELENGTH-1) { 
774       if(debug){
775         fprintf(stderr,"line %d is too long, skipping\n",totallines+1); 
776       }
777       continue; 
778     }
779     if (i<8) {
780       if(debug) {
781         fprintf(stderr, "line %d is too short to be valid, skipping\n",totallines+1);
782       }
783       continue;
784     }
785     line[i-1]='\0';
786     pos=0;
787     totallines++;
788     if (totallines%10000==0 && debug) { printf("."); fflush(stdout); }
789     if (strncmp("--- Day changed",line,15)==0) /* --- Day changed Wed May 01 2002 */
790     {
791       for (i=30;i>0;i--)
792       {
793         lastdays[i].lines=lastdays[i-1].lines;
794         for (j=0;j<4;j++) lastdays[i].hours[j]=lastdays[i-1].hours[j];
795       }
796       lastdays[0].lines=0;
797       for (j=0;j<4;j++) lastdays[0].hours[j]=0;
798       days++;
799     }
800     else if (strncmp("-!- mode/",&line[6],9)==0) /* 00:00 -!- mode/#channel [...] by (Nick, Nick2, )Nick3 */
801     {
802       for (i=strlen(line);line[i]!=' ';i--);
803       nick=&line[i+1];
804       users[dichotomic(nick)].counters[D_MODE]++;
805     }
806     else if (strncmp("-!-",&line[6],3)==0) /* 00:00 -!- Nick something... */
807     {
808       for (i=10;line[i]!=' ' && i <= 10 + MAXNICKLENGTH;i++);
809       if(i > 10 + MAXNICKLENGTH) {
810         if(debug) {
811           fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
812         }
813         continue;
814       }
815       line[i]='\0';
816       nick=&line[10];
817       message=&line[i+1];
818       if (strncmp("changed the topic of",message,20)==0) /* 00:00 -!- Nick changed the topic of #channel to: new topic */
819       {
820         users[dichotomic(nick)].counters[D_TOPIC]++;
821         for (i=21;message[i]!=':';i++);
822         message=&message[i+2];
823         nbtopics++;
824         if ((nbtopics<=NBTOPICS) || (rand()%(nbtopics/NBTOPICS)==0))
825         {
826           temp=nbtopics<=NBTOPICS?nbtopics-1:rand()%NBTOPICS;
827           strcpy(topics[temp].nick,nick);
828           strncpy(topics[temp].topic,message,MAXQUOTELENGTH);
829         }
830       }
831       else if (strncmp("was kicked from",message,15)==0) /* 00:00 -!- Nick was kicked from #channel by Nick [Reason] */
832       {
833         users[dichotomic(nick)].counters[D_KICKED]++;
834         for (i=16;message[i]!=' ';i++);
835         message=&message[i+4];
836         for (i=0;message[i]!=' ';i++);
837         message[i]='\0';
838         users[dichotomic(message)].counters[D_KICK]++;
839       }
840       else if (strncmp("is now known as",message,15)==0) /* 00:00 -!- Nick is now known as Nick */
841         users[dichotomic(nick)].counters[D_NICK]++;
842       else if (message[0]=='[') /* 00:00 -!- Nick [user@host] something... */
843       {
844         for (i=0;message[i]!=']';i++);
845         message=&message[i+2];
846         if (strncmp("has joined",message,10)==0) /* 00:00 -!- Nick [user@host] has joined #channel */
847           users[dichotomic(nick)].counters[D_JOIN]++;
848         else if (strncmp("has quit",message,8)==0); /* 00:00 -!- Nick [user@host] has quit [Reason] */
849         else if (strncmp("has left",message,8)==0); /* 00:00 -!- Nick [user@host] has left #channel [Reason] */
850         else;
851       }
852     }
853     else if ((line[6]=='<') || (line[7]=='*'))
854     {
855       line[2]='\0';
856       hour=atoi(line);
857       if (line[7]=='*') /* 00:00  * Nick the message */
858       {
859         for (i=9;line[i]!=' ' && i <= 9 + MAXNICKLENGTH;i++);
860         if(i > 9 + MAXNICKLENGTH) {
861           if(debug) {
862             fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
863           }
864           continue; 
865         }
866         nick=&line[9];
867         message=&line[i+1];
868       }
869       else if (line[7]=='>') /* 00:00 <>>>?Nick<<<> the personal message */
870                              /* 00:00 <>>?Nick<<> the personal message */
871       {
872         for (i=10;line[i]!='<' && i <= 10 + MAXNICKLENGTH;i++);
873          if(i > 10 + MAXNICKLENGTH) { 
874           if(debug) {
875             fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
876           }
877           continue; 
878         }
879         nick=&line[10];
880         if (line[9]=='>') nick++;
881         message=&line[i+5];
882       }
883       else /* 00:00 <?Nick> the message */
884       {
885
886         /* 
887          * Irssi doesn't log channel mode with show_nickmode = OFF    
888          * the following covers op, half-op, voice and show_nickmode_empty                 
889          */
890         if (line[7]=='@' || line[7]=='%' || line[7]=='+' || line[7]==' ') {
891             nickstart = 8;
892         } else {
893             nickstart = 7;
894         }
895           
896         for (i=nickstart;line[i]!='>' && i <= nickstart + MAXNICKLENGTH;i++);
897         if(i > nickstart + MAXNICKLENGTH) { 
898           if(debug) {
899             fprintf(stderr,"nick on line %d is too long, skipping line\n",totallines); 
900           }
901           continue; 
902         }
903         nick=&line[nickstart];
904         message=&line[i+2];
905       }
906       line[i]='\0';
907       i=dichotomic(nick);
908       if (line[7]=='*') users[i].counters[D_ME]++;
909       if (i==mononick)
910       {
911         monolines++;
912         if (monolines==5) users[i].counters[D_MONOLOGUE]++;
913       }
914       else
915       {
916         mononick=i;
917         monolines=1;
918       }
919       j=strlen(message);
920       users[i].lines++;
921       if (top_words || ranking==1) users[i].words+=findwords(message);
922       users[i].letters+=j;
923       users[i].hours[hour/6]++;
924       lastdays[0].lines++;
925       lastdays[0].hours[hour/6]++;
926       lines++;
927       if (quarter)
928       {
929         line[5]='\0';
930         hour=hour*4+atoi(&line[3])/15;
931       }
932       hours[hour]++;
933       if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
934       else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
935       else if ((message[j-3]==' ')&&(message[j-2]==':'))
936       {
937         if (message[j-1]==')') users[i].counters[D_SMILE]++;
938         else if (message[j-1]=='(') users[i].counters[D_FROWN]++;
939       }
940       if (rand()%users[i].lines==0) strncpy(users[i].quote,message,MAXQUOTELENGTH);
941       if (strncmp("http://",message,7)==0)
942       {
943         users[i].counters[D_URL]++;
944         for (i=0;(message[i]!=' ') && (i<strlen(message));i++);
945         message[i]='\0';
946         nburls++;
947         if ((nburls<=NBURLS) || (rand()%(nburls/NBURLS)==0))
948         {
949           temp=nburls<=NBURLS?nburls-1:rand()%NBURLS;
950           strcpy(urls[temp].nick,nick);
951           strcpy(urls[temp].url,message);
952           strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
953         }
954       }
955     }
956     pos=0;
957   }
958   fclose(fic);
959   if (debug) printf(" done\n");
960 }
961
962 #ifndef __WIN32__
963 void parse_nick(char *nickfile)
964 {
965   FILE *fic;
966   int i,j;
967   regex_t preg;
968   char line[MAXLINELENGTH];
969   int user;
970   
971   for (i=0;i<nbusers;i++) users[i].temp=users[i].lines;
972   if ((fic=fopen(nickfile,"rt"))==NULL) { fprintf(stderr,"can't open nick file \"%s\"\n",nickfile); exit(1); }
973   while (fscanf(fic,"%s",line)==1)
974   {
975     user=dichotomic(line);
976     fscanf(fic,"%s",line);
977     if (regcomp(&preg,line,0)!=0) { fprintf(stderr,"error in nick file"); exit(1); }
978     for (i=0;i<nbusers;i++) if ((i!=user) && (regexec(&preg,users[i].nick,0,0,0)==0) && (users[i].lines>=0))
979     {
980       if (users[i].temp>users[user].temp) /* for nick alias, keep the random quote of the most used nick */
981       {
982         strcpy(users[user].quote,users[i].quote);
983         users[user].temp=users[i].temp;
984       }
985       users[user].lines+=users[i].lines;
986       users[user].words+=users[i].words;
987       users[user].letters+=users[i].letters;
988       for (j=0;j<4;j++) users[user].hours[j]+=users[i].hours[j];
989       for (j=0;j<NBCOUNTERS;j++) users[user].counters[j]+=users[i].counters[j];
990       /* "remove" old user */
991       users[i].lines=-1;
992       users[i].words=-1;
993       users[i].letters=-1;
994       for (j=0;j<4;j++) users[i].hours[j]=-1;
995       for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
996     }
997     regfree(&preg);
998   }
999   fclose(fic);
1000   /* "remove" the ignored nicks */
1001   i=dichotomic("<NULL>");
1002   users[i].lines=-1;
1003   users[i].words=-1;
1004   users[i].letters=-1;
1005   for (j=0;j<4;j++) users[i].hours[j]=-1;
1006   for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
1007 }
1008 #endif
1009
1010 void parse_photo(char *photofile)
1011 {
1012   FILE *fic;
1013   char line[MAXLINELENGTH];
1014   int user;
1015   
1016   if ((fic=fopen(photofile,"rt"))==NULL) { fprintf(stderr,"can't open photo file \"%s\"\n",photofile); exit(1); }
1017   while (fscanf(fic,"%s",line)==1)
1018   {
1019     user=dichotomic(line);
1020     fscanf(fic,"%s",line);
1021     users[user].photo=malloc(strlen(line)+1);
1022     strcpy(users[user].photo,line);
1023   }
1024   fclose(fic);
1025 }
1026
1027 void gen_xhtml(char *xhtmlfile)
1028 {
1029   FILE *fic;
1030   FILE *sfic;
1031   int i,j,k;
1032   int user,max,temp;
1033   char line[MAXLINELENGTH];
1034   char *subtheme;
1035   int photos=0;
1036   
1037   for (i=0;i<nbusers;i++) if (users[i].photo!=NULL) photos=1;
1038   
1039   if ((fic=fopen(xhtmlfile,"wt"))==NULL) { fprintf(stderr,"can't open xhtml file \"%s\"\n",xhtmlfile); exit(1); }
1040   
1041   /* header */
1042   if (strcmp("none",header)==0)
1043   {
1044     fprintf(fic,"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n");
1045     fprintf(fic,"<!-- Generated by irssistats %s : %s -->\n\n",VERSION,URL);
1046     fprintf(fic,"<html>\n\n<head>\n<title>");
1047     fprintf(fic,L("HEADER"),channel,maintainer);
1048     fprintf(fic,"</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n",L("CHARSET"));
1049     if (refresh_time)
1050       fprintf(fic,"<meta http-equiv=\"Refresh\" content=\"%d\" />\n",refresh_time);
1051     subtheme=strtok(theme,",");
1052     fprintf(fic,"<link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\" title=\"%s\" />\n",subtheme,subtheme);
1053     while ((subtheme=strtok(NULL,","))!=NULL)
1054       fprintf(fic,"<link rel=\"alternate stylesheet\" type=\"text/css\" href=\"%s.css\" title=\"%s\" />\n",subtheme,subtheme);
1055     fprintf(fic,"</head>\n\n");
1056     fprintf(fic,"<body>\n\n");
1057   }
1058   else
1059   {
1060     if ((sfic=fopen(header,"rt"))==NULL) { fprintf(stderr,"can't open header file \"%s\"\n",header); exit(1); }
1061     while ((temp=fread(line,1,MAXLINELENGTH,sfic))) fwrite(line,temp,1,fic);
1062     fclose(sfic);
1063   }
1064   fprintf(fic,"<div id=\"irssistats\">\n\n<div id=\"irssistats_header\">\n<h1>");
1065   fprintf(fic,L("HEADER"),channel,maintainer);
1066   fprintf(fic,"</h1>\n<p>\n%s</p>\n</div>\n\n",ctime(&debut));
1067
1068   /* legend */
1069   fprintf(fic,"<div id=\"irssistats_legend\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LEGEND"));
1070   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);
1071   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
1072   
1073   /* last days */
1074   fprintf(fic,"<div id=\"irssistats_lastdays\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("LASTDAYS"));
1075   max=-1;
1076   for (i=30;i>=0;i--) if (lastdays[i].lines>max) max=lastdays[i].lines;
1077   for (i=30;i>=0;i--)
1078   {
1079     fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%d</small>",lastdays[i].lines);
1080     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);
1081     fprintf(fic,"</td>\n");
1082   }
1083   fprintf(fic,"</tr>\n<tr>\n");
1084   for (i=30;i>=0;i--)
1085     fprintf(fic,"<th>%d</th>\n",i);
1086   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
1087   
1088   /* top hours */
1089   fprintf(fic,"<div id=\"irssistats_tophours\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("TOPHOURS"));
1090   max=-1;
1091   for (i=0;i<24*4;i++) if (hours[i]>max) max=hours[i];
1092   if (quarter) for (i=0;i<24*4;i++)
1093   {
1094     fprintf(fic,"<td align=\"center\" valign=\"bottom\">");
1095     if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"width:4px;height:%dpx\"></div>",i/4/6+1,150*hours[i]/max);
1096     fprintf(fic,"</td>\n");
1097   }
1098   else for (i=0;i<24;i++)
1099   {
1100     fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%.1f%%</small>",lines!=0?(float)100*hours[i]/lines:0);
1101     if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",i/6+1,150*hours[i]/max);
1102     fprintf(fic,"</td>\n");
1103   }
1104   fprintf(fic,"</tr>\n<tr>\n");
1105   for (i=0;i<24;i++)
1106     if (quarter) fprintf(fic,"<th colspan=\"4\">%d</th>\n",i);
1107     else fprintf(fic,"<th>%d</th>\n",i);
1108   fprintf(fic,"</tr>\n</table>\n</div>\n\n");
1109   
1110   /* top users */
1111   fprintf(fic,"<div id=\"irssistats_topusers\">\n<h2>%s</h2>\n",L("TOPUSERS"));
1112   switch (ranking)
1113   {
1114     case 0:
1115       fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th><th>%s</th><th colspan=\"2\">%s</th><th>%s</th>\n",L("NICK"),L("NBLINES"),L("HOURS"),L("AVGLETTERS"),L("QUOTE"));
1116       break;
1117     default:
1118       /* "letters" and "words" ranking are not yet translated so we use the generic word "rank" ... */
1119       fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th><th>%s</th><th colspan=\"2\">%s</th><th>%s</th>\n",L("NICK"),"rank",L("HOURS"),L("AVGLETTERS"),L("QUOTE"));
1120       break;
1121   }
1122   if (photos) fprintf(fic,"<th></th>");
1123   fprintf(fic,"</tr>");
1124   for (i=1;i<=NBUSERS;i++)
1125   {
1126     user=-1;
1127     max=0;
1128     switch (ranking)
1129     {
1130       case 0:
1131         for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
1132         break;
1133       case 1:
1134         for (j=0;j<nbusers;j++) if (users[j].words>max) max=users[user=j].words;
1135         break;
1136       case 2:
1137         for (j=0;j<nbusers;j++) if (users[j].letters>max) max=users[user=j].letters;
1138         break;
1139     }
1140     if (user!=-1)
1141     {
1142       switch (ranking)
1143       {
1144         case 0:
1145           fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].lines);
1146           break;
1147         case 1:
1148           fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].words);
1149           break;
1150         case 2:
1151           fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].letters);
1152           break;
1153       }
1154       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);
1155       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);
1156       printhtml(fic,users[user].quote);
1157       fprintf(fic,"\"</td>");
1158       if (photos && users[user].photo!=NULL)
1159       {
1160         if (photo_size)
1161           fprintf(fic,"<td class=\"tdphoto\"><a href=\"%s\"><img src=\"%s\" class=\"imgphoto\" width=\"%d\" height=\"%d\" alt=\"\" style=\"border: 0\" /></a></td>",users[user].photo,users[user].photo,photo_size,photo_size);
1162         else
1163           fprintf(fic,"<td class=\"tdphoto\"><img src=\"%s\" class=\"imgphoto\" alt=\"\" /></td>",users[user].photo,users[user].photo);
1164       }
1165       fprintf(fic,"</tr>\n");
1166       users[user].lines=-1;
1167       users[user].words=-1;
1168       users[user].letters=-1;
1169     }    
1170   }
1171   fprintf(fic,"</table>\n");
1172   temp=0;
1173   for (i=0;i<=nbusers;i++) if (users[i].lines>=0) temp++;
1174   if (temp>0)
1175   {
1176     fprintf(fic,"<p>");
1177     fprintf(fic,L("OTHERS"),temp);
1178     fprintf(fic,"</p>\n");
1179   }
1180   fprintf(fic,"</div>\n\n");
1181   
1182   /* top users by time */
1183   fprintf(fic,"<div id=\"irssistats_topuserstime\">\n<h2>%s</h2>\n",L("TOPUSERSTIME"));
1184   fprintf(fic,"<table>\n<tr><th></th>");
1185   for (i=0;i<4;i++) fprintf(fic,"<th colspan=\"2\">%s %d-%d</th>",L("HOURS"),i*6,i*6+5);
1186   fprintf(fic,"</tr>\n");
1187   for (i=1;i<=NBUSERSTIME;i++)
1188   {
1189     fprintf(fic,"<tr><td>%d</td>",i);
1190     for (j=0;j<4;j++)
1191     {
1192       user=-1;
1193       max=0;
1194       for (k=0;k<nbusers;k++) if (users[k].hours[j]>max) max=users[user=k].hours[j];
1195       if (user!=-1)
1196       {
1197         fprintf(fic,"<td>%s</td><td>%d</td>",users[user].nick,users[user].hours[j]);
1198         users[user].hours[j]=-1;
1199       }
1200       else fprintf(fic,"<td></td><td></td>");
1201     }
1202     fprintf(fic,"</tr>\n");
1203   }
1204   fprintf(fic,"</table>\n</div>\n\n");
1205
1206   /* random topics */
1207   fprintf(fic,"<div id=\"irssistats_randtopics\">\n<h2>%s</h2>\n",L("RANDTOPICS"));
1208   fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th></tr>\n",L("CHANGEDBY"),L("NEWTOPIC"));
1209   for (i=nbtopics<NBTOPICS?nbtopics-1:NBTOPICS-1;i>=0;i--)
1210   {
1211     fprintf(fic,"<tr><td>%s</td><td>\"",topics[i].nick);
1212     printhtml(fic,topics[i].topic);
1213     fprintf(fic,"\"</td></tr>\n");
1214   }
1215   fprintf(fic,"</table>\n</div>\n\n");
1216   
1217   /* random urls */
1218   fprintf(fic,"<div id=\"irssistats_randurls\">\n<h2>%s</h2>\n",L("RANDURLS"));
1219   fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th></tr>\n",L("POSTEDBY"),L("POSTEDURL"));
1220   for (i=nburls<NBURLS?nburls-1:NBURLS-1;i>=0;i--)
1221   {
1222     fprintf(fic,"<tr><td>%s</td><td>\"<a href=\"",urls[i].nick);
1223     printhtml(fic,urls[i].url);
1224     fprintf(fic,"\">");
1225     printhtml(fic,urls[i].shorturl);
1226     fprintf(fic,"</a>\"</td></tr>\n");
1227   }
1228   fprintf(fic,"</table>\n</div>\n\n");
1229   
1230   /* top words */
1231   if (top_words)
1232   {
1233     fprintf(fic,"<div id=\"irssistats_topwords\">\n<h2>%s</h2>\n",L("TOPWORDS"));
1234     fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th></tr>\n",L("WORD"),L("OCCURRENCES"));
1235     for (i=0;i<NBWORDS;i++)
1236       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);
1237     fprintf(fic,"</table>\n</div>\n\n");
1238   }
1239   
1240   /* big numbers */
1241   fprintf(fic,"<div id=\"irssistats_bignumbers\">\n<h2>%s</h2>\n",L("BIGNUMBERS"));
1242   fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th><th>%s</th></tr>\n",L("NICK"),L("NUMBERS"),L("NBLINES"));
1243   for (i=0;i<NBCOUNTERS;i++)
1244   {
1245     user=-1;
1246     max=0;
1247     for (j=0;j<nbusers;j++) if (users[j].counters[i]>max) max=users[user=j].counters[i];
1248     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]);
1249   }
1250   fprintf(fic,"</table>\n</div>\n\n");
1251   
1252   /* footer */
1253   fprintf(fic,"<div id=\"irssistats_footer\">\n<p>");
1254   fprintf(fic,L("TIME"),totallines,days,(int)(time(NULL)-debut));
1255   fprintf(fic,"</p>\n<p>%s <a href=\"%s\">irssistats %s</a></p>\n",L("FOOTER"),URL,VERSION);
1256   if (w3c_link)
1257   {
1258     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");
1259     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");
1260   }
1261   fprintf(fic,"</div>\n\n");
1262   
1263   /* logo*/
1264   if (logo) fprintf(fic,"<div class=\"logo\"></div>\n\n");
1265   
1266   /* end */
1267   if (strcmp("none",footer)==0)
1268   {
1269     fprintf(fic,"</div>\n\n</body>\n\n</html>\n");
1270   }
1271   else
1272   {
1273     if ((sfic=fopen(footer,"rt"))==NULL) { fprintf(stderr,"can't open footer file \"%s\"\n",footer); exit(1); }
1274     while ((temp=fread(line,1,MAXLINELENGTH,sfic))) fwrite(line,temp,1,fic);
1275     fclose(sfic);
1276   }
1277   
1278   fclose(fic);
1279 }
1280
1281 void parse_config(char *configfile)
1282 {
1283   void expand(char *path)
1284   {
1285     char temp[MAXLINELENGTH];
1286     if (*path=='~')
1287     {
1288       snprintf(temp,MAXLINELENGTH-1,"%s%s",getenv("HOME"),path+1);
1289       temp[MAXLINELENGTH-1]='\0';
1290       strcpy(path,temp);
1291     }
1292   }
1293   
1294   FILE *fic;
1295   char line[MAXLINELENGTH];
1296   char keyword[MAXLINELENGTH];
1297   char value[MAXLINELENGTH];
1298   int configlines=0;
1299   int i;
1300   
1301   if (configfile!=NULL)
1302   {
1303     if ((fic=fopen(configfile,"rt"))==NULL)
1304     {
1305       fprintf(stderr,"can't open config file : \"%s\"\n",configfile);
1306       exit(1);
1307     }
1308   }
1309   else
1310   {
1311     snprintf(line,MAXLINELENGTH-1,"%s/.irssistats",getenv("HOME"));
1312     line[MAXLINELENGTH-1]='\0';
1313     if ((fic=fopen(line,"rt"))==NULL)
1314       if ((fic=fopen(GLOBALCONF,"rt"))==NULL)
1315       {
1316         fprintf(stderr,"can't find config file : \"%s\" nor \"" GLOBALCONF "\"\n",line);
1317         fprintf(stderr,"please give the path to the config file in argument\n");
1318         exit(1);
1319       }
1320   }
1321
1322   while (fgets(line,MAXLINELENGTH,fic))
1323   {
1324     configlines++;
1325     if (*line!=';' && *line!='#' && *line!='/' && *line!='-' && *line!='\n')
1326     {
1327       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); }
1328       
1329       if (strcmp("debug",keyword)==0)
1330       {
1331         if (strcmp("none",value)==0) debug=0;
1332         else
1333         if (strcmp("normal",value)==0) debug=1;
1334         else
1335         if (strcmp("verbose",value)==0) { debug=2; fprintf(stderr,"switching to verbose output\n"); }
1336         else { fprintf(stderr,"unknown value for \"debug\" option, must be \"normal\", \"verbose\" or \"none\"\n"); exit(1); }
1337       }
1338       else
1339       
1340       if (strcmp("channel",keyword)==0)
1341       {
1342         if (debug==2) fprintf(stderr,"setting channel name to \"%s\"\n",value);
1343         strcpy(channel,value);
1344       }
1345       else
1346       
1347       if (strcmp("maintainer",keyword)==0)
1348       {
1349         if (debug==2) fprintf(stderr,"setting maintainer to \"%s\"\n",value);
1350         strcpy(maintainer,value);
1351       }
1352       else
1353       
1354       if (strcmp("language",keyword)==0)
1355       {
1356         if (debug==2) fprintf(stderr,"setting language to \"%s\"\n",value);
1357         for (i=0;i<NBLANGUAGES;i++) if (strcmp(value,keys[i][0][1])==0) { language=i; break; }
1358         if (i==NBLANGUAGES)
1359         {
1360           fprintf(stderr,"Invalid language : %s\n",value);
1361           fprintf(stderr,"Supported languages :\n");
1362           for (i=0;i<NBLANGUAGES;i++) fprintf(stderr,"%s = %s\n",keys[i][0][1],keys[i][0][0]);
1363           exit(1);
1364         }
1365       }
1366       else
1367       
1368       if (strcmp("theme",keyword)==0)
1369       {
1370         if (debug==2) fprintf(stderr,"setting theme to \"%s\"\n",value);
1371         strcpy(theme,value);
1372       }
1373       else
1374       
1375       if (strcmp("refresh_time",keyword)==0)
1376       {
1377         refresh_time=atoi(value);
1378         if (debug==2) fprintf(stderr,"setting refresh_time to \"%d\"\n",refresh_time);
1379       }
1380       else
1381       
1382       if (strcmp("photo_size",keyword)==0)
1383       {
1384         photo_size=atoi(value);
1385         if (debug==2) fprintf(stderr,"setting photo_size to \"%d\"\n",photo_size);
1386       }
1387       else
1388       
1389       if (strcmp("w3c_link",keyword)==0)
1390       {
1391         if (debug==2) fprintf(stderr,"setting w3c_link to \"%s\"\n",value);
1392         if (strcmp("no",value)==0) w3c_link=0;
1393         else if (strcmp("yes",value)==0) w3c_link=1;
1394         else { fprintf(stderr,"unknown value for \"w3c_link\" option, must be \"yes\" or \"no\"\n"); exit(1); }
1395       }
1396       else
1397       
1398       if (strcmp("logo",keyword)==0)
1399       {
1400         if (debug==2) fprintf(stderr,"setting logo to \"%s\"\n",value);
1401         if (strcmp("no",value)==0) logo=0;
1402         else if (strcmp("yes",value)==0) logo=1;
1403         else { fprintf(stderr,"unknown value for \"logo\" option, must be \"yes\" or \"no\"\n"); exit(1); }
1404       }
1405       else
1406       
1407       if (strcmp("header",keyword)==0)
1408       {
1409         expand(value);
1410         if (debug==2) fprintf(stderr,"setting header to \"%s\"\n",value);
1411         strcpy(header,value);
1412       }
1413       else
1414       
1415       if (strcmp("footer",keyword)==0)
1416       {
1417         expand(value);
1418         if (debug==2) fprintf(stderr,"setting footer to \"%s\"\n",value);
1419         strcpy(footer,value);
1420       }
1421       else
1422       
1423       if (strcmp("input",keyword)==0)
1424       {
1425         expand(value);
1426         if (debug==2) fprintf(stderr,"parsing log file \"%s\"\n",value);
1427         parse_log(value);
1428       }
1429       else
1430       
1431       if (strcmp("nickfile",keyword)==0)
1432       {
1433         expand(value);
1434         if (debug==2) fprintf(stderr,"nick alias using file \"%s\"\n",value);
1435 #ifdef __WIN32__
1436         fprintf(stderr,"no support for nickfile in WIN32 version\n");
1437 #else
1438         parse_nick(value);
1439 #endif
1440       }
1441       else
1442       
1443       if (strcmp("photofile",keyword)==0)
1444       {
1445         expand(value);
1446         if (debug==2) fprintf(stderr,"parsing photo file \"%s\"\n",value);
1447         parse_photo(value);
1448       }
1449       else
1450       
1451       if (strcmp("output",keyword)==0)
1452       {
1453         expand(value);
1454         if (debug==2) fprintf(stderr,"generating xhtml file \"%s\"\n",value);
1455         bestwords(words,0);
1456         gen_xhtml(value);
1457         
1458         /* reset variables */
1459         nbusers=0;
1460         nburls=0;
1461         nbtopics=0;
1462         days=0;
1463         for (i=0;i<24*4;i++) hours[i]=0;
1464         lines=0;
1465         freewords(&words);
1466         for (i=0;i<NBWORDS;i++) topwords[i].nb=0;
1467         totallines=0;
1468         debut=time(NULL);
1469         for (i=0;i<nbusers;i++)
1470         {
1471           free(users[i].photo);
1472           users[i].photo=NULL;
1473         }
1474       }
1475       else
1476       
1477       if (strcmp("top_words",keyword)==0)
1478       {
1479         if (debug==2) fprintf(stderr,"setting top_words to \"%s\"\n",value);
1480         if (strcmp("no",value)==0) top_words=0;
1481         else if (strcmp("yes",value)==0) top_words=1;
1482         else { fprintf(stderr,"unknown value for \"top_words\" option, must be \"yes\" or \"no\"\n"); exit(1); }
1483       }
1484       else
1485       
1486       if (strcmp("ranking",keyword)==0)
1487       {
1488         if (strcmp("lines",value)==0) ranking=0;
1489         else
1490         if (strcmp("words",value)==0) ranking=1;
1491         else
1492         if (strcmp("letters",value)==0) ranking=2;
1493         else { fprintf(stderr,"unknown value for \"ranking\" option, must be \"lines\", \"words\" or \"letters\"\n"); exit(1); }
1494       }
1495       else
1496       
1497       if (strcmp("quarter",keyword)==0)
1498       {
1499         if (debug==2) fprintf(stderr,"setting quarter to \"%s\"\n",value);
1500         if (strcmp("no",value)==0) quarter=0;
1501         else if (strcmp("yes",value)==0) quarter=1;
1502         else { fprintf(stderr,"unknown value for \"quarter\" option, must be \"yes\" or \"no\"\n"); exit(1); }
1503       }
1504
1505       else { fprintf(stderr,"error in config file : \"%s\" is an unknown keyword (line %d)\n",keyword,configlines); exit(1); }        
1506     }
1507   }
1508   fclose(fic);
1509 }
1510
1511 int main(int argc,char *argv[])
1512 {
1513   if ((users=malloc(maxusers*sizeof(struct user)))==NULL) { fprintf(stderr,"unable to malloc memory\n"); exit(1); }
1514   srand(debut=time(NULL));
1515   if (argc==1) parse_config(NULL);
1516   else if (argc==2) parse_config(argv[1]);
1517   else
1518   {
1519     fprintf(stderr,"Usage : %s [/path/to/file.conf]\n",argv[0]);
1520     fprintf(stderr,"Version : irssistats %s\n",VERSION);
1521     exit(1);
1522   }
1523   return(0);
1524 }