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