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