-/* Usage: irssistats [/path/to/file.conf] */
+/*
+ * irssistats version 0.6
+ *
+ * This tool generates IRC stats based on irssi logs.
+ * Usage: irssistats [/path/to/file.conf]
+ *
+ * Copyright (C) 2002-2004 Antoine Jacquet <royale@zerezo.com>
+ * http://royale.zerezo.com/irssistats/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
#include <sys/types.h>
#include <stdio.h>
#include <regex.h>
/* Config */
-#define MAXUSERS 10000
+#define BASEUSERS 1000
#define MAXNICKLENGTH 50
#define MAXLINELENGTH 2000
#define MAXQUOTELENGTH 100
#define MINWORDLENGTH 5
/* irssistats */
-#define VERSION "0.51"
+#define VERSION "0.6"
#define URL "http://royale.zerezo.com/irssistats/"
/* Counters */
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"};
/* Languages */
-#define NBLANGUAGES 8
+#define NBLANGUAGES 9
#define NBKEYS 39
char *keys[NBLANGUAGES][NBKEYS+1][2]= /* first key used for language name and abbreviation */
{
{ "C_MONOLOGUE", "Habla solo" }
},
{ /* Polish language */
- /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
- /* updated by Jakub Jankowski <shasta@atn.pl> */
+ /* contributed by Jakub Jankowski <shasta@atn.pl> */
{ "Polish", "pl" },
{ "CHARSET", "ISO-8859-2" },
{ "HEADER", "Statystyki dla %s zebrane przez %s" },
{ "C_NICK", "czêsto zmienia nick" },
{ "C_MONOLOGUE", "uwielbia monologi" }
},
+ { /* Polish language */
+ /* contributed by Piotr Jarmuz <coreupper@yahoo.com> */
+ { "Polish", "pl-old" },
+ { "CHARSET", "ISO-8859-1" },
+ { "HEADER", "Statystyki dla %s przez %s" },
+ { "LEGEND", "Legenda" },
+ { "LASTDAYS", "Statystyki z ostatnich dni" },
+ { "TOPHOURS", "Statystyki godzinne" },
+ { "TOPUSERS", "Najaktywniejsi ludzie" },
+ { "OTHERS", "Zostalo jeszcze %d nie sklasyfikowanych..." },
+ { "NBLINES", "linie" },
+ { "NICK", "nick" },
+ { "AVGLETTERS", "litery/linie" },
+ { "HOURS", "godziny" },
+ { "QUOTE", "przypadkowa wiadomosc" },
+ { "TOPUSERSTIME", "Najaktywniejsi ludzie wedlug czasu dnia" },
+ { "RANDTOPICS", "Pare tematow" },
+ { "CHANGEDBY", "zmienione przez" },
+ { "NEWTOPIC", "nowy temat" },
+ { "RANDURLS", "Pare URL-i" },
+ { "POSTEDBY", "wyslane przez" },
+ { "POSTEDURL", "URL" },
+ { "TOPWORDS", "Najczestsze slowa" },
+ { "WORD", "slowo" },
+ { "OCCURRENCES", "wystapienia" },
+ { "BIGNUMBERS", "Pare wielkich liczb..." },
+ { "NUMBERS", "liczby" },
+ { "TIME", "%d linii (%d dni) sparsowanych w %d sekund" },
+ { "FOOTER", "Statystyki wygenerowane przez" },
+ { "C_SMILE", "jest czesto szczesliwy :)" },
+ { "C_FROWN", "jest czesto smutny :(" },
+ { "C_EXCLAM", "duzo krzyczy !" },
+ { "C_QUESTION", "zadaje duzo pytan ?" },
+ { "C_ME", "lubi /mnie polecenie" },
+ { "C_TOPIC", "czesto zmienia temat" },
+ { "C_MODE", "czesto zmienia tryb" },
+ { "C_KICK", "lubi /kopac" },
+ { "C_KICKED", "czesto go wykopuja" },
+ { "C_URL", "wysyla duzo URL-i" },
+ { "C_JOIN", "nie wie czy zostac czy wyjsc" },
+ { "C_NICK", "czesto zmienia swojego nicka" },
+ { "C_MONOLOGUE", "czesto mowi monologiem" }
+ },
{ /* Finnish language */
/* contributed by Antti Huopana <ahuopana@ratol.fi> */
{ "Finnish", "fi" },
int debug=1; /* 0 = none ; 1 = normal ; 2 = verbose */
char channel[MAXLINELENGTH]="set_channel_in_config_file";
char maintainer[MAXLINELENGTH]="set_maintainer_in_config_file";
-char theme[MAXLINELENGTH]="default";
+char theme[MAXLINELENGTH]="default,blue,dark,grayscale,namour,pisg,zeduel,zerezo";
int refresh_time=0; /* 0 = disabled */
int w3c_link=1; /* 0 = disabled */
char header[MAXLINELENGTH]="none";
char footer[MAXLINELENGTH]="none";
int totallines=0;
time_t debut;
+int top_words=1; /* 0 = disabled */
+int ranking=0; /* 0 = lines ; 1 = words ; 2 = letters */
+int quarter=0; /* 1 = enabled */
-struct
+struct user
{
char nick[MAXNICKLENGTH];
int lines;
+ int words;
int letters;
int hours[4];
char quote[MAXQUOTELENGTH+1];
int counters[NBCOUNTERS];
int temp;
-} users[MAXUSERS];
+} *users;
int nbusers=0;
+int maxusers=BASEUSERS;
struct
{
} lastdays[31];
int days=0;
-int hours[24];
+int hours[24*4];
int lines=0;
struct letter
#define isletter(c) (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z')))
#define lowercase(c) (((c>='A')&&(c<='Z'))?c-'A'+'a':c)
-void findwords(char *message)
+int findwords(char *message)
{
- int i,c;
+ int i,c,n=0;
struct letter *pos,*tmp;
for (;;)
{
- while (!isletter(*message)) if (*message=='\0') return; else message++;
+ while (!isletter(*message)) if (*message=='\0') return n; else message++;
pos=&words;
while (isletter(*message))
{
message++;
}
pos->nb++;
+ n++;
}
- return;
+ return n;
}
char tempword[MAXLINELENGTH];
if (strcmp(nick,users[start].nick)!=0)
{
nbusers++;
- if (nbusers>=MAXUSERS) { fprintf(stderr,"too many users\n"); exit(1); }
+ if (nbusers>=maxusers)
+ {
+ maxusers+=BASEUSERS;
+ if (debug==2) fprintf(stderr,"allocating more users : %d\n",maxusers);
+ if ((users=realloc(users,maxusers*sizeof(struct user)))==NULL) { fprintf(stderr,"too many users : unable to realloc memory\n"); exit(1); }
+ }
for (i=nbusers-1;i>start;i--)
{
strcpy(users[i].nick,users[i-1].nick);
users[i].lines=users[i-1].lines;
+ users[i].words=users[i-1].words;
users[i].letters=users[i-1].letters;
for (j=0;j<4;j++) users[i].hours[j]=users[i-1].hours[j];
strcpy(users[i].quote,users[i-1].quote);
}
strcpy(users[start].nick,nick);
users[start].lines=0;
+ users[start].words=0;
users[start].letters=0;
for (j=0;j<4;j++) users[start].hours[j]=0;
users[start].quote[0]='\0';
}
j=strlen(message);
users[i].lines++;
+ if (top_words || ranking==1) users[i].words+=findwords(message);
users[i].letters+=j;
users[i].hours[hour/6]++;
lastdays[0].lines++;
lastdays[0].hours[hour/6]++;
lines++;
+ if (quarter)
+ {
+ line[5]='\0';
+ hour=hour*4+atoi(&line[3])/15;
+ }
hours[hour]++;
if (message[j-1]=='?') users[i].counters[D_QUESTION]++;
else if (message[j-1]=='!') users[i].counters[D_EXCLAM]++;
strncpy(urls[temp].shorturl,message,MAXQUOTELENGTH);
}
}
- findwords(message);
}
pos=0;
}
users[user].temp=users[i].temp;
}
users[user].lines+=users[i].lines;
+ users[user].words+=users[i].words;
users[user].letters+=users[i].letters;
for (j=0;j<4;j++) users[user].hours[j]+=users[i].hours[j];
for (j=0;j<NBCOUNTERS;j++) users[user].counters[j]+=users[i].counters[j];
/* "remove" old user */
users[i].lines=-1;
+ users[i].words=-1;
users[i].letters=-1;
for (j=0;j<4;j++) users[i].hours[j]=-1;
for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
/* "remove" the ignored nicks */
i=dichotomic("<NULL>");
users[i].lines=-1;
+ users[i].words=-1;
users[i].letters=-1;
for (j=0;j<4;j++) users[i].hours[j]=-1;
for (j=0;j<NBCOUNTERS;j++) users[i].counters[j]=-1;
int i,j,k;
int user,max,temp;
char line[MAXLINELENGTH];
+ char *subtheme;
if ((fic=fopen(xhtmlfile,"wt"))==NULL) { fprintf(stderr,"can't open xhtml file \"%s\"\n",xhtmlfile); exit(1); }
fprintf(fic,"</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n",L("CHARSET"));
if (refresh_time)
fprintf(fic,"<meta http-equiv=\"Refresh\" content=\"%d\" />\n",refresh_time);
- fprintf(fic,"<link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\" />\n",theme);
+ subtheme=strtok(theme,",");
+ fprintf(fic,"<link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\" title=\"%s\" />\n",subtheme,subtheme);
+ while ((subtheme=strtok(NULL,","))!=NULL)
+ fprintf(fic,"<link rel=\"alternate stylesheet\" type=\"text/css\" href=\"%s.css\" title=\"%s\" />\n",subtheme,subtheme);
fprintf(fic,"</head>\n\n");
fprintf(fic,"<body>\n\n");
}
/* top hours */
fprintf(fic,"<div id=\"irssistats_tophours\">\n<h2>%s</h2>\n<table>\n<tr>\n",L("TOPHOURS"));
max=-1;
- for (i=0;i<24;i++) if (hours[i]>max) max=hours[i];
- for (i=0;i<24;i++)
+ for (i=0;i<24*4;i++) if (hours[i]>max) max=hours[i];
+ if (quarter) for (i=0;i<24*4;i++)
+ {
+ fprintf(fic,"<td align=\"center\" valign=\"bottom\">");
+ if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"width:4px;height:%dpx\"></div>",i/4/6+1,150*hours[i]/max);
+ fprintf(fic,"</td>\n");
+ }
+ else for (i=0;i<24;i++)
{
fprintf(fic,"<td align=\"center\" valign=\"bottom\"><small>%.1f%%</small>",lines!=0?(float)100*hours[i]/lines:0);
if (hours[i]!=0) fprintf(fic,"<div class=\"v%d\" style=\"height:%dpx\"></div>",i/6+1,150*hours[i]/max);
}
fprintf(fic,"</tr>\n<tr>\n");
for (i=0;i<24;i++)
- fprintf(fic,"<th>%d</th>\n",i);
+ if (quarter) fprintf(fic,"<th colspan=\"4\">%d</th>\n",i);
+ else fprintf(fic,"<th>%d</th>\n",i);
fprintf(fic,"</tr>\n</table>\n</div>\n\n");
/* top users */
fprintf(fic,"<div id=\"irssistats_topusers\">\n<h2>%s</h2>\n",L("TOPUSERS"));
- 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"));
+ switch (ranking)
+ {
+ case 0:
+ 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"));
+ break;
+ default:
+ /* "letters" and "words" ranking are not yet translated so we use the generic word "rank" ... */
+ 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"));
+ break;
+ }
for (i=1;i<=NBUSERS;i++)
{
user=-1;
max=0;
- for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
+ switch (ranking)
+ {
+ case 0:
+ for (j=0;j<nbusers;j++) if (users[j].lines>max) max=users[user=j].lines;
+ break;
+ case 1:
+ for (j=0;j<nbusers;j++) if (users[j].words>max) max=users[user=j].words;
+ break;
+ case 2:
+ for (j=0;j<nbusers;j++) if (users[j].letters>max) max=users[user=j].letters;
+ break;
+ }
if (user!=-1)
{
- fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].lines);
+ switch (ranking)
+ {
+ case 0:
+ fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].lines);
+ break;
+ case 1:
+ fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].words);
+ break;
+ case 2:
+ fprintf(fic,"<tr><td>%d</td><td>%s</td><td>%d</td><td class=\"oneline\">",i,users[user].nick,users[user].letters);
+ break;
+ }
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);
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);
printhtml(fic,users[user].quote);
fprintf(fic,"\"</td></tr>\n");
users[user].lines=-1;
+ users[user].words=-1;
+ users[user].letters=-1;
}
}
fprintf(fic,"</table>\n");
fprintf(fic,"</table>\n</div>\n\n");
/* top words */
- fprintf(fic,"<div id=\"irssistats_topwords\">\n<h2>%s</h2>\n",L("TOPWORDS"));
- fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th></tr>\n",L("WORD"),L("OCCURRENCES"));
- for (i=0;i<NBWORDS;i++)
- 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);
- fprintf(fic,"</table>\n</div>\n\n");
-
+ if (top_words)
+ {
+ fprintf(fic,"<div id=\"irssistats_topwords\">\n<h2>%s</h2>\n",L("TOPWORDS"));
+ fprintf(fic,"<table>\n<tr><th></th><th>%s</th><th>%s</th></tr>\n",L("WORD"),L("OCCURRENCES"));
+ for (i=0;i<NBWORDS;i++)
+ 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);
+ fprintf(fic,"</table>\n</div>\n\n");
+ }
+
/* big numbers */
fprintf(fic,"<div id=\"irssistats_bignumbers\">\n<h2>%s</h2>\n",L("BIGNUMBERS"));
fprintf(fic,"<table>\n<tr><th>%s</th><th>%s</th><th>%s</th></tr>\n",L("NICK"),L("NUMBERS"),L("NBLINES"));
else
if (strcmp("verbose",value)==0) { debug=2; fprintf(stderr,"switching to verbose output\n"); }
else { fprintf(stderr,"unknown value for \"debug\" option, must be \"normal\", \"verbose\" or \"none\"\n"); exit(1); }
- } else
+ }
+ else
if (strcmp("channel",keyword)==0)
{
nburls=0;
nbtopics=0;
days=0;
- for (i=0;i<24;i++) hours[i]=0;
+ for (i=0;i<24*4;i++) hours[i]=0;
lines=0;
freewords(&words);
for (i=0;i<NBWORDS;i++) topwords[i].nb=0;
totallines=0;
debut=time(NULL);
}
+ else
+ if (strcmp("top_words",keyword)==0)
+ {
+ if (debug==2) fprintf(stderr,"setting top_words to \"%s\"\n",value);
+ if (strcmp("no",value)==0) top_words=0;
+ else if (strcmp("yes",value)==0) top_words=1;
+ else { fprintf(stderr,"unknown value for \"top_words\" option, must be \"yes\" or \"no\"\n"); exit(1); }
+ }
+ else
+
+ if (strcmp("ranking",keyword)==0)
+ {
+ if (strcmp("lines",value)==0) ranking=0;
+ else
+ if (strcmp("words",value)==0) ranking=1;
+ else
+ if (strcmp("letters",value)==0) ranking=2;
+ else { fprintf(stderr,"unknown value for \"ranking\" option, must be \"lines\", \"words\" or \"letters\"\n"); exit(1); }
+ }
+ else
+
+ if (strcmp("quarter",keyword)==0)
+ {
+ if (debug==2) fprintf(stderr,"setting quarter to \"%s\"\n",value);
+ if (strcmp("no",value)==0) quarter=0;
+ else if (strcmp("yes",value)==0) quarter=1;
+ else { fprintf(stderr,"unknown value for \"quarter\" option, must be \"yes\" or \"no\"\n"); exit(1); }
+ }
+
else { fprintf(stderr,"error in config file : \"%s\" is an unknown keyword (line %d)\n",keyword,configlines); exit(1); }
}
}
int main(int argc,char *argv[])
{
+ if ((users=malloc(maxusers*sizeof(struct user)))==NULL) { fprintf(stderr,"unable to malloc memory\n"); exit(1); }
srand(debut=time(NULL));
if (argc==1) parse_config(NULL);
else if (argc==2) parse_config(argv[1]);