diff -Nur src/mod/gseen.mod/Makefile src/mod/gseen.mod/Makefile
--- ./src/mod/gseen.mod/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/Makefile	2002-10-26 13:17:44.000000000 +0200
@@ -0,0 +1,28 @@
+# Makefile for src/mod/gseen.mod/
+
+doofus:
+	@echo ""
+	@echo "Let's try this from the right directory..."
+	@echo ""
+	@cd ../../../; make
+
+clean:
+	@rm -f *.o *.$(MOD_EXT) *~
+
+static: ../gseen.o
+
+modules: ../../../gseen.$(MOD_EXT)
+
+../gseen.o: ../module.h ../modvals.h ../../eggdrop.h datahandling.c \
+ gseen.c sensors.c gseencmds.c gseencmds.c do_seen.c ai.c tclcmds.c \
+ misc.c seentree.c generic_binary_tree.c slang_gseen_commands.c \
+ slang.c slang_text.c slang_ids.c slang_chanlang.c seenlang.h \
+ slang_multitext.c gseen.h
+	$(CC) $(CFLAGS) $(CPPFLAGS) -DMAKING_MODS -c gseen.c
+	rm -f ../gseen.o
+	mv gseen.o ../
+
+../../../gseen.$(MOD_EXT): ../gseen.o
+	$(LD) -o ../../../gseen.$(MOD_EXT) ../gseen.o $(XLIBS)
+
+#safety hash
diff -Nur src/mod/gseen.mod/README src/mod/gseen.mod/README
--- ./src/mod/gseen.mod/README	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/README	2002-10-26 13:17:45.000000000 +0200
@@ -0,0 +1,140 @@
+Description:
+------------
+
+gseen.mod is a seen module for eggdrop that tracks not only the users in the
+bot's userfile, but everyone who enters one of the bots channels.
+It does pretty much the same as the popular script bseen and has a few
+additional features like AI-seen and seen-notification.
+It's also way faster than any corresponding script because scripts are always
+much slower than modules. Especially scripts that deal with large amount of
+data often become incredible slow.
+
+Installation:
+-------------
+
+gseen.mod is written for eggdrop1.6, but it should also work with eggdrop 1.4.
+
+You need the eggdrop source to compile the module.
+
+The following instructions assume, ~/eggdrop1.6.2/ is the directory
+where you installed your eggdrop from. (of course, other source dirs
+will work as well)
+
+Put gseen.mod.1.1.0.tar.gz in ~/eggdrop1.6.2/src/mod/,
+and unpack it (tar xfz gseen.mod.1.1.0.tar.gz). Change directory
+back to ~/eggdrop1.6.2/.
+
+Now just do what you've done when you compiled your bot:
+"./configure"
+"make config" (you can skip this command on eggdrop 1.4)
+"make"
+"make install"
+
+Don't forget to copy the langfiles from eggdrop1.6.2/src/mod/gseen.mod/ to
+eggdrop/language.
+
+All settings can be found in ~/eggdrop1.6.2/src/mod/gseen.mod/gseen.conf
+Copy it to your eggdrop directory, edit it to fit your needs and put
+"source gseen.conf" at the end of your eggdrop config file. The last thing
+to do is to .rehash your bot.
+
+
+Public commands:
+----------------
+
+!seen <nick>
+  I think this command doesn't need an explanation. ^_^
+!seen <mask>
+  Searches the database for entries that match <mask>
+  for example "!seen *!user@dialin-*.isp.com"
+!seennick <nick>
+  !seen also checks if a user was online later with a
+  different nick. !seennick only seens for <nick>
+!seenstats
+  just a little report on how many nicks are tracked
+
+All commands are also accessible via /msg.
+("/msg <bot> seen <nick>", for example)
+
+
+AI seen:
+--------
+
+This module has a simple built in AI routine.
+A short example:
+
+<G`Quann> Argo: have you seen Fabian recently?
+<|Argo|> G`Quann, fabian (~fabian@dns.gifs.de) was last seen quitting
+from #eggdev 1 week 4 days 9 hours 40 minutes 56 seconds ago
+(20.02. 01:39) stating ".....zzzzZZZzzZZZzZZZZZZZZZZzzz..".
+
+Well, it's not a very intelligent AI, it's rather brute-force. So don't
+forget to use the ai-seen-ignore setting.
+I know that's not coded very elegant, but if you configure it correctly,
+the failure-rate is way lower than with other AI scripts...
+
+DCC commands:
+-------------
+
+.seen
+.seennick
+.seenstats
+  just the same as the public versions
+.purgeseens
+  deletes expired data (this also happens automatically once a day)
+  (m)
+
+Channel Settings:
+-----------------
+
+  +noseendata
+	don't log any seen data in this channel
+  +quietseens
+	send answers directly via notice to the person who asked and
+	don't bother the rest of the channel with the reply
+  +quietaiseens
+	same as +quietseens, but for AI seens
+  +nopubseens
+	ignore every seen-command in this channel
+
+TCL commands:
+-------------
+
+There are no special tcl commands, only the usual bind procs.
+
+The only one that should be mentioned is:
+
+*pubm:seen <nick> <uhost> <hand> <chan> <text>
+  triggers the AI seen
+  returns: 1 if a reply was sent, 0 otherwise
+
+So if you're using another AI script on your bot, you can modify it to
+use this proc and avoid doubled replies this way.
+
+Other:
+------
+
+There is absolutely NO WARRANTY on this module. I do my best to make it
+work properly, but if anything gets screwed up, I'm not responsible. Use
+this module at your own risk.
+
+Feedback:
+---------
+
+Feel free to send feedback and bugreports (I hope there won't be any<g>) to
+gseen.mod@visions-of-fantasy.de
+
+The newest gseen version can always be found at:
+http://www.visions-of-fantasy.de/gseen.mod/
+
+Thanks to:
+----------
+
+- Fabian for teaching me plenty of things
+- everyone who tested the many buggy development versions :)
+- the eggdev team for developing eggdrop
+
+Most of all, I would like to thank Bass for writing bseen.tcl because alot
+of the ideas for this module came from using that tcl script. It's still the
+most powerful seen script, so if you want something that's easier to use than
+a module, get a copy of bseen.tcl.
diff -Nur src/mod/gseen.mod/UPDATES src/mod/gseen.mod/UPDATES
--- ./src/mod/gseen.mod/UPDATES	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/UPDATES	2002-10-26 13:17:46.000000000 +0200
@@ -0,0 +1,55 @@
+Changes in gseen.mod: (since v1.0.0)
+--------------------
+
+1.1.1
+- fixed "no newline" compilation warnings that appeared on some systems.
+- fixed uninitialized "li" variable in do_seen()
+- fixed lacking compatibility to eggdrop1.4 (confirmation anyone?)
+- new option: hide-secret-chans
+
+1.1.0	(15.6.2001)
+- added multilang support
+- removed static buffers
+- organized data in a binary search tree (much faster)
+- optimized a few other things
+- added settings:
+    - fuzzy-search
+    - max-matches
+    - wildcard-search
+
+1.0.8
+- quiet-seens wasn't working for !seennick
+- added quiet-ai-seens
+- renamed nopub to nopubseens and nolog to noseendata and
+  quietseen to quietseens
+
+1.0.7
+- added compatibility to !channels
+- fixed a bug relating strict-host 0 had some strange effects on
+  !seen requests for users with ~ in their ident
+
+1.0.6
+- fixed a very evil bug that allowed anyone to crash the bot, sorry
+
+1.0.5
+- quietseens wasn't working correctly
+- added support for egg1.5's udef chansets
+
+1.0.4
+- added GPL stuff
+- changed error msg that appears if no gseen file exists
+
+1.0.3
+- readme updates
+- fixed a grammatical error in do_seen
+
+1.0.2
+- bot wanted to free a NULL pointer sometimes
+
+1.0.1
+- !seen without parameter returned stupid results :)
+- fixed little typo in .purgeseens
+- "I found 1 matches..." -> "I found 1 match..."
+
+1.0.0
+- release :)
diff -Nur src/mod/gseen.mod/addons/gseen.selectlang.1.0.0.tcl src/mod/gseen.mod/addons/gseen.selectlang.1.0.0.tcl
--- ./src/mod/gseen.mod/addons/gseen.selectlang.1.0.0.tcl	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/addons/gseen.selectlang.1.0.0.tcl	2002-10-26 13:18:14.000000000 +0200
@@ -0,0 +1,80 @@
+#####################################################################
+#
+# gseen.selectlang v1.0.0
+#
+# This is a simple script which selects a language based on the 
+# user's host.
+#
+# It only works for /msg commands.
+#
+# If the user is in a channel which has a language defined, gseen's
+# internal functions will override this selection and use the language
+# of the channel instead.
+#
+#####################################################################
+
+
+# Here you can define which language to use for which host.
+# The first part is the mask for the host, and the second part
+# is the language which should be used for this host.
+
+set tld-langs {
+  {"*.de"		"de"}
+  {"*.at"		"de"}
+  {"*.ch"		"de"}
+  {"*.t-dialin.net"	"de"}
+  {"*.t-ipconnect.net"	"de"}
+  {"*.pl"		"pl"}
+  {"*.jp"		"ja"}
+}
+
+#################################################
+
+
+proc selectlang:getlang {uhost} {
+  global tld-langs
+  
+  foreach tld ${tld-langs} {
+    if {[string match [lindex $tld 0] $uhost]} {
+      return [lindex $tld 1]
+    }
+  }
+  return ""
+}
+
+proc sl:rebind {oldtarget newtarget} {
+  foreach binding [binds msg] {
+    if {[lindex $binding 4] == $oldtarget} {
+      unbind [lindex $binding 0] [lindex $binding 1] [lindex $binding 2] [lindex $binding 4]
+      bind [lindex $binding 0] [lindex $binding 1] [lindex $binding 2] $newtarget
+    }
+  }
+}
+
+proc sl:msg:trigger {nick uhost hand rest target} {
+  global default-slang
+  
+  set lang [selectlang:getlang $uhost]
+  set old-slang ${default-slang}
+  if {$lang != ""} {
+    set default-slang $lang
+    putlog "using '$lang'..."
+  }
+  $target $nick $uhost $hand $rest
+  set default-slang ${old-slang}
+}
+
+sl:rebind *msg:seen sl:msg:seen
+proc sl:msg:seen {nick uhost hand rest} {
+  sl:msg:trigger $nick $uhost $hand $rest *msg:seen
+}
+
+sl:rebind *msg:seenstats sl:msg:seenstats
+proc sl:msg:seenstats {nick uhost hand rest} {
+  sl:msg:trigger $nick $uhost $hand $rest *msg:seenstats
+}
+
+sl:rebind *msg:seennick sl:msg:seennick
+proc sl:msg:seennick {nick uhost hand rest} {
+  sl:msg:trigger $nick $uhost $hand $rest *msg:seennick
+}
\ No newline at end of file
diff -Nur src/mod/gseen.mod/ai.c src/mod/gseen.mod/ai.c
--- ./src/mod/gseen.mod/ai.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/ai.c	2002-10-26 13:17:47.000000000 +0200
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+static int quietaiseens(char *chan)
+{
+  char buf[121], *b;
+
+  Context;
+  strncpy(buf, quiet_ai_seen, 120);
+  buf[120] = 0;
+  b = buf;
+  while (b[0])
+    if (!strcasecmp(chan, newsplit(&b)))
+      return 1;
+#if EGG_IS_MIN_VER(10503)
+  if (ngetudef("quietaiseens", chan))
+    return 1;
+#endif
+  return 0;
+}
+
+static int tcl_pubmseen STDVAR
+{
+  char *nick, *uhost, *hand, *chan, *text;
+  char buf[1024];
+  char *words, *word;
+  seendat *l;
+  int i;
+
+  Context;
+  BADARGS(6, 6, " nick uhost hand chan text");
+  nick = argv[1];
+  uhost = argv[2];
+  hand = argv[3];
+  chan = argv[4];
+  text = argv[5];
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, chan));
+  glob_nick = nick;
+  for (i = 0; i < strlen(text); i++)
+    if (strchr("!?.,\"", text[i]))
+      text[i] = ' ';
+  strncpy(buf, ignore_words, 1023);
+  buf[1023] = 0;
+  words = buf;
+  while (words[0])
+    add_ignoredword(newsplit(&words));
+  strncpy(buf, text, 1023);
+  buf[1023] = 0;
+  words = buf;
+  while (words[0]) {
+    word = newsplit(&words);
+    if (word_is_ignored(word))
+      continue;
+    l = findseen(word);
+    if (l) {
+      if (quietaiseens(chan)) {
+	set_prefix(SLNOTPREFIX);
+        dprintf(DP_HELP, "NOTICE %s :%s%s\n", nick, reply_prefix,
+        	do_seen(word, nick, uhost, chan, 0));
+      } else {
+	set_prefix(SLPUBPREFIX);
+        dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", chan, reply_prefix,
+        	do_seen(word, nick, uhost, chan, 0));
+      }
+      add_seenreq(word, nick, uhost, chan, now);
+      free_ignoredwords();
+      Tcl_AppendResult(irp, "1", NULL);
+      return TCL_OK;
+    }
+  }
+  free_ignoredwords();
+  Tcl_AppendResult(irp, "0", NULL);
+  return TCL_OK;
+}
+
+static tcl_cmds mytcls[] =
+{
+  {"*pubm:seen", tcl_pubmseen},
+  {"*chjn:gseen", gseen_chjn},
+  {"*chpt:gseen", gseen_chpt},
+  {0, 0}
+};
+
+static void add_ignoredword(char *word)
+{
+  ignoredword *l, *nl;
+
+  l = ignoredwords;
+  while (l && l->next)
+    l = l->next;
+  nl = nmalloc(sizeof(ignoredword));
+  nl->word = nmalloc(strlen(word) + 1);
+  strcpy(nl->word, word);
+  nl->next = NULL;
+  if (ignoredwords)
+    l->next = nl;
+  else
+    ignoredwords = nl;
+}
+
+static void free_ignoredwords()
+{
+  ignoredword *l, *ll;
+
+  l = ignoredwords;
+  while (l) {
+    ll = l->next;
+    nfree(l->word);
+    nfree(l);
+    l = ll;
+  }
+  ignoredwords = NULL;
+}
+
+static int expmem_ignoredwords()
+{
+  ignoredword *l;
+  int size = 0;
+
+  for (l = ignoredwords; l; l = l->next) {
+    size += sizeof(ignoredword);
+    size += strlen(l->word) + 1;
+  }
+  return size;
+}
+
+static int word_is_ignored(char *word)
+{
+  ignoredword *l;
+
+  for (l = ignoredwords; l; l = l->next)
+    if (!strcasecmp(l->word, word))
+      return 1;
+  return 0;
+}
diff -Nur src/mod/gseen.mod/datahandling.c src/mod/gseen.mod/datahandling.c
--- ./src/mod/gseen.mod/datahandling.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/datahandling.c	2002-10-26 13:17:48.000000000 +0200
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+
+static void write_seens()
+{
+  seenreq *r;
+  seenreq_by *b;
+  FILE *f;
+  char s[125];
+
+  Context;
+  /* putlog(LOG_MISC, "*", "Saving seen data..."); */
+  if (!gseenfile[0])
+    return;
+  sprintf(s, "%s~new", gseenfile);
+  f = fopen(s, "w");
+  chmod(s, 0600);
+  if (f == NULL) {
+    putlog(LOG_MISC, "*", "ERROR writing gseen file.");
+    return;
+  }
+  fprintf(f, "# gseen data file v1.\n");
+  write_seen_tree_target = f;
+  btree_getall(&seentree, write_seen_tree);
+  for (r = requests; r; r = r->next)
+    for (b = r->by; b; b = b->next)
+      /* @ nick by host chan when */
+      fprintf(f, "@ %s %s %s %s %lu\n", r->nick, b->who, b->host, b->chan,
+              b->when);
+  fclose(f);
+  unlink(gseenfile);
+  movefile(s, gseenfile);
+  /* putlog(LOG_MISC, "*", "Done."); */
+  return;
+}
+
+static void read_seens()
+{
+  FILE *f;
+  char buf[512], *s, *type, *nick, *host, *chan, *msg, *by;
+  time_t when;
+  int spent, iType, i;
+
+  Context;
+  f = fopen(gseenfile, "r");
+  if (f == NULL) {
+    putlog(LOG_MISC, "*", "Can't open gseen file, creating new database...");
+    return;
+  }
+  while (!feof(f)) {
+    buf[0] = 0;
+    s = buf;
+    fgets(s, 511, f);
+    i = strlen(buf);
+    if (buf[i - 1] == '\n')
+      buf[i - 1] = 0;
+    if ((buf[0] == 0) || (buf[0] == '#'))
+      continue;
+    type = newsplit(&s);
+    if (!strcmp(type, "!")) {
+      nick = newsplit(&s);
+      host = newsplit(&s);
+      chan = newsplit(&s);
+      iType = atoi(newsplit(&s));
+      when = (time_t) atoi(newsplit(&s));
+      spent = atoi(newsplit(&s));
+      msg = s;
+      add_seen(iType, nick, host, chan, msg, when, spent);
+    } else if (!strcmp(type, "@")) {
+      nick = newsplit(&s);
+      by = newsplit(&s);
+      host = newsplit(&s);
+      chan = newsplit(&s);
+      when = (time_t) atoi(newsplit(&s));
+      add_seenreq(nick, by, host, chan, when);
+    }
+  }
+  fclose(f);
+  Context;
+  return;
+}
+
+static void purge_seens()
+{
+  seenreq *r, *rr;
+  seenreq_by *b, *bb;
+
+  Context;
+  if (!expire_seens)
+    return;
+  btree_getall_expanded(&seentree, purge_seen_tree);
+  debug0("purge done");
+  r = requests;
+  rr = NULL;
+  while (r) {
+    b = r->by;
+    bb = NULL;
+    while (b) {
+      if ((now - b->when) > (expire_seens * 86400)) {
+        debug2("request for %s from %s has expired.", r->nick, b->who);
+        nfree(b->who);
+        nfree(b->host);
+        nfree(b->chan);
+        if (bb) {
+          bb->next = b->next;
+          nfree(b);
+          b = bb->next;
+        } else {
+          r->by = b->next;
+          nfree(b);
+          b = r->by;
+        }
+      } else {
+        bb = b;
+        b = b->next;
+      }
+    }
+    if (!r->by) {
+      debug1("no further seen requests for %s, deleting", r->nick);
+      nfree(r->nick);
+      if (rr) {
+        rr->next = r->next;
+        nfree(r);
+        r = rr->next;
+      } else {
+        requests = r->next;
+        nfree(r);
+        r = requests;
+      }
+    } else {
+      rr = r;
+      r = r->next;
+    }
+  }
+}
diff -Nur src/mod/gseen.mod/do_seen.c src/mod/gseen.mod/do_seen.c
--- ./src/mod/gseen.mod/do_seen.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/do_seen.c	2002-10-26 13:17:50.000000000 +0200
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+/* do_seen(): Checks if someone matches the mask, and returns the reply
+ * mask : first paramater (e.g. "G`Quann", "G`Quann", "*!*@*.isp.de", ...)
+ * nick : nick of the one, who triggered the command
+ * uhost: user@host of nick
+ * chan : chan, where the command was triggered
+ * bns  :
+ *        1 : do a botnet-seen if no matches are found
+ *        0 : don't do a botnet-seen
+ *       -1 : return NULL instead of text, if no matches were found
+ *            (necessary for botnet seen)
+ */
+static char *do_seen(char *mask, char *nick, char *uhost, char *chan, int bns)
+{
+  char hostbuf[UHOSTLEN + 1], *host, *newhost, *tmp, *dur;
+  seendat *l;
+  gseenres *r;
+  int wild, nr;
+  char bnquery[256];
+  struct userrec *u;
+  struct laston_info *li;
+  struct chanset_t *ch;
+
+  Context;
+  start_seentime_calc();
+  if (seen_reply) {
+    nfree(seen_reply);
+    seen_reply = NULL;
+  }
+  l = NULL;
+  li = NULL;
+  host = hostbuf;
+  newhost = NULL;
+  mask = newsplit(&mask);
+  glob_query = mask;
+  while (mask[0] == ' ')
+    mask++;
+  if (!mask[0]) {
+    return SLNOPARAM;
+  }
+  if (strchr(mask, '?') || strchr(mask, '*')) {
+    // if wildcard-searches ares not allowed, then either return
+    // NULL (for botnet-seen), or a appropriate warning
+    if (!wildcard_search) {
+      if (bns == -1)
+        return NULL;
+      else
+        return SLNOWILDCARDS;
+    } else
+      wild = 1;
+  } else {
+    if (strlen(mask) > seen_nick_len) // don't process if requested nick is too long
+      return SLTOOLONGNICK;      // (e.g. stop stupid jokes)
+    if (!strcasecmp(mask, nick)) {
+      return SLMIRROR;
+    }
+    // check if the nick is on the current channel
+    if (onchan(mask, chan))
+      return SLONCHAN;
+    if ((glob_othernick = handonchan(mask, chan)))
+      return SLHANDONCHAN;
+    // check if it is on any other channel
+    if ((ch = onanychan(mask))) {
+#if EGG_IS_MIN_VER(10500)
+      if (!secretchan(ch->dname)) {
+	glob_otherchan = ch->dname;
+        return SLONOTHERCHAN;
+      }
+#else
+      if (!secretchan(ch->name)) {
+	glob_otherchan = ch->name;
+        return SLONOTHERCHAN;
+      }
+#endif
+    }
+    // check if the user who uses this handle is on the channel under
+    // a different nick
+    if ((ch = handonanychan(mask))) {
+#if EGG_IS_MIN_VER(10500)
+      if (!secretchan(ch->dname)) {
+        glob_otherchan = ch->dname;
+        return SLONOTHERCHAN;
+      }
+#else
+      if (!secretchan(ch->name)) {
+        glob_otherchan = ch->name;
+        return SLONOTHERCHAN;
+      }
+#endif
+    }
+    add_seenreq(mask, nick, uhost, chan, now);
+    wild = 0;
+    l = findseen(mask);
+    // if there's a result, and if we don't want to search for the same user
+    // under a different nick, just make a do_seennick on the result
+    if (l && !fuzzy_search) {
+      tmp = do_seennick(l);
+      end_seentime_calc();
+      return tmp;
+    }
+    if (!l) {
+      u = get_user_by_handle(userlist, mask);
+      if (u) {
+        li = get_user(&USERENTRY_LASTON, u);
+      }
+      if (!u || !li) {
+        if (bns == -1) {       // if bns is 0, then do_seen() was triggered by
+          end_seentime_calc(); // a botnet seen function, which needs a clear
+          return NULL;         // NULL to detect if there was a result or not
+        }
+        tmp = SLNOTSEEN;
+        if (bns && ((strlen(mask) + strlen(nick) + strlen(uhost)
+            + strlen(chan) + 20) < 255)) {
+          debug0("trying botnet seen");
+          if (bnsnick)
+            nfree(bnsnick);
+          if (bnschan)
+            nfree(bnschan);
+          bnsnick = nmalloc(strlen(nick) + 1);
+          strcpy(bnsnick, nick);
+          bnschan = nmalloc(strlen(chan) + 1);
+          strcpy(bnschan, chan);
+          sprintf(bnquery, "gseen_req %s %s %s %s", mask, nick, uhost, chan);
+          botnet_send_zapf_broad(-1, botnetnick, NULL, bnquery);
+        }
+      } else {
+        // we have a matching handle, no seen-entry, but a laston entry
+        // in the userbase, so let's just return that one.
+        dur = gseen_duration(now - li->laston);
+        glob_laston = dur;
+        tmp = SLPOORSEEN;
+        seen_reply = nmalloc(strlen(tmp) + 1);
+        strcpy(seen_reply, tmp);
+        end_seentime_calc();
+        return seen_reply;
+      }
+      end_seentime_calc();
+      return tmp;
+    }
+    // now prepare the host for fuzzy-search
+    if (strlen(l->host) < UHOSTLEN) {
+      maskstricthost(l->host, host);
+      host = strchr(host, '!') + 1; // strip nick from host for faster search
+    } else {
+      end_seentime_calc();
+      return "error, too long host";
+    }
+  }
+  if (l && (l->type == SEEN_CHPT)) {
+    tmp = do_seennick(l);
+    end_seentime_calc();
+    return tmp;
+  }
+  numresults = 0;
+  // wildmatch_seens uses a global var to store hosts in it
+  // (to prevent massive nmalloc/nfree-usage), so don't forget
+  // to initialize and free it
+  temp_wildmatch_host = my_malloc(1);
+  wildmatch_seens(host, mask, wild);
+  my_free(temp_wildmatch_host);
+  temp_wildmatch_host = NULL;
+  if (!results) {
+    end_seentime_calc();
+    if (bns == -1)
+      return NULL; // let the botnet seen function know, that seen failed
+    return SLNOMATCH;
+  }
+  if (numresults >= max_matches) {
+    end_seentime_calc();
+    free_seenresults();
+    return SLTOOMANYMATCHES;
+  }
+  sortresults();
+  if (strcasecmp(results->seen->nick, mask)) {
+    // if the user's latest nick is not the nick for which we were searching,
+    // say that there were multiple matches and display the latest one
+    if (numresults == 1)
+      tmp = SLONEMATCH;
+    else if (numresults <= 5)
+      tmp = SLLITTLEMATCHES;
+    else
+      tmp = SLMANYMATCHES;
+    seen_reply = nmalloc(strlen(tmp) + 1);
+    strcpy(seen_reply, tmp);
+    nr = 0;
+    for (r = results; (r && (nr < 5)); r = r->next) {
+      nr++;
+      if (nr > 1) {
+        seen_reply = nrealloc(seen_reply, 1 + strlen(seen_reply) + 1 + strlen(r->seen->nick) + 1);
+        strcat(seen_reply, ", ");
+      } else {
+	seen_reply = nrealloc(seen_reply, 1 + strlen(seen_reply) + strlen(r->seen->nick) + 1);
+        strcat(seen_reply, " ");
+      }
+      strcat(seen_reply, r->seen->nick);
+    }
+    tmp = do_seennick(results->seen);
+    seen_reply = nrealloc(seen_reply, 2 + strlen(seen_reply) + strlen(tmp) + 1);
+    sprintf(seen_reply, "%s. %s", seen_reply, tmp);
+  } else { // first result is the nick which we were searching for
+    // just return the info for this nick and don't care about other results
+    tmp = do_seennick(results->seen);
+    seen_reply = nmalloc(strlen(tmp) + 1);
+    strcpy(seen_reply, tmp);
+  }
+  free_seenresults();
+  end_seentime_calc();
+  return seen_reply;
+}
+
+/* do_seennick():
+ * takes a seen-dataset and produces the corresponding reply basically
+ * by referencing to the lang entry with the same number as the seen-type.
+ */
+static char *do_seennick(seendat *l)
+{
+//  char buf[256], *msg;
+  int stype;
+
+  Context;
+  if (!l) {
+    debug0("ERROR! Tryed to do a seennick on a NULL pointer!");
+    return "ERROR! seendat == NULL!!!";
+  }
+  glob_seendat = l;
+  // l->type is the basic language-entry-number
+  stype = l->type + 100;
+  // in some cases, we might need a special reply, so modify the
+  // number if neccessary
+  switch (l->type) {
+    case SEEN_JOIN:
+      if (!onchan(l->nick, l->chan))
+        stype += 20;
+      break;
+    case SEEN_PART:
+      /* nothing to do here */
+      break;
+    case SEEN_SIGN:
+      /* nothing again */
+      break;
+    case SEEN_NICK:
+      if (!onchan(l->msg, l->chan))
+        stype += 20;
+      break;
+    case SEEN_NCKF:
+      if (!onchan(l->nick, l->chan))
+        stype += 20;
+      break;
+    case SEEN_KICK:
+/*      msg = buf;
+      strncpy(buf, l->msg, 255);
+      msg[255] = 0;
+      sglobpunisher = newsplit(&msg);
+      sglobreason = msg; */
+      break;
+    case SEEN_SPLT:
+      /* nothing to do here */
+      break;
+    case SEEN_REJN:
+      if (!onchan(l->nick, l->chan))
+        stype += 20;
+      break;
+    case SEEN_CHJN:
+    case SEEN_CHPT:
+      if (!strcmp(l->chan, "0"))
+        stype += 20;
+      break;
+    default:
+      stype = 140;
+  }
+  return getslang(stype);
+}
+
+/* findseens():
+ * interface for webseen.mod
+ * find all results for a query and return a pointer to this list
+ * (basically the core of do_seen())
+ */
+static gseenres *findseens(char *mask, int *ret, int fuzzy)
+{
+  char hostbuf[UHOSTLEN + 1], *host, *newhost;
+  seendat *l;
+  int wild;
+
+  Context;
+  start_seentime_calc();
+  *ret = WS_OK;
+  l = NULL;
+  host = hostbuf;
+  newhost = NULL;
+  mask = newsplit(&mask);
+  while (mask[0] == ' ')
+    mask++;
+  if (!mask[0]) {
+    *ret = WS_NOPARAM;
+    return NULL;
+  }
+  if (strchr(mask, '?') || strchr(mask, '*')) {
+    // if wildcard-searches ares not allowed, then either return
+    // NULL (for botnet-seen), or a appropriate warning
+    if (!wildcard_search) {
+      *ret = WS_NOWILDCARDS;
+      return NULL;
+    }
+    wild = 1;
+  } else {
+    if (strlen(mask) > seen_nick_len) { // don't process if requested nick is too long
+      *ret = WS_TOOLONGNICK;       // (e.g. stop stupid jokes)
+      return NULL;
+    }
+    add_seenreq(mask, "www-user", "unknown_host", "webinterface", now);
+    wild = 0;
+    l = findseen(mask);
+    // if there's a result, and if we don't want to search for the same user
+    // under a different nick, just return this result
+    if (l && (!fuzzy_search || !fuzzy)) {
+      numresults = 1;
+      add_seenresult(l);
+      end_seentime_calc();
+      return results;
+    }
+    if (!l) {
+      // no matching user was found :(
+      *ret = WS_NORESULT;
+      end_seentime_calc();
+      return NULL;
+    }
+    // now prepare the host for fuzzy-search
+    if (strlen(l->host) < UHOSTLEN) {
+      maskstricthost(l->host, host);
+      host = strchr(host, '!') + 1; // strip nick from host for faster search
+    } else {
+      *ret = WS_TOOLONGHOST;
+      end_seentime_calc();
+      return NULL;
+    }
+  }
+  if (l && (l->type == SEEN_CHPT)) {
+    numresults = 1;
+    add_seenresult(l);
+    end_seentime_calc();
+    return results;
+  }
+  numresults = 0;
+  // wildmatch_seens uses a global var to store hosts in it
+  // (to prevent massive nmalloc/nfree-usage), so don't forget
+  // to initialize and free it
+  temp_wildmatch_host = my_malloc(1);
+  wildmatch_seens(host, mask, wild);
+  my_free(temp_wildmatch_host);
+  temp_wildmatch_host = NULL;
+  if (!results) {
+    // no match :(
+    *ret = WS_NORESULT;
+    end_seentime_calc();
+    return NULL;
+  }
+  if (numresults >= max_matches) {
+    free_seenresults();
+    *ret = WS_TOOMANYMATCHES;
+    end_seentime_calc();
+    return NULL;
+  }
+  sortresults();
+  *ret = 0;
+  end_seentime_calc();
+  return results;
+}
+
+
+char seenstats_reply[512];
+static char *do_seenstats()
+{
+  glob_totalnicks = count_seens();
+  glob_totalbytes = gseen_expmem();
+  sprintf(seenstats_reply, "%s", SLSEENSTATS);
+  return seenstats_reply;
+}
+
+// add an seen result (to the top of the list)
+static void add_seenresult(seendat *seen)
+{
+  gseenres *nl;
+
+  numresults++;
+  if (numresults > max_matches)
+    return;
+  nl = nmalloc(sizeof(gseenres));
+  nl->seen = seen;
+  nl->next = results;
+  results = nl;
+}
+
+static int expmem_seenresults()
+{
+  int bytes = 0;
+  gseenres *l;
+
+  for (l = results; l; l = l->next)
+    bytes += sizeof(gseenres);
+  return bytes;
+}
+
+static void free_seenresults()
+{
+  gseenres *l, *ll;
+
+  l = results;
+  while (l) {
+    ll = l->next;
+    nfree(l);
+    l = ll;
+  }
+  results = NULL;
+}
+
+static void sortresults()
+{
+  int again = 1;
+  gseenres *last, *p, *c, *n;
+  int a, b;
+
+  Context;
+  again = 1;
+  last = NULL;
+  while ((results != last) && (again)) {
+    p = NULL;
+    c = results;
+    n = c->next;
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else
+        a = c->seen->when;
+        b = n->seen->when;
+      if (a < b) {
+  again = 1;
+  c->next = n->next;
+  n->next = c;
+  if (p == NULL)
+    results = n;
+  else
+    p->next = n;
+      }
+      p = c;
+      c = n;
+      n = n->next;
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sortrequests(seenreq *l)
+{
+  int again = 1;
+  seenreq_by *last, *p, *c, *n;
+  int a, b;
+
+  Context;
+  again = 1;
+  last = NULL;
+  while ((l->by != last) && (again)) {
+    p = NULL;
+    c = l->by;
+    n = c->next;
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else
+        a = c->when;
+        b = n->when;
+      if (a < b) {
+  again = 1;
+  c->next = n->next;
+  n->next = c;
+  if (p == NULL)
+    l->by = n;
+  else
+    p->next = n;
+      }
+      p = c;
+      c = n;
+      n = n->next;
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+/* stolen from tcl_duration in tclmisc.c */
+char gs_duration_temp[256];
+static char *gseen_duration(int seconds)
+{
+  char s[256];
+  time_t sec;
+
+  sec = seconds;
+  s[0] = 0;
+  if (sec < 1) {
+    snprintf(gs_duration_temp, sizeof(gs_duration_temp), "%s", SLSOMETIME);
+    return gs_duration_temp;
+  }
+  if (sec < 60) {
+    sprintf(gs_duration_temp, "%d %s", (int) (sec / 1),
+            ((int) (sec / 1) > 1) ? SLSECONDS : SLSECOND);
+    return gs_duration_temp;
+  }
+  if (sec >= 31536000) {
+    sprintf(s, "%d %s ", (int) (sec / 31536000),
+            ((int) (sec / 31536000) > 1) ? SLYEARS : SLYEAR);
+    sec -= (((int) (sec / 31536000)) * 31536000);
+  }
+  if (sec >= 604800) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 604800),
+            ((int) (sec / 604800) > 1) ? SLWEEKS : SLWEEK);
+    sec -= (((int) (sec / 604800)) * 604800);
+  }
+  if (sec >= 86400) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 86400),
+            ((int) (sec / 86400) > 1) ? SLDAYS : SLDAY);
+    sec -= (((int) (sec / 86400)) * 86400);
+  }
+  if (sec >= 3600) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 3600),
+            ((int) (sec / 3600) > 1) ? SLHOURS : SLHOUR);
+    sec -= (((int) (sec / 3600)) * 3600);
+  }
+  if (sec >= 60) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 60),
+            ((int) (sec / 60) > 1) ? SLMINUTES : SLMINUTE);
+    sec -= (((int) (sec / 60)) * 60);
+  }
+  strcpy(gs_duration_temp, s);
+  if (gs_duration_temp[strlen(gs_duration_temp) - 1] == ' ')
+    gs_duration_temp[strlen(gs_duration_temp) - 1] = 0;
+  return gs_duration_temp;
+}
+
+static int onchan(char *nick, char *chan)
+{
+  struct chanset_t *ch;
+  memberlist *m;
+
+  ch = findchan_by_dname(chan);
+  if (!ch)
+    return 0;
+  m = ismember(ch, nick);
+  if (!m)
+    return 0;
+  else if (chan_issplit(m))
+    return 0;
+  else
+    return 1;
+}
+
+/* handonchan():
+ * checks if the given user is on the channel and returns its nick
+ */
+static char *handonchan(char *hand, char *chan)
+{
+  struct chanset_t *ch;
+  memberlist *m;
+
+  ch = findchan_by_dname(chan);
+  if (!ch)
+    return 0;
+  if (ch->channel.members > 0) {
+    for (m = ch->channel.member; m; m = m->next) {
+      if (m->user) {
+        if (m->user->handle && !rfc_casecmp(m->user->handle, hand))
+          return m->nick;
+      }
+    }
+  }
+  return NULL;
+}
+
+/* onanychan():
+ * checks if the given nickname is on any of the bot's chans.
+ */
+static struct chanset_t *onanychan(char *nick)
+{
+  struct chanset_t *ch;
+  memberlist *m;
+
+  for (ch = chanset; ch; ch = ch->next) {
+    m = ismember(ch, nick);
+    if (m && !chan_issplit(m))
+      return ch;
+  }
+  return NULL;
+}
+
+/* handonanychan():
+ * checks if the given user is on any channel (no matter under which nick)
+ */
+static struct chanset_t *handonanychan(char *hand)
+{
+  struct chanset_t *ch;
+  memberlist *m;
+
+  for (ch = chanset; ch; ch = ch->next) {
+    if (ch->channel.members > 0) {
+      for (m = ch->channel.member; m; m = m->next) {
+        if (m->user) {
+          if (m->user->handle && !rfc_casecmp(m->user->handle, hand))
+            return ch;
+        }
+      }
+    }
+  }
+  return NULL;
+}
+
+static void add_seenreq(char *nick, char *from, char *host, char *chan,
+			time_t when)
+{
+  seenreq *l, *nl;
+  seenreq_by *b, *nb;
+  char buf[10] = "[secret]";
+
+  Context;
+  if (!tell_seens)
+    return;
+  if (strcmp(chan, "[partyline]") && secretchan(chan))
+    chan = buf;
+  for (l = requests; l; l = l->next) {
+    if (!strcasecmp(nick, l->nick)) {
+      for (b = l->by; b; b = b->next) {
+	if (!strcasecmp(from, b->who)) {
+	  nfree(b->chan);
+	  b->chan = nmalloc(strlen(chan) + 1);
+	  strcpy(b->chan, chan);
+	  b->when = when;
+	  return;
+	}
+      }
+      b = l->by;
+      while (b && b->next)
+        b = b->next;
+      nb = nmalloc(sizeof(seenreq_by));
+      nb->who = nmalloc(strlen(from) + 1);
+      strcpy(nb->who, from);
+      nb->host = nmalloc(strlen(host) + 1);
+      strcpy(nb->host, host);
+      nb->chan = nmalloc(strlen(chan) + 1);
+      strcpy(nb->chan, chan);
+      nb->when = when;
+      nb->next = NULL;
+      if (l->by)
+        b->next = nb;
+      else
+        l->by = nb;
+      return;
+    }
+  }
+  nb = nmalloc(sizeof(seenreq_by));
+  nb->who = nmalloc(strlen(from) + 1);
+  strcpy(nb->who, from);
+  nb->host = nmalloc(strlen(host) + 1);
+  strcpy(nb->host, host);
+  nb->chan = nmalloc(strlen(chan) + 1);
+  strcpy(nb->chan, chan);
+  nb->when = when;
+  nb->next = NULL;
+  l = requests;
+  while (l && l->next)
+    l = l->next;
+  nl = nmalloc(sizeof(seenreq));
+  nl->nick = nmalloc(strlen(nick) + 1);
+  strcpy(nl->nick, nick);
+  nl->by = nb;
+  nl->next = NULL;
+  if (requests)
+    l->next = nl;
+  else
+    requests = nl;
+}
+
+static int expmem_seenreq()
+{
+  seenreq *l;
+  seenreq_by *b;
+  int size;
+
+  size = 0;
+  for (l = requests; l; l = l->next) {
+    size += sizeof(seenreq);
+    size += strlen(l->nick) + 1;
+    for (b = l->by; b; b = b->next) {
+      size += sizeof(seenreq_by);
+      size += strlen(b->who) + 1;
+      size += strlen(b->host) + 1;
+      size += strlen(b->chan) + 1;
+    }
+  }
+  return size;
+}
+
+static int count_seenreq(seenreq_by *b)
+{
+  seenreq_by *l;
+  int nr;
+
+  nr = 0;
+  for (l = b; l; l = l->next)
+    nr++;
+  return nr;
+}
+
+static void free_seenreq()
+{
+  seenreq *l, *ll;
+  seenreq_by *b, *bb;
+
+  Context;
+  l = requests;
+  while (l) {
+    b = l->by;
+    while (b) {
+      bb = b->next;
+      nfree(b->who);
+      nfree(b->host);
+      nfree(b->chan);
+      nfree(b);
+      b = bb;
+    }
+    ll = l->next;
+    nfree(l->nick);
+    nfree(l);
+    l = ll;
+  }
+  requests = NULL;
+}
+
+static void report_seenreq(char *channel, char *nick)
+{
+  seenreq *l, *ll;
+  seenreq_by *b, *bb;
+  char *reply, *tmp;
+  int nr;
+
+  if (!tell_seens)
+    return;
+  ll = NULL;
+  l = requests;
+  reply = NULL;
+  while (l) {
+    if (!strcasecmp(l->nick, nick)) {
+      reset_global_vars();
+      glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, channel));
+      glob_nick = nick;
+      nr = count_seenreq(l->by);
+      if (nr == 1) {
+        glob_seenrequest = l;
+        dprintf(DP_HELP, "NOTICE %s :%s\n", l->nick, SLONELOOK);
+      } else {
+        sortrequests(l);
+        glob_seenrequest = l;
+        glob_seenrequests = nr;
+        tmp = SLMORELOOKS;
+        reply = nmalloc(strlen(tmp) + 1);
+	    strcpy(reply, tmp);
+	    nr = 0;
+        for (b = l->by; b; b = b->next) {
+          nr++;
+          reply = nrealloc(reply, strlen(reply) + ((nr == 1) ? 1 : 2) + strlen(b->who) + 1);
+          sprintf(reply, "%s%s%s", reply, (nr == 1) ? " " : ", ", b->who);
+	    }
+        tmp = SLLASTLOOK;
+        reply = nrealloc(reply, strlen(reply) + 2 + strlen(tmp) + 1);
+        sprintf(reply, "%s. %s", reply, tmp);
+	    dprintf(DP_HELP, "NOTICE %s :%s\n", l->nick, reply);
+        nfree(reply);
+      }
+      b = l->by;
+      while (b) {
+        bb = b->next;
+        nfree(b->who);
+        nfree(b->host);
+        nfree(b->chan);
+        nfree(b);
+        b = bb;
+      }
+      nfree(l->nick);
+      if (ll)
+        ll->next = l->next;
+      else
+        requests = l->next;
+      nfree(l);
+      if (ll)
+        l = ll->next;
+      else
+        l = requests;
+    } else {
+      ll = l;
+      l = l->next;
+    }
+  }
+}
+
+static void start_seentime_calc()
+{
+  struct timeval t;
+
+  gettimeofday(&t, NULL);
+  glob_presearch = (float) t.tv_sec + (((float) t.tv_usec) / 1000000);
+}
+
+static void end_seentime_calc()
+{
+  struct timeval t;
+
+  gettimeofday(&t, NULL);
+  glob_aftersearch = (float) t.tv_sec + (((float) t.tv_usec) / 1000000);
+  glob_total_searchtime += glob_aftersearch - glob_presearch;
+  glob_total_queries++;
+}
diff -Nur src/mod/gseen.mod/generic_binary_tree.c src/mod/gseen.mod/generic_binary_tree.c
--- ./src/mod/gseen.mod/generic_binary_tree.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/generic_binary_tree.c	2002-10-26 13:17:51.000000000 +0200
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+#define GENERIC_BINARY_TREE 1
+
+struct generic_binary_tree {
+  void *root;
+  int (*comparedata) (void *data1, void *data2);
+  int (*expmemdata) (void *data);
+  void (*freedata) (void *data);
+};
+
+struct generic_binary_tree_node {
+  void *data;
+  void *left;
+  void *right;
+};
+
+static void btree_add(struct generic_binary_tree *, void *);
+static int btree_expmem(struct generic_binary_tree *);
+static int btree_recursive_expmem(struct generic_binary_tree *, struct generic_binary_tree_node *);
+static void *btree_get(struct generic_binary_tree *, void *t);
+static void btree_freetree(struct generic_binary_tree *);
+static void btree_recursive_free(struct generic_binary_tree *,
+                                 struct generic_binary_tree_node *);
+static void btree_getall(struct generic_binary_tree *, void (*) (void *));
+static void btree_recursive_getall(struct generic_binary_tree_node *,
+                                   void (*) (void *));
+static void btree_getall_expanded(struct generic_binary_tree *tree, void (*) (void *));
+static void btree_recursive_getall_expanded(struct generic_binary_tree_node *,
+                                            void (*) (void *));
+static void btree_remove(struct generic_binary_tree *, void *);
+
+static void btree_add(struct generic_binary_tree *tree, void *data)
+{
+  struct generic_binary_tree_node *node, *lastnode;
+  int cmp, lastcmp;
+
+  Assert(tree);
+  Assert(data);
+  cmp = lastcmp = 0;
+  node = tree->root;
+  lastnode = NULL;
+  while (node) {
+    cmp = tree->comparedata(node->data, data);
+    if (!cmp) {
+      // item is identical -> free old data and insert new
+      tree->freedata(node->data);
+      node->data = data;
+      return;
+    }
+    lastnode = node;
+    lastcmp = cmp;
+    if (cmp < 0)
+      node = node->left;
+    else
+      node = node->right;
+  }
+  node = nmalloc(sizeof(struct generic_binary_tree_node));
+  node->left = NULL;
+  node->right = NULL;
+  node->data = data;
+  if (!lastnode)
+    tree->root = node;
+  else {
+    Assert(lastcmp);
+    if (lastcmp < 0) {
+      Assert(!lastnode->left);
+      lastnode->left = node;
+    } else {
+      Assert(!lastnode->right);
+      lastnode->right = node;
+    }
+  }
+}
+
+static int btree_expmem(struct generic_binary_tree *tree)
+{
+  int size = 0;
+
+  Assert(tree);
+  size += btree_recursive_expmem(tree, tree->root);
+  return size;
+}
+
+static int btree_recursive_expmem(struct generic_binary_tree *tree, struct generic_binary_tree_node *node)
+{
+  int size = 0;
+
+  if (!node)
+    return 0;
+  size += sizeof(struct generic_binary_tree_node);
+  size += tree->expmemdata(node->data);
+  size += btree_recursive_expmem(tree, node->left);
+  size += btree_recursive_expmem(tree, node->right);
+  return size;
+}
+
+static void *btree_get(struct generic_binary_tree *tree, void *what)
+{
+  struct generic_binary_tree_node *node;
+  int cmp;
+
+  node = tree->root;
+  while (node) {
+    cmp = tree->comparedata(node->data, what);
+    if (!cmp)
+      return node->data;
+    if (cmp < 0)
+      node = node->left;
+    else
+      node = node->right;
+  }
+  return NULL;
+}
+
+static void btree_freetree(struct generic_binary_tree *tree)
+{
+  btree_recursive_free(tree, tree->root);
+}
+
+static void btree_recursive_free(struct generic_binary_tree *tree,
+                                 struct generic_binary_tree_node *node)
+{
+  if (!node)
+    return;
+  btree_recursive_free(tree, node->left);
+  btree_recursive_free(tree, node->right);
+  tree->freedata(node->data);
+  nfree(node);
+}
+
+/* btree_getall():
+ * calls the specified function for each item in the tree.
+ * NOTE: getall() calls the proc _before_ it proceeds into recursion. This way,
+ *       one can savely store the tree into a file without mixing up its form.
+ *       But if you delete an item from the called prcedure, this function
+ *       WILL crash. Use btree_getall() expanded instead.
+ */
+static void btree_getall(struct generic_binary_tree *tree, void (*func) (void *))
+{
+  Assert(tree);
+  btree_recursive_getall(tree->root, func);
+}
+
+static void btree_recursive_getall(struct generic_binary_tree_node *node,
+                                   void (*func) (void *))
+{
+  if (!node)
+    return;
+  // first call the function, then proceed into recursion
+  // this way, the tree keeps in form if its saved to a file, for example
+  Assert(func);
+  func(node->data);
+
+  btree_recursive_getall(node->left, func);
+  btree_recursive_getall(node->right, func);
+}
+
+/* btree_getall_expanded():
+ * the same as btree_getall(), but calls the function after the greatest level of recursion
+ * has been reached. The node-pointers won't be accessed anymore when the first function
+ * gets called. You can savely use this to free items.
+ */
+static void btree_getall_expanded(struct generic_binary_tree *tree, void (*func) (void *))
+{
+  Assert(tree);
+  btree_recursive_getall_expanded(tree->root, func);
+}
+
+static void btree_recursive_getall_expanded(struct generic_binary_tree_node *node,
+                                            void (*func) (void *))
+{
+  if (!node)
+    return;
+  btree_recursive_getall_expanded(node->left, func);
+  btree_recursive_getall_expanded(node->right, func);
+
+  Assert(func);
+  func(node->data);
+}
+
+static void btree_remove(struct generic_binary_tree *tree, void *data)
+{
+  struct generic_binary_tree_node *node, *last, *largenode, *lastlarge;
+  int ret, lastret;
+
+  Assert(tree);
+  Assert(data);
+  last = NULL;
+  lastret = 0;
+  node = tree->root;
+  while (node) {
+    ret = tree->comparedata(node->data, data);
+    if (ret == 0)
+      break;
+    last = node;
+    lastret = ret;
+    if (ret < 0)
+      node = node->left;
+    else
+      node = node->right;
+  }
+  if (!node)  // oops, item not found
+    return;
+  if (!node->left && !node->right) {
+    // *freu* no sub-branches! We can easily delete this item.
+    if (last) {
+      if (lastret < 0)
+        last->left = NULL;
+      else
+        last->right = NULL;
+    } else
+      tree->root = NULL;
+  } else if (!node->left) {
+    // also pretty easy. Just connect the child to the parent.
+    if (last) {
+      if (lastret < 0)
+        last->left = node->right;
+      else
+        last->right = node->right;
+    } else
+      tree->root = node->right;
+  } else if (!node->right) {
+    // same as above, but mirrored
+    if (last) {
+      if (lastret < 0)
+        last->left = node->left;
+      else
+        last->right = node->left;
+    } else
+      tree->root = node->left;
+  } else {
+    // aaargh... two sub-trees! The world is not fair... *sigh*
+    debug0("argl... worst case, two subtrees. :( Let's pray...");
+    // now we take the largest item from the left subtree and replace the
+    // doomed node with it.
+    // since it is the largest val, the tree remains valid and doesn't
+    // get deformed too much.
+
+    // at first, we have to find this node and cut it from the tree
+    largenode = node->left;
+    lastlarge = NULL;
+    while (largenode && largenode->right) {
+      lastlarge = largenode;
+      largenode = largenode->right;
+    }
+
+    // only set largenode->left to node->left if largenode exists.
+    // otherwise node->left points to largenode, which would result
+    // in a nice short-circuit
+    // If it does not exist, just leave largenode->left as it is because we just
+    // move largenode one level up, so it can keep its left subtree.
+    if (lastlarge) {
+      lastlarge->right = largenode->left;
+      largenode->left = node->left;
+    }
+
+    // now connect node's subtrees to it
+    largenode->right = node->right;
+
+    // and finally replace node with largenode
+    if (last) {
+      if (lastret < 0)
+        last->left = largenode;
+      else
+        last->right = largenode;
+    } else
+      tree->root = largenode;
+  }
+  // finally kill the node... we shouldn't need it anymore
+  tree->freedata(node->data);
+  nfree(node);
+  node = NULL;
+}
+
+#ifdef BTREE_WITHOPTIMIZE
+static void btree_optimize(struct generic_binary_tree *tree,
+                           struct generic_binary_tree_node *node,
+                           struct generic_binary_tree_node *last,
+                           int limit)
+{
+/*  int leftdepth, rightdepth;
+
+  if (!node)
+    return;
+  btree_optimize(tree, node->left, node, last, limit);
+  btree_optimize(tree, node->right, node, last, limit);
+  leftdepth = btree_depth(node->left);
+  rightdepth = btree_depth(node->right);
+  if ((leftdepth - rightdepth) > limit) {
+
+  }
+*/
+}
+#endif
diff -Nur src/mod/gseen.mod/global_vars.c src/mod/gseen.mod/global_vars.c
--- ./src/mod/gseen.mod/global_vars.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/global_vars.c	2002-10-26 13:18:09.000000000 +0200
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+static char *glob_query, *glob_laston, *glob_otherchan, *glob_othernick;
+static char *glob_remotebot, *glob_nick;
+static struct slang_header *glob_slang;
+static seendat *glob_seendat;
+static seenreq *glob_seenrequest;
+static int glob_seenrequests, glob_totalnicks, glob_totalbytes;
+
+static void reset_global_vars()
+{
+  glob_query = glob_laston = glob_otherchan = glob_othernick = NULL;
+  glob_remotebot = glob_nick = NULL;
+  glob_seendat = NULL;
+  glob_slang = NULL;
+  glob_seenrequest = NULL;
+  glob_seenrequests = glob_totalnicks = glob_totalbytes = 0;
+}
diff -Nur src/mod/gseen.mod/gseen.c src/mod/gseen.mod/gseen.c
--- ./src/mod/gseen.mod/gseen.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/gseen.c	2002-10-26 14:24:48.000000000 +0200
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+#define MAKING_GSEEN
+#define MODULE_NAME "gseen"
+#define MODULE_VERSION "1.1.1 dev3"
+#define MODULE_NUMVERSION 10100
+#include "../module.h"
+#include "../irc.mod/irc.h"
+#include "../server.mod/server.h"
+#include "../channels.mod/channels.h"
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <time.h> /* for time_t */
+
+#undef global
+static Function *global = NULL, *irc_funcs = NULL, *server_funcs = NULL, *channels_funcs = NULL;
+
+#ifndef EGG_IS_MIN_VER
+#define EGG_IS_MIN_VER(ver)             ((ver) <= 10400)
+#endif
+
+#ifndef EGG_IS_MAX_VER
+#define EGG_IS_MAX_VER(ver)		((ver) >= 10400)
+#endif
+
+#ifndef Context
+#define Context context
+#endif
+
+#ifndef findchan_by_dname
+#define findchan_by_dname findchan
+#endif
+
+#include "gseen.h"
+#include "seenlang.h"
+
+static struct slang_header *coreslangs = NULL;
+static gseenres *results = NULL;
+static seenreq *requests = NULL;
+static ignoredword *ignoredwords = NULL;
+static char *bnsnick = NULL;
+static char *bnschan = NULL;
+static char *seen_reply = NULL;
+static char *temp_wildmatch_host;
+static int numresults = 0;
+static double glob_presearch, glob_aftersearch;
+int numseens, glob_total_queries;
+double glob_total_searchtime;
+
+static char gseenfile[121] = "gseen.dat";
+static char no_pub[121];
+static char quiet_seen[121];
+static char quiet_ai_seen[121];
+static char no_log[121];
+static char ignore_words[1024];
+static char default_slang[21] = "eng";
+static int gseen_numversion = MODULE_NUMVERSION;
+static int save_seens = 60;
+static int save_seens_temp = 1;
+static int expire_seens = 60;
+static int maxseen_thr = 0;
+static int maxseen_time = 0;
+static int seenflood_thr = 0;
+static time_t seenflood_time = 0;
+static int use_handles = 0;
+static int tell_seens = 1;
+static int botnet_seen = 1;
+int fuzzy_search = 1;          // search for the same user under a differnt nick
+static int wildcard_search = 1;// allow wildcard seaching? ("*!*@*.isp.de")
+static int max_matches = 500;  // break if there are more than X matches
+static int hide_secret_chans = 1;	// #chan (+secret) => [secret]
+static int seen_nick_len = 32;
+
+#include "global_vars.c"
+#define SLANG_NOTYPES 1
+#define SLANG_NOFACTS 1
+#define SLANG_NOGETALL 1
+#define SLANG_NOVALIDATE 1
+#include "slang.c"
+#include "slang_gseen_commands.c"
+#include "generic_binary_tree.c"
+#include "seentree.c"
+#include "datahandling.c"
+#include "sensors.c"
+#include "do_seen.c"
+#include "gseencmds.c"
+#include "ai.c"
+#include "misc.c"
+#include "tclcmds.c"
+
+static int gseen_expmem()
+{
+  int size = 0;
+
+  size += seentree_expmem();
+  size += expmem_seenresults();
+  size += expmem_seenreq();
+  size += expmem_ignoredwords();
+  size += slang_expmem(coreslangs);
+  size += slang_glob_expmem();
+  size += slang_chanlang_expmem(chanlangs);
+  if (bnsnick)
+    size += strlen(bnsnick) + 1;
+  if (bnschan)
+    size += strlen(bnschan) + 1;
+  if (seen_reply) {
+    size += strlen(seen_reply) + 1;
+  }
+  return size;
+}
+
+static void free_gseen()
+{
+  seentree_free();
+  slang_free(coreslangs);
+  slang_chanlang_free(chanlangs);
+  if (seen_reply)
+    nfree(seen_reply);
+  return;
+}
+
+/* a report on the module status */
+static void gseen_report(int idx, int details)
+{
+  int size = 0;
+
+  Context;
+  if (details) {
+    size = gseen_expmem();
+    dprintf(idx, "    using %d bytes\n", size);
+  }
+}
+
+static void gseen_minutely ()
+{
+  if (save_seens_temp >= save_seens) {
+    write_seens();
+    save_seens_temp = 1;
+  } else
+    save_seens_temp++;
+}
+
+static void gseen_daily ()
+{
+  Context;
+  purge_seens();
+}
+
+static tcl_strings my_tcl_strings[] =
+{
+  {"gseenfile", gseenfile, 121, 0},
+  {"ai-seen-ignore", ignore_words, 1024, 0},
+  {"no-pub-seens", no_pub, 121, 0},
+  {"quiet-seens", quiet_seen, 121, 0},
+  {"quiet-ai-seens", quiet_ai_seen, 121, 0},
+  {"no-log", no_log, 121, 0},
+  {"no-seendata", no_log, 121, 0},
+  {"default-slang", default_slang, 20, 0},
+  {0, 0, 0, 0}
+};
+
+static tcl_ints my_tcl_ints[] =
+{
+  {"save-seens", &save_seens, 0},
+  {"expire-seens", &expire_seens, 0},
+  {"use-handles", &use_handles, 0},
+  {"tell-seens", &tell_seens, 0},
+  {"botnet-seens", &botnet_seen, 0},
+  {"max-matches", &max_matches, 0},
+  {"fuzzy-search", &fuzzy_search, 0},
+  {"wildcard-search", &wildcard_search, 0},
+  {"hide-secret-chans", &hide_secret_chans, 0},
+  {"seen-nick-len", &seen_nick_len, 0},
+  {0, 0, 0}
+};
+
+static tcl_coups my_tcl_coups[] =
+{
+  {"max-seens", &maxseen_thr, &maxseen_time},
+  {0, 0, 0},
+};
+
+static char *gseen_close()
+{
+  Context;
+  write_seens();
+  slang_glob_free();
+  free_gseen();
+  free_seenreq();
+  free_seenresults();
+  free_ignoredwords();
+  if (bnsnick)
+    nfree(bnsnick);
+  if (bnschan)
+    nfree(bnschan);
+  rem_tcl_strings(my_tcl_strings);
+  rem_tcl_ints(my_tcl_ints);
+  rem_tcl_coups(my_tcl_coups);
+  rem_tcl_commands(mytcls);
+  rem_tcl_commands(gseentcls);
+  rem_tcl_commands(seendebugtcls);
+  rem_tcl_commands(gseentcls);
+  rem_builtins(H_dcc, mydcc);
+  rem_builtins(H_join, seen_join);
+  rem_builtins(H_kick, seen_kick);
+  rem_builtins(H_nick, seen_nick);
+  rem_builtins(H_part, seen_part);
+  rem_builtins(H_sign, seen_sign);
+  rem_builtins(H_splt, seen_splt);
+  rem_builtins(H_rejn, seen_rejn);
+  rem_builtins(H_pub, seen_pub);
+  rem_builtins(H_msg, seen_msg);
+  rem_builtins(H_bot, seen_bot);
+  del_hook(HOOK_MINUTELY, (Function) gseen_minutely);
+  del_hook(HOOK_DAILY, (Function) gseen_daily);
+  module_undepend(MODULE_NAME);
+  return NULL;
+}
+
+char *gseen_start();
+
+static Function gseen_table[] =
+{
+  (Function) gseen_start,
+  (Function) gseen_close,
+  (Function) gseen_expmem,
+  (Function) gseen_report,
+  /* 4 - 7 */
+  (Function) findseens,
+  (Function) free_seenresults,
+  (Function) gseen_duration,
+  (Function) & glob_seendat,
+  (Function) & numresults,
+  (Function) & fuzzy_search,
+  (Function) & numseens,
+  (Function) & glob_total_queries,
+  (Function) & glob_total_searchtime,
+  (Function) & gseen_numversion,
+};
+
+char *gseen_start(Function * global_funcs)
+{
+  global = global_funcs;
+  Context;
+  module_register(MODULE_NAME, gseen_table, 1, 1);
+  if (!(irc_funcs = module_depend(MODULE_NAME, "irc", 1, 0)))
+    return "You need the irc module to use the gseen module.";
+  if (!(server_funcs = module_depend(MODULE_NAME, "server", 1, 0)))
+    return "You need the server module to use the gseen module.";
+  if (!(channels_funcs = module_depend(MODULE_NAME, "channels", 1, 0)))
+    return "You need the channels module to use the gseen module.";
+  if (!module_depend(MODULE_NAME, "eggdrop", 107, 0)) {
+    if (!module_depend(MODULE_NAME, "eggdrop", 106, 0)) {
+      if (!module_depend(MODULE_NAME, "eggdrop", 105, 0)) {
+        if (!module_depend(MODULE_NAME, "eggdrop", 104, 0)) {
+          module_undepend(MODULE_NAME);
+          return "This module requires eggdrop1.4.0 or later";
+        }
+      }
+    }
+  }
+  chanlangs = NULL;
+  coreslangs = NULL;
+  slang_glob_init();
+
+  results = NULL;
+  requests = NULL;
+  ignoredwords = NULL;
+  bnsnick = NULL;
+  bnschan = NULL;
+  seen_reply = NULL;
+
+  numresults = 0;
+  numseens = 0;
+  glob_total_queries = 0;
+  glob_total_searchtime = 0.0;
+  ignore_words[0] = 0;
+  no_pub[0] = 0;
+  quiet_seen[0] = 0;
+  no_log[0] = 0;
+  seentree_init();
+  add_tcl_strings(my_tcl_strings);
+  add_tcl_ints(my_tcl_ints);
+  add_tcl_coups(my_tcl_coups);
+  add_tcl_commands(mytcls);
+  add_tcl_commands(seendebugtcls);
+  add_tcl_commands(gseentcls);
+  add_builtins(H_dcc, mydcc);
+  add_builtins(H_join, seen_join);
+  add_builtins(H_kick, seen_kick);
+  add_builtins(H_nick, seen_nick);
+  add_builtins(H_part, seen_part);
+  add_builtins(H_sign, seen_sign);
+  add_builtins(H_sign, seen_sign);
+  add_builtins(H_splt, seen_splt);
+  add_builtins(H_rejn, seen_rejn);
+  add_builtins(H_pub, seen_pub);
+  add_builtins(H_msg, seen_msg);
+  add_builtins(H_bot, seen_bot);
+  read_seens();
+  add_hook(HOOK_MINUTELY, (Function) gseen_minutely);
+  add_hook(HOOK_DAILY, (Function) gseen_daily);
+#if EGG_IS_MIN_VER(10503)
+  initudef(1, "noseendata", 1);
+  initudef(1, "quietseens", 1);
+  initudef(1, "quietaiseens", 1);
+  initudef(1, "nopubseens", 1);
+#endif
+  glob_slang_cmd_list = slang_commands_list_add(glob_slang_cmd_list, slang_text_gseen_command_table);
+  putlog(LOG_MISC, "*", "gseen.mod v%s loaded.", MODULE_VERSION);
+  return NULL;
+}
diff -Nur src/mod/gseen.mod/gseen.conf src/mod/gseen.mod/gseen.conf
--- ./src/mod/gseen.mod/gseen.conf	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/gseen.conf	2002-10-26 13:17:54.000000000 +0200
@@ -0,0 +1,147 @@
+
+######
+#####
+###   General Settings
+#####
+######
+
+# the file where the seen data will be backuped.
+# WARNING: set this _before_ the module is loaded.
+set gseenfile "gseen.dat"
+
+# now load the module
+loadmodule gseen
+
+# load the English language file
+loadseenslang "en" "English" language/gseen.en.lang
+
+# load the German language file
+loadseenslang "de" "Deutsch" language/gseen.de.lang
+
+# set the default language to english...
+set default-slang "en"
+
+# ... but let #xwp use the german langfile
+setchanseenlang #xwp "de"
+
+# the char that marks public commands (!seen, etc...)
+# "" is a valid option
+set cmdchar "!"
+
+# delete data sets that are older than x days
+set expire-seens 60
+
+# only answer x seen requests in y seconds to prevent flooding
+set max-seens 7:60
+
+# tell users if someone was !seen'ing for them
+set tell-seens 1
+
+# check if the user was online under a different nick
+set fuzzy-search 1
+
+# allow user to include wildcards in the search?
+set wildcard-search 1
+
+# break search if there are more than x matches
+set max-matches 250
+
+# forward a request to other bots, if a !seen returned no result?
+set botnet-seens 1
+
+# store channels, which are +secret on the bot as [secret]?
+set hide-secret-chans 1
+
+# backup the seen data every x minutes
+set save-seens 60
+
+######
+#####
+###   AI Settings
+#####
+######
+
+# this setting configures on which sentences your bot should
+# attempt to do an ai-seen. Each of them is a simple wildcard
+# mask. Set this to "" if you want to deactivate ai-seens or
+# create more precise masks if the bots reacts too often.
+set ai-seen-binds {
+  "${nick}*seen*"
+  "${botnet-nick}*seen*"
+  "${nick}*gesehen*"
+  "${botnet-nick}*gesehen*"
+}
+
+# this is just the same as above, but if triggered it will
+# not do an ai-seen, but display its seen-stats.
+set ai-seenstats-binds {
+  "${nick}*seenstats*"
+  "${botnet-nick}*seenstats*"
+}
+
+# when doing an AI seen, ignore the following words (otherwise
+# the bot might give weird answers like "<bot> nick, bot was last seen..." :)
+set ai-seen-ignore "$nick ${botnet-nick} seen"
+
+######
+#####
+###   special stuff (can be ignored in most cases)
+#####
+######
+
+# if the user is known by the bot, log their handle instead of the nick
+# (not recommended, might cause confusion by the users)
+set use-handles 0
+
+######
+#####
+###   outdated settings (only important for eggdropv1.4 users)
+#####
+######
+
+# channels where you do not want your bot to reply to public queries
+set no-pub-seens ""
+
+# channels where you want your bot to send replies via notice to the user and
+# not to the channel
+set quiet-seens ""
+
+# same as quiet-seens but for AI seen
+set quiet-ai-seens ""
+
+# channels where you do not want your bot to log seen data
+set no-seendata ""
+
+
+###############################################################################
+# end of configuration
+# just ignore everything below ^_^
+###############################################################################
+
+bind chjn - * *chjn:gseen
+bind chpt - * *chpt:gseen
+
+catch "unbind pub - !seen *pub:!seen"
+catch "unbind pub - !seennick *pub:!seennick"
+catch "unbind pub - !seenstats *pub:!seenstats"
+bind pub - ${cmdchar}seen *pub:!seen
+bind pub - ${cmdchar}seennick *pub:!seennick
+bind pub - ${cmdchar}seenstats *pub:!seenstats
+
+foreach bnd [binds pubm] {
+  if {([lindex $bnd 2] == "*pubm:seen") || ([lindex $bnd 2] == "*pub:!seenstats")} {
+    unbind [lindex $bnd 0] [lindex $bnd 1] [lindex $bnd 2] [lindex $bnd 4]
+  }
+}
+
+if {${ai-seen-binds} != ""} {
+  foreach mask ${ai-seen-binds} {
+    bind pubm -|- "% [subst $mask]" *pubm:seen
+  }
+}
+
+if {${ai-seenstats-binds} != ""} {
+  foreach mask ${ai-seenstats-binds} {
+    bind pubm -|- "% [subst $mask]" *pub:!seenstats
+  }
+}
diff -Nur src/mod/gseen.mod/language/gseen.de.lang src/mod/gseen.mod/language/gseen.de.lang
--- ./src/mod/gseen.mod/language/gseen.de.lang	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/language/gseen.de.lang	2002-10-26 13:18:12.000000000 +0200
@@ -0,0 +1,131 @@
+#####################################################################
+#
+# Deutsche Sprachdatei für GSeen.Mod v1.1.0
+#
+# Der Text in dieser Datei kann nach belieben verändert werden. Du
+# kannst Tags hinzufügen oder entfernen, wie es Dir gefällt. Die Tags
+# müssen nicht in einer bestimmten Reihenfolge oder Anzahl vorkommen.
+#
+# Wenn Du mehr als eine Zeile pro ID angibst, dann wird bei der
+# Antwort per Zufall eine daraus ausgewählt. (das funktioniert nicht
+# bei den Zeiteinheiten)
+#
+# Falls Du denkst, daß hier noch ein paar wichtige Tags fehlen, dann
+# schick mir einfach eine email. Vielleicht füge ich sie dann in der
+# nächsten Version hinzu.
+#
+# Eine komplette Liste der Verfügbaren Tags befindet sich am Ende von
+# slang_gseen_commands.c (leider ohne Erklährungen)
+#
+#####################################################################
+
+#
+## Zeiteinheiten
+#
+# jeweils in Singular und Plural
+#
+D 0 Jahr
+D 1 Jahre
+D 2 Woche
+D 3 Wochen
+D 4 Tag
+D 5 Tage
+D 6 Stunde
+D 7 Stunden
+D 8 Minute
+D 9 Minuten
+D 10 Sekunde
+D 11 Sekunden
+# falls ein üngültiger Zeitwert angegeben war, dann wird dieser Text ausgegeben:
+D 12 einiger Zeit
+
+
+#
+## Präfixe
+#
+# Dieses Fragment wird jeweils vor eine Antwort gesetzt. Dadurch
+# ist beispielsweise bei öffentlichen Anfragen ersichtlich, für
+# wen die Antwort ist.
+# Achtung: Die Nummer muss auf jeden Fall definiert werden. Sie muss
+#	   zwar keinen Text beinhalten, aber wenn sie nicht vorhanden
+#	   ist, dann gibt es eine Fehlermeldung
+
+# für Antworten, die in den Channel geschrieben werden:
+10 <?nick/?>, 
+# für Antworten, die per NOTICE an den User geschickt werden:
+11
+# für Antworten auf Anfragen, die per "/msg <bot> seen" erfolgt sind:
+12
+# und für Antworten auf der Partyline:
+13
+
+#
+## Fehlermeldungen
+#
+54 weißt Du was ein Parameter ist? ^_^
+54 ich würde Dir ja gerne helfen, aber solange Du nicht sagst, nach wem Du suchst, kann ich nicht viel tun.
+54 meinst Du nicht, es wäre geschickter zu sagen, nach wem Du überhaupt suchst?
+54 42.
+55 sehe ich etwa wie ein Spiegel aus? ^_^
+55 Spieglein, Spieglein an der Wand...
+55 leidest Du etwa unter multiplen Persönlichkeiten? *eg*
+56 also wenn Du <?query/?> jetzt hier nicht sehen kannst, dann brauchst Du sicherlich eine neue Brille ^_^
+56 ich muss mir unbedingt mal die Tarnkappe von <?query/?> ausleihen. Scheint ja prima zu funktioneren.
+56 schau Dir bitte nochmal ganz genau an, wer grade alles im Channel ist.
+57 Tut mir leid, aber Wildcards ('?', oder '*') sind bei der Suche nicht erlaubt.
+58 Öhm... naja... etwas arg lang, dieser Nick... :)
+
+#
+## Kein Ergebnis
+#
+65 Ich kann mich nicht daran erinnern, <?query/?> gesehen zu haben...
+65 <?query/?>? Hmm... ich bin mir nicht sicher... vielleicht... eventuell... nein, kenne ich nicht.
+65 der Name sagt mir nichts. Hast Du Dich vielleicht vertippt?
+66 Ich hab' <?query/?> seit <?laston/?> nicht mehr gesehen.
+67 Sorry, aber zu deiner Anfrage passt nichts in meiner Datenbank :(
+68 Autschi, das gab viel zu viele Ergebnisse. Bitte formuliere deine Suche etwas genauer.
+
+73 <?query/?> ist grade unter dem Nick "<?othernick/?>" in diesem Channel zu finden.
+74 <?query/?> ist gerade in <?otherchan/?>.
+75 Deine Anfrage führte zu genau einem Ergebnis:
+76 Immerhin <?numresults/?> Treffer ergab deine Anfrage:
+77 Wow, auf deine Anfrage passen sogar <?numresults/?> Einträge in meiner Datenbank! Dies sind die 5 aktuellsten:
+
+#
+## falls ein anderer Bot etwas gefunden hat:
+#
+85 <?remotebot/?> sagt: 
+
+#
+## die eigentliche Information
+#
+101 Ich habe <?snick/?> (<?shost/?>) zuletzt <?schan/?> vor <?swhen/?> betreten sehen (<?stime/?>). <?snick/?> ist noch immer da.
+121 Ich habe <?snick/?> (<?shost/?>) zuletzt <?schan/?> vor <?swhen/?> betreten sehen (<?stime/?>), aber <?snick/?> verschwand mysteriöserweise.
+102 Ich habe <?snick/?> (<?shost/?>) zuletzt <?schan/?> vor <?swhen/?> nach <?spent/?> verchatteter Zeit verlassen sehen (<?stime/?>)
+103 Ich habe <?snick/?> (<?shost/?>) zuletzt in <?schan/?> gesehen, als er/sie vor <?swhen/?> (<?stime/?>) nach <?spent/?> das IRC verließ ("<?smsg/?>").
+104 Zuletzt habe ich <?snick/?> (<?shost/?>) vor <?swhen/?> in <?schan/?> gesehen, den Nick zu <?snick2/?> wechselnd. <?snick2/?> ist noch immer dort.
+124 <?snick/?> (<?shost/?>) was last seen changing his/her nick to <?snick2/?> on <?schan/?> <?swhen/?> ago (<?stime/?>), but <?snick2/?> mysteriously dematerialized.
+105 Zuletzt habe ich <?snick/?> (<?shost/?>) vor <?swhen/?> in <?schan/?> gesehen, den Nick von <?snick2/?> wechselnd. <?snick/?> ist noch immer dort.
+125 <?snick/?> (<?shost/?>) was last seen changing his/her nick from <?snick2/?> on <?schan/?> <?swhen/?> ago (<?stime/?>), but <?snick/?> mysteriously dematerialized.
+106 Zuletzt habe ich <?snick/?> (<?shost/?>) gesehen, als er vor <?swhen/?> (<?stime/?>) von <?punisher/?> aus <?schan/?> gejagt wurde. (<?kickreason/?>)
+107 <?snick/?> (<?shost/?>) habe ich zuletzt vor <?swhen/?> gesehen, als er/sie von <?schan/?> aus in einem Netsplit verschwand.
+108 <?snick/?> (<?shost/?>) habe ich zuletzt vor <?swhen/?> gesehen, als er/sie nach einem Netsplit in <?schan/?> zurück kam. <?snick/?> ist noch immer dort.
+128 <?snick/?> (<?shost/?>) habe ich zuletzt vor <?swhen/?> gesehen, als er/sie nach einem Netsplit in <?schan/?> zurück kam. Allerdings konnte <?snick/?> dem Gott der Netsplits nicht endgültig entkommen und ist wieder verschollen...
+109 <?snick/?> was last seen joining the botnet channel <?schan/?> on <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+129 <?snick/?> was last seen joining the partyline on <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+110 <?snick/?> was last seen leaving the botnet channel <?schan/?> from <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+130 <?snick/?> was last seen leaving the partyline from <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+140 <?snick/?> (<?shost/?>) was last seen on <?schan/?> <?swhen/?> ago (<?stime/?>).
+
+#
+## Seen-Mitteilungen
+#
+170 <?rnick/?> (<?rhost/?>) scheint vor <?rwhen/?> (<?rtime/?>) in <?rchan/?> auf der Suche nach Dir gewesen zu sein.
+171 <?requests/?> Leute haben sich nach Dir erkundigt:
+172 Der/die letzte war <?rnick/?> (<?rhost/?>) in <?rchan/?> vor <?rwhen/?> (<?rtime/?>).
+
+#
+## Statistiken
+#
+180 Momentan sind <?totalnicks/?> Nicks in meiner Datenbank. Gesamter Speicherverbrauch: <?totalbytes/?> Bytes
+180 In meiner Datenbank befinden sich <?totalnicks/?> Nicks und verbrauchen <?totalbytes/?> Bytes Speicher.
diff -Nur src/mod/gseen.mod/language/gseen.en.lang src/mod/gseen.mod/language/gseen.en.lang
--- ./src/mod/gseen.mod/language/gseen.en.lang	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/language/gseen.en.lang	2002-10-26 13:18:13.000000000 +0200
@@ -0,0 +1,131 @@
+#####################################################################
+#
+# Default English langfile for GSeen.Mod v1.1.0
+#
+# Just edit the text below to fit your needs. You can add or remove
+# any tag just like you want, they do not need to appear in a special
+# order (or number).
+#
+# If you enter more than one line per ID, then a random one will be
+# chosen for each reply. (this does not work for the time strings)
+#
+# If you think you need more tags, just email me and maybe I'll add
+# them in the next release.
+#
+# A complete list of available Tags can be found at the end of the
+# file slang_gseen_commands.c (unfortunately, it does not contain any
+# descriptions for the tags)
+#
+#####################################################################
+
+#
+## time string
+#
+# each time string in singular and plural
+#
+D 0 year
+D 1 years
+D 2 week
+D 3 weeks
+D 4 day
+D 5 days
+D 6 hour
+D 7 hours
+D 8 minute
+D 9 minutes
+D 10 second
+D 11 seconds
+# if an invalid time value was supplied, output the following string
+D 12 some time
+
+#
+## Prefixes
+#
+# These are the prefixes of the replies. By default, there's only
+# a prefix for public requests (so you know for whom the answer is),
+# but you can also define prefixes for other requests.
+
+# for replies in the channel:
+10 <?nick/?>, 
+# for replies via notice:
+11
+# for replies via PRIVMSG
+12
+# for replies on the partyline
+13
+
+#
+## error messages
+#
+54 do you know what a parameter is?
+54 don't you think it would be more reasonable to say for whom you are searching?
+54 42.
+55 do I look like a mirror? ^_^
+55 mirror mirror on the wall...
+55 do you have a split personality? *eg*
+56 if you can't see <?query/?> here right now, you probably need new glasses. ^_^
+56 please look a bit closer at the memberlist of this channel.
+57 I'm sorry, but wildcards ('?' or '*') are not allowed in a search.
+58 Hum... don't you think this nick is a bit long? ^_^
+58 you know that the length of nicks is limited, don't you?
+
+#
+## no result
+#
+65 I don't remember seeing <?query/?>.
+65 <?query/?>? hmm... I'm trying to remember... maybe... I'm not sure... no. I don't remember <?query/?>.
+66 I haven't seen <?query/?> for <?laston/?>.
+67 I found no matches to your query.
+67 I'm sorry, but your search didn't return any results.
+68 Ouch, your search returned way too many matches. Please refine it.
+
+#
+## victim is online
+#
+73 <?query/?> is <?othernick/?>, who is on this channel right now.
+74 <?query/?> is on <?otherchan/?> right now.
+
+#
+## results found
+#
+75 I found one match to your query:
+76 I found <?numresults/?> matches to your query:
+77 I found <?numresults/?> matches to your query. These are the 5 most recent ones:
+
+#
+## results found by another bot in the botnet
+#
+85 <?remotebot/?> says: 
+
+#
+## the core info
+#
+101 <?snick/?> (<?shost/?>) was last seen joining <?schan/?> <?swhen/?> ago (<?stime/?>). <?snick/?> is still there.
+121 <?snick/?> (<?shost/?>) was last seen joining <?schan/?> <?swhen/?> ago (<?stime/?>), but <?snick/?> mysteriously dematerialized.
+102 <?snick/?> (<?shost/?>) was last seen parting <?schan/?> <?swhen/?> ago (<?stime/?>), after spending <?spent/?> there.
+103 <?snick/?> (<?shost/?>) was last seen quitting <?schan/?> <?swhen/?> ago (<?stime/?>) stating "<?smsg/?>" after spending <?spent/?> there.
+104 <?snick/?> (<?shost/?>) was last seen changing his/her nick to <?snick2/?> on <?schan/?> <?swhen/?> ago (<?stime/?>). <?snick2/?> is still there.
+124 <?snick/?> (<?shost/?>) was last seen changing his/her nick to <?snick2/?> on <?schan/?> <?swhen/?> ago (<?stime/?>), but <?snick2/?> mysteriously dematerialized.
+105 <?snick/?> (<?shost/?>) was last seen changing his/her nick from <?snick2/?> on <?schan/?> <?swhen/?> ago (<?stime/?>). <?snick/?> is still there.
+125 <?snick/?> (<?shost/?>) was last seen changing his/her nick from <?snick2/?> on <?schan/?> <?swhen/?> ago (<?stime/?>), but <?snick/?> mysteriously dematerialized.
+106 <?snick/?> (<?shost/?>) was last seen being kicked from <?schan/?> by <?punisher/?> (<?kickreason/?>) <?swhen/?> ago (<?stime/?>), after spending <?spent/?> there.
+107 <?snick/?> (<?shost/?>) was last seen splitting from <?schan/?> <?swhen/?> ago (<?stime/?>), after spending <?spent/?> there.
+108 <?snick/?> (<?shost/?>) was last seen rejoining <?schan/?> from a netsplit <?swhen/?> ago (<?stime/?>) <?snick/?> is still there.
+128 <?snick/?> (<?shost/?>) was last seen rejoining <?schan/?> from a netsplit <?swhen/?> ago (<?stime/?>), but the god of netsplits didn't let him escape, so he's not here now.
+109 <?snick/?> was last seen joining the botnet channel <?schan/?> on <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+129 <?snick/?> was last seen joining the partyline on <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+110 <?snick/?> was last seen leaving the botnet channel <?schan/?> from <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+130 <?snick/?> was last seen leaving the partyline from <?bnbot/?> <?swhen/?> ago (<?stime/?>).
+140 <?snick/?> (<?shost/?>) was last seen on <?schan/?> <?swhen/?> ago (<?stime/?>).
+
+#
+## seen notification
+#
+170 <?rnick/?> (<?rhost/?>) was looking for you on <?rchan/?> <?rwhen/?> ago (<?rtime/?>).
+171 There have been <?requests/?> users looking for you:
+172 The last one was <?rnick/?> (<?rhost/?>) on <?rchan/?> <?rwhen/?> ago (<?rtime/?>).
+
+#
+## seen stats
+#
+180 I'm currently tracking <?totalnicks/?> nicks using <?totalbytes/?> bytes.
diff -Nur src/mod/gseen.mod/gseen.h src/mod/gseen.mod/gseen.h
--- ./src/mod/gseen.mod/gseen.h	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/gseen.h	2002-10-26 13:17:55.000000000 +0200
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+/* #define USE_MEMDEBUG 1 */
+
+#define SEEN_JOIN 1
+#define SEEN_PART 2
+#define SEEN_SIGN 3
+#define SEEN_NICK 4
+#define SEEN_NCKF 5
+#define SEEN_KICK 6
+#define SEEN_SPLT 7
+#define SEEN_REJN 8
+#define SEEN_CHPT 9
+#define SEEN_CHJN 10
+
+typedef struct gseen_data {
+  int type;
+  char *nick;
+  char *host;
+  char *chan;
+  char *msg;
+  time_t when;
+  int spent;
+} seendat;
+
+typedef struct gseen_result {
+  struct gseen_result *next;
+  seendat *seen;
+} gseenres;
+
+typedef struct gseen_requests {
+  struct gseen_requests *next;
+  char *who;
+  char *host;
+  char *chan;
+  time_t when;
+} seenreq_by;
+
+typedef struct gseen_request {
+  struct gseen_request *next;
+  char *nick;
+  struct gseen_requests *by;
+} seenreq;
+
+typedef struct gseen_ignorewords {
+  struct gseen_ignorewords *next;
+  char *word;
+} ignoredword;
+
+#ifdef MAKING_GSEEN
+static int gseen_expmem();
+static void free_gseen();
+static int get_spent(char *, char *);
+static void write_seens();
+static void read_seens();
+static char *do_seen(char *, char *, char *, char *, int);
+static void add_seenresult(seendat *);
+static int expmem_seenresults();
+static void free_seenresults();
+static void sortresults();
+static char *do_seennick(seendat *);
+static int onchan(char *, char *);
+static char *handonchan(char *, char *);
+static struct chanset_t *onanychan(char *);
+static struct chanset_t *handonanychan(char *);
+static char *do_seenstats();
+static void add_seenreq(char *, char *, char *, char *, time_t);
+static int expmem_seenreq();
+static void free_seenreq();
+static void sortrequests(seenreq *);
+static void report_seenreq(char *, char *);
+static int count_seenreq(seenreq_by *b);
+static int expmem_ignoredwords();
+static void free_ignoredwords();
+static void add_ignoredword(char *word);
+static int word_is_ignored(char *word);
+static void purge_seens();
+static int seenflood();
+static int secretchan(char *);
+static int nopub(char *);
+static int quietseen(char *);
+static int quietaiseens(char *);
+static int nolog(char *);
+static void start_seentime_calc();
+static void end_seentime_calc();
+#endif
+
+
+#ifdef MAKING_GSEEN
+
+// tree stuff
+static void maskstricthost(const char *, char *);
+#endif
+
+// interface for webseen
+#define WS_OK 0
+#define WS_NORESULT 1
+#define WS_NOPARAM 2
+#define WS_NOWILDCARDS 3
+#define WS_TOOLONGNICK 4
+#define WS_TOOMANYMATCHES 5
+#define WS_TOOLONGHOST 6
+
+#ifndef MAKING_GSEEN
+#define findseens ((gseenres *(*)(char *, int *, int))gseen_funcs[4])
+#define free_seenresults ((void (*)())gseen_funcs[5])
+#define gseen_duration ((char *(*)(int))gseen_funcs[6])
+#define numresults (*(int *)(gseen_funcs[12]))
+#define fuzzy_search (*(int *)(gseen_funcs[13]))
+#define numseens (*(int *)(gseen_funcs[15]))
+#define glob_total_queries (*(int *)(gseen_funcs[16]))
+#define glob_total_searchtime (*(double *)(gseen_funcs[17]))
+#define gseen_numversion (*(int *)(gseen_funcs[19]))
+#else
+static gseenres *findseens(char *, int *, int);
+static char *gseen_duration(int);
+#endif
+
+#ifdef MAKING_GSEEN
+
+#ifdef malloc
+#undef malloc
+#endif
+#ifdef free
+#undef free
+#endif
+#ifdef realloc
+#undef realloc
+#endif
+
+#ifdef USE_MEMDEBUG
+#define my_malloc nmalloc
+#define my_free nfree
+#define my_realloc nrealloc
+#else
+#define my_malloc malloc
+#define my_free free
+#define my_realloc realloc
+#endif
+
+#endif
diff -Nur src/mod/gseen.mod/gseencmds.c src/mod/gseen.mod/gseencmds.c
--- ./src/mod/gseen.mod/gseencmds.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/gseencmds.c	2002-10-26 13:17:56.000000000 +0200
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+#define PREFIX_LENGTH 20
+
+static char reply_prefix[PREFIX_LENGTH + 1];
+#define set_prefix(x) strncpy(reply_prefix, x, PREFIX_LENGTH); \
+	reply_prefix[PREFIX_LENGTH] = 0;
+
+static int seenflood()
+{
+  if (!maxseen_thr || !maxseen_time)
+    return 0;
+  if ((now - seenflood_time) > maxseen_time) {
+    seenflood_time = now;
+    seenflood_thr = 0;
+  }
+  seenflood_thr++;
+  if (seenflood_thr > maxseen_thr)
+    return 1;
+  else
+    return 0;
+}
+
+static int nopub(char *chan)
+{
+  char buf[121], *b;
+
+  Context;
+  strncpy(buf, no_pub, 120);
+  buf[120] = 0;
+  b = buf;
+  while (b[0])
+    if (!strcasecmp(chan, newsplit(&b)))
+      return 1;
+#if EGG_IS_MIN_VER(10503)
+  if (ngetudef("nopubseens", chan))
+    return 1;
+#endif
+  return 0;
+}
+
+static int quietseen(char *chan)
+{
+  char buf[121], *b;
+
+  Context;
+  strncpy(buf, quiet_seen, 120);
+  buf[120] = 0;
+  b = buf;
+  while (b[0])
+    if (!strcasecmp(chan, newsplit(&b)))
+      return 1;
+#if EGG_IS_MIN_VER(10503)
+  if (ngetudef("quietseens", chan))
+    return 1;
+#endif
+  return 0;
+}
+
+static int cmd_seen(struct userrec *u, int idx, char *par)
+{
+  char *query;
+  
+  Context;
+  if (seenflood())
+    return 0;
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, default_slang);
+  glob_nick = dcc[idx].nick;
+  query = newsplit(&par);
+  glob_query = query;
+  set_prefix(SLDCCPREFIX);
+  putlog(LOG_CMDS, "*", "#%s# seen %s", dcc[idx].nick, par);
+  dprintf(idx, "%s%s\n", reply_prefix, do_seen(query, dcc[idx].nick,
+  	  dcc[idx].host, "[partyline]", botnet_seen));
+  return 0;
+}
+
+static int cmd_seenstats(struct userrec *u, int idx, char *par)
+{
+  Context;
+  if (seenflood())
+    return 0;
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, default_slang);
+  glob_nick = dcc[idx].nick;
+  set_prefix(SLDCCPREFIX);
+  putlog(LOG_CMDS, "*", "#%s# seenstats", dcc[idx].nick);
+  dprintf(idx, "%s%s\n", reply_prefix, do_seenstats());
+  return 0;
+}
+
+static int cmd_purgeseens(struct userrec *u, int idx, char *par)
+{
+  Context;
+  purge_seens();
+  putlog(LOG_CMDS, "*", "#%s# purgeseens", dcc[idx].nick);
+  return 0;
+}
+
+static int pub_seen(char *nick, char *host, char *hand,
+        char *channel, char *text)
+{
+  char *dest;
+#if EGG_IS_MIN_VER(10500)
+  struct chanset_t *chan;
+#endif
+
+  Context;
+  if (seenflood() || nopub(channel))
+    return 0;
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, channel));
+  glob_nick = nick;
+  putlog(LOG_CMDS, "*", "<<%s>> !%s! seen %s", nick, hand, text);
+  if (quietseen(channel)) {
+    set_prefix(SLNOTPREFIX);
+    dprintf(DP_HELP, "NOTICE %s :%s%s\n", nick, reply_prefix,
+           do_seen(newsplit(&text), nick, host, channel, botnet_seen));
+    return 0;
+  }
+#if EGG_IS_MIN_VER(10500)
+  chan = findchan_by_dname(channel);
+  if (chan)
+    dest = chan->name;
+  else
+    dest = channel;
+#else
+  dest = channel;
+#endif
+  set_prefix(SLPUBPREFIX);
+  dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", dest, reply_prefix,
+  	  do_seen(newsplit(&text), nick, host, channel, botnet_seen));
+  return 0;
+}
+
+static int pub_seenstats(char *nick, char *host, char *hand,
+        char *channel, char *text)
+{
+  char *dest;
+#if EGG_IS_MIN_VER(10500)
+  struct chanset_t *chan;
+#endif
+
+  Context;
+  if (seenflood())
+    return 0;
+  if (nopub(channel))
+    return 0;
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, channel));
+  glob_nick = nick;
+  putlog(LOG_CMDS, "*", "<<%s>> !%s! seenstats", nick, hand);
+  if (quietseen(channel)) {
+    set_prefix(SLNOTPREFIX);
+    dprintf(DP_HELP, "NOTICE %s :%s%s\n", nick, reply_prefix, do_seenstats());
+    return 0;
+  }
+#if EGG_IS_MIN_VER(10500)
+  chan = findchan_by_dname(channel);
+  if (chan)
+    dest = chan->name;
+  else
+    dest = channel;
+#else
+  dest = channel;
+#endif
+  set_prefix(SLPUBPREFIX);
+  dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", dest, reply_prefix, do_seenstats());
+  return 1;
+}
+
+static int msg_seen(char *nick, char *uhost, struct userrec *u, char *text)
+{
+  Context;
+  if (seenflood())
+    return 0;
+  reset_global_vars();
+  glob_slang = slang_getbynick(coreslangs, nick);
+  glob_nick = nick;
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! seen %s", nick, uhost, u ? u->handle : "*", text);
+  set_prefix(SLMSGPREFIX);
+  dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", nick, reply_prefix,
+  	  do_seen(newsplit(&text), nick, uhost, "[/msg]", botnet_seen));
+  return 1;
+}
+
+static int pub_seennick(char *nick, char *host, char *hand,
+        char *channel, char *text)
+{
+  seendat *l;
+  char *dest;
+#if EGG_IS_MIN_VER(10500)
+  struct chanset_t *chan;
+#endif
+
+  Context;
+  if (seenflood())
+    return 0;
+  if (nopub(channel))
+    return 0;
+  putlog(LOG_CMDS, "*", "<<%s>> !%s! seennick %s", nick, hand, text);
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, channel));
+  glob_nick = nick;
+#if EGG_IS_MIN_VER(10500)
+  chan = findchan_by_dname(channel);
+  if (chan)
+    dest = chan->name;
+  else
+    dest = channel;
+#else
+  dest = channel;
+#endif
+  text = newsplit(&text);
+  l = findseen(text);
+  if (!l) {
+    glob_query = text;
+    if (quietseen(channel)) {
+      set_prefix(SLNOTPREFIX);
+      dprintf(DP_HELP, "NOTICE %s :%s%s\n", nick, reply_prefix, SLNOTSEEN);
+    } else {
+      set_prefix(SLPUBPREFIX);
+      dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", dest, reply_prefix, SLNOTSEEN);
+    }
+    return 0;
+  }
+  if (quietseen(channel)) {
+    set_prefix(SLNOTPREFIX);
+    dprintf(DP_HELP, "NOTICE %s :%s%s\n", nick, reply_prefix, do_seennick(l));
+  } else {
+    set_prefix(SLPUBPREFIX);
+    dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", dest, reply_prefix, do_seennick(l));
+  }
+  return 0;
+}
+
+static int msg_seennick(char *nick, char *uhost, struct userrec *u, char *text)
+{
+  seendat *l;
+
+  Context;
+  if (seenflood())
+    return 0;
+  putlog(LOG_CMDS, "*", "(%s!%s) !%s! seennick %s", nick, uhost, u ? u->handle : "*", text);
+  reset_global_vars();
+  glob_slang = slang_getbynick(coreslangs, nick);
+  glob_nick = nick;
+  set_prefix(SLMSGPREFIX);
+  text = newsplit(&text);
+  l = findseen(text);
+  if (!l) {
+    glob_query = text;
+    dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", nick, reply_prefix, SLNOTSEEN);
+    return 0;
+  }
+  dprintf(DP_HELP, "PRIVMSG %s :%s%s\n", nick, reply_prefix, do_seennick(l));
+  return 0;
+}
+
+static int cmd_seennick(struct userrec *u, int idx, char *text)
+{
+  seendat *l;
+
+  Context;
+  if (seenflood())
+    return 0;
+  putlog(LOG_CMDS, "*", "#%s# seennick %s", dcc[idx].nick, text);
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, default_slang);
+  glob_nick = dcc[idx].nick;
+  set_prefix(SLMSGPREFIX);
+  text = newsplit(&text);
+  l = findseen(text);
+  if (!l) {
+    glob_query = text;
+    dprintf(idx, "%s%s\n", reply_prefix, SLNOTSEEN);
+    return 0;
+  }
+  dprintf(idx, "%s%s\n", reply_prefix, do_seennick(l));
+  return 0;
+}
+
+static int bot_gseen_req(char *bot, char *code, char *par)
+{
+  char *mask, *nick, *uhost, *chan, *reply;
+  char tosend[256];
+  int i;
+
+  Context;
+  if (seenflood())
+    return 0;
+  i = nextbot(bot);
+  if (i < 0) {
+    debug1("Couldn't answer botnet-seen-request from %s: no such bot", bot);
+    return 0;
+  }
+  mask = newsplit(&par);
+  nick = newsplit(&par);
+  uhost = newsplit(&par);
+  chan = newsplit(&par);
+  reset_global_vars();
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, chan));
+  glob_nick = nick;
+  reply = do_seen(mask, nick, uhost, chan, -1);
+  if (!reply)
+    return 0;
+  if ((strlen(nick) + strlen(chan) + strlen(reply)) < 255) {
+    sprintf(tosend, "gseen_rep %s %s %s", nick, chan, reply);
+    botnet_send_zapf(i, botnetnick, bot, tosend);
+  }
+  return 0;
+}
+
+static int bot_gseen_rep(char *bot, char *code, char *par)
+{
+  char *nick, *chan, *reply;
+  int i;
+
+  Context;
+  if (seenflood())
+    return 0;
+  if (!bnsnick || !bnschan) {
+    if (bnsnick)
+      nfree(bnsnick);
+    if (bnschan)
+      nfree(bnschan);
+    bnsnick = bnschan = NULL;
+    return 0;
+  }
+  nick = newsplit(&par);
+  chan = newsplit(&par);
+  reset_global_vars();
+  glob_remotebot = bot;
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, chan));
+  glob_nick = nick;
+  reply = par;
+  if (strcmp(nick, bnsnick) || strcmp(chan, bnschan))
+    return 0; /* unwanted reply */
+  if (findchan(chan)) {
+    if (nopub(chan)) {
+      nfree(bnsnick);
+      nfree(bnschan);
+      bnsnick = bnschan = NULL;
+      debug1("%s is nopub, bns-reply dropped", chan);
+      return 0;
+    }
+    if (quietseen(chan)) {
+      set_prefix(SLNOTPREFIX);
+      dprintf(DP_HELP, "NOTICE %s :%s%s%s\n", nick, reply_prefix, SLRBOTSAYS, reply);
+    } else {
+      set_prefix(SLPUBPREFIX);
+      dprintf(DP_HELP, "PRIVMSG %s :%s%s%s\n", chan, reply_prefix, SLRBOTSAYS, reply);
+    }
+  } else if (!strcmp(chan, "[/msg]")) {
+    set_prefix(SLMSGPREFIX);
+    dprintf(DP_HELP, "PRIVMSG %s :%s%s%s\n", nick, reply_prefix, SLRBOTSAYS, reply);
+  } else if (!strcmp(chan, "[partyline]")) {
+    for (i = 0; i < dcc_total; i++) {
+      if ((!strcasecmp(nick, dcc[i].nick)) &&
+         (dcc[i].type->flags & DCT_SIMUL)) {
+	set_prefix(SLDCCPREFIX);
+        dprintf(i, "%s%s%s\n", reply_prefix, SLRBOTSAYS, reply);
+        break;
+      }
+    }
+  } else
+    debug1("Couldn't send received bns answer, no such chan %s", chan);
+  nfree(bnsnick);
+  nfree(bnschan);
+  bnsnick = bnschan = NULL;
+  return 0;
+}
+
+static cmd_t mydcc[] =
+{
+  {"seen", "-|-", cmd_seen, NULL},
+  {"seenstats", "-|-", cmd_seenstats, NULL},
+  {"purgeseens", "m", cmd_purgeseens, NULL},
+  {"seennick", "-|-", cmd_seennick, NULL},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_pub[] =
+{
+  {"!seen", "", pub_seen, 0},
+  {"!seenstats", "", pub_seenstats, 0},
+  {"!seennick", "", pub_seennick, 0},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_msg[] =
+{
+  {"seen", "", msg_seen, 0},
+  {"seennick", "", msg_seennick, 0},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_bot[] =
+{
+  {"gseen_req", "", bot_gseen_req, 0},
+  {"gseen_rep", "", bot_gseen_rep, 0},
+  {0, 0, 0, 0}
+};
diff -Nur src/mod/gseen.mod/misc.c src/mod/gseen.mod/misc.c
--- ./src/mod/gseen.mod/misc.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/misc.c	2002-10-26 13:17:57.000000000 +0200
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+/* maskstricthost():
+ * basically the same as maskhost() from src/misc.c, but _never_ stripts
+ * "~+-^=" off the host
+ * maskhost() version: * $Id: misc.c,v 1.30 2000/10/27 19:27:32 fabian Exp $
+ */
+static void maskstricthost(const char *s, char *nw)
+{
+  register const char *p, *q, *e, *f;
+  int i;
+
+  *nw++ = '*';
+  *nw++ = '!';
+  p = (q = strchr(s, '!')) ? q + 1 : s;
+  /* Strip of any nick, if a username is found, use last 8 chars */
+  if ((q = strchr(p, '@'))) {
+    int fl = 0;
+
+    if ((q - p) > 9) {
+      nw[0] = '*';
+      p = q - 7;
+      i = 1;
+    } else
+      i = 0;
+    while (*p != '@') {
+      if (!fl && strchr("~+-^=", *p)) {
+//        if (strict_host)
+      nw[i] = '?';
+//    else
+//      i--;
+      } else
+    nw[i] = *p;
+      fl++;
+      p++;
+      i++;
+    }
+    nw[i++] = '@';
+    q++;
+  } else {
+    nw[0] = '*';
+    nw[1] = '@';
+    i = 2;
+    q = s;
+  }
+  nw += i;
+  e = NULL;
+  /* Now q points to the hostname, i point to where to put the mask */
+  if ((!(p = strchr(q, '.')) || !(e = strchr(p + 1, '.'))) && !strchr(q, ':'))
+    /* TLD or 2 part host */
+    strcpy(nw, q);
+  else {
+    if (e == NULL) {        /* IPv6 address?        */
+      const char *mask_str;
+
+      f = strrchr(q, ':');
+      if (strchr(f, '.')) { /* IPv4 wrapped in an IPv6? */
+    f = strrchr(f, '.');
+    mask_str = ".*";
+      } else            /* ... no, true IPv6.       */
+    mask_str = ":*";
+      strncpy(nw, q, f - q);
+      /* No need to nw[f-q] = 0 here, as the strcpy below will
+       * terminate the string for us.
+       */
+      nw += (f - q);
+      strcpy(nw, mask_str);
+    } else {
+      for (f = e; *f; f++);
+      f--;
+      if (*f >= '0' && *f <= '9') { /* Numeric IP address */
+    while (*f != '.')
+      f--;
+    strncpy(nw, q, f - q);
+    /* No need to nw[f-q] = 0 here, as the strcpy below will
+     * terminate the string for us.
+     */
+    nw += (f - q);
+    strcpy(nw, ".*");
+      } else {              /* Normal host >= 3 parts */
+    /*    a.b.c  -> *.b.c
+     *    a.b.c.d ->  *.b.c.d if tld is a country (2 chars)
+     *             OR   *.c.d if tld is com/edu/etc (3 chars)
+     *    a.b.c.d.e -> *.c.d.e   etc
+     */
+    const char *x = strchr(e + 1, '.');
+
+    if (!x)
+      x = p;
+    else if (strchr(x + 1, '.'))
+      x = e;
+    else if (strlen(x) == 3)
+      x = p;
+    else
+      x = e;
+    sprintf(nw, "*%s", x);
+      }
+    }
+  }
+}
diff -Nur src/mod/gseen.mod/seenlang.h src/mod/gseen.mod/seenlang.h
--- ./src/mod/gseen.mod/seenlang.h	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/seenlang.h	2002-10-26 13:17:58.000000000 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+#define SLPUBPREFIX	getslang(10)
+#define SLNOTPREFIX	getslang(11)
+#define SLMSGPREFIX	getslang(12)
+#define SLDCCPREFIX	getslang(13)
+
+#define SLNOPARAM       getslang(54)
+#define SLMIRROR        getslang(55)
+#define SLONCHAN        getslang(56)
+#define SLNOWILDCARDS   getslang(57)
+#define SLTOOLONGNICK   getslang(58)
+
+#define SLNOTSEEN       getslang(65)
+#define SLPOORSEEN      getslang(66)
+#define SLNOMATCH       getslang(67)
+#define SLTOOMANYMATCHES getslang(68)
+
+#define SLHANDONCHAN    getslang(73)
+#define SLONOTHERCHAN   getslang(74)
+#define SLONEMATCH      getslang(75)
+#define SLLITTLEMATCHES getslang(76)
+#define SLMANYMATCHES   getslang(77)
+
+#define SLRBOTSAYS      getslang(85)
+
+#define SLYEAR          getdur(0)
+#define SLYEARS         getdur(1)
+#define SLWEEK          getdur(2)
+#define SLWEEKS         getdur(3)
+#define SLDAY           getdur(4)
+#define SLDAYS          getdur(5)
+#define SLHOUR          getdur(6)
+#define SLHOURS         getdur(7)
+#define SLMINUTE        getdur(8)
+#define SLMINUTES       getdur(9)
+#define SLSECOND        getdur(10)
+#define SLSECONDS       getdur(11)
+#define SLSOMETIME      getdur(12)
+
+#define SLONELOOK       getslang(170)
+#define SLMORELOOKS     getslang(171)
+#define SLLASTLOOK      getslang(172)
+
+#define SLSEENSTATS     getslang(180)
diff -Nur src/mod/gseen.mod/seentree.c src/mod/gseen.mod/seentree.c
--- ./src/mod/gseen.mod/seentree.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/seentree.c	2002-10-26 13:18:10.000000000 +0200
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+static struct generic_binary_tree seentree;
+
+static void seentree_init();
+static int seentree_expmem();
+static void seentree_free();
+static int compareseens(void *, void *);
+static int expmemseen(void *);
+static void add_seen(int, char *, char *, char *, char *,
+                     time_t, int);
+static void freeseen(void *);
+static seendat *findseen(char *);
+static void wildmatch_seens(char *, char *, int);
+static void process_wildmatch_seens(void *);
+static void write_seen_tree(void *);
+static void purge_seen_tree(void *);
+static int count_seens();
+static void _count_seens(void *);
+
+
+static void seentree_init()
+{
+  seentree.root = NULL;
+  seentree.comparedata = compareseens;
+  seentree.expmemdata = expmemseen;
+  seentree.freedata = freeseen;
+}
+
+static int seentree_expmem()
+{
+  return btree_expmem(&seentree);
+}
+
+static void seentree_free()
+{
+  btree_freetree(&seentree);
+  seentree.root = NULL;
+}
+
+static int compareseens(void *first, void *second)
+{
+  return rfc_casecmp(((seendat *) first)->nick, ((seendat *) second)->nick);
+}
+
+// add another entry to the tree
+static void add_seen(int type, char *nick, char *host, char *chan, char *msg,
+                     time_t when, int spent)
+{
+  seendat *newseen;
+
+  newseen = nmalloc(sizeof(seendat));
+  newseen->type = type;
+  newseen->nick = nmalloc(strlen(nick) + 1);
+  strcpy(newseen->nick, nick);
+  newseen->host = nmalloc(strlen(host) + 1);
+  strcpy(newseen->host, host);
+  newseen->chan = nmalloc(strlen(chan) + 1);
+  strcpy(newseen->chan, chan);
+  newseen->msg = nmalloc(strlen(msg) + 1);
+  strcpy(newseen->msg, msg);
+  newseen->when = when;
+  newseen->spent = spent;
+  btree_add(&seentree, newseen);
+}
+
+static void freeseen(void *what)
+{
+  seendat *s = (seendat *) what;
+  
+  Assert(s);
+  Assert(s->nick);
+  Assert(s->host);
+  Assert(s->chan);
+  Assert(s->msg);
+
+  nfree(s->nick);
+  nfree(s->host);
+  nfree(s->chan);
+  nfree(s->msg);
+  nfree(s);
+}
+
+static int expmemseen(void *what)
+{
+  int size = 0;
+  seendat *d = (seendat *) what;
+  
+  size += sizeof(seendat);
+  size += strlen(d->nick) + 1;
+  size += strlen(d->host) + 1;
+  size += strlen(d->chan) + 1;
+  size += strlen(d->msg) + 1;
+  return size;
+}
+
+// finds a seen entry in the tree
+seendat findseen_temp;
+static seendat *findseen(char *nick)
+{
+  findseen_temp.nick = nick;
+  return btree_get(&seentree, &findseen_temp);
+}
+
+// function to find all nicks that match a host
+// (calls btree_getall() which calls a target function for each item)
+// host: user's hostmask (used if search query doesn't contain any wildcards)
+// mask: search mask
+// wild: defines if we want to use the mask, or host for the search
+static char *wildmatch_host, *wildmatch_mask;
+int wildmatch_wild;
+static void wildmatch_seens(char *host, char *mask, int wild)
+{
+  wildmatch_host = host;
+  wildmatch_mask = mask;
+  wildmatch_wild = wild;
+  btree_getall(&seentree, process_wildmatch_seens);
+}
+
+/* process_wildmatch_seens():
+ * gets called from the binary tree for each existing item.
+ */
+static void process_wildmatch_seens(void *data)
+{
+  seendat *s = (seendat *) data;
+  
+  if ((numresults > max_matches) && (max_matches > 0)) // Don't return too many
+    return;                                            // matches...
+  if (!wildmatch_wild) {
+    if (wild_match(wildmatch_host, s->host))
+      add_seenresult(s);
+  } else {
+    temp_wildmatch_host = my_realloc(temp_wildmatch_host, strlen(s->nick) + 1 + strlen(s->host) + 1);
+    strcpy(temp_wildmatch_host, s->nick);
+    strcat(temp_wildmatch_host, "!");
+    strcat(temp_wildmatch_host, s->host);
+    if (wild_match(wildmatch_mask, s->nick) || wild_match(wildmatch_mask, temp_wildmatch_host))
+      add_seenresult(s);
+  }
+}
+
+// write seendata in the datafile
+FILE *write_seen_tree_target;
+static void write_seen_tree(void *data)
+{
+  seendat *node = (seendat *) data;
+  
+  /* format: "! nick host chan type when spent msg" */
+  fprintf(write_seen_tree_target, "! %s %s %s %d %lu %d %s\n", node->nick,
+          node->host, node->chan, node->type, node->when, node->spent,
+          node->msg);
+}
+
+// recursive function to remove old data
+// QUESTION: What happens if one of the nodes get moved by killseen()?
+//           Possible bug/crash?
+//           I think it should not be a problem. When killseen() is called the
+//           first time, recursion already reached its end and no pointers
+//           are accessed anymore. But I'm not sure... maybe I'm wrong.
+static void purge_seen_tree(void *data)
+{
+  seendat *node = (seendat *) data;
+  
+  if ((now - node->when) > (expire_seens * 86400)) {
+    debug1("seen data for %s has expired.", node->nick);
+    btree_remove(&seentree, node);
+  }
+}
+
+// counts the number of nicks in the database
+static int count_seens_temp;
+static int count_seens()
+{
+  count_seens_temp = 0;
+  btree_getall(&seentree, _count_seens);
+  return count_seens_temp;
+}
+
+static void _count_seens(void *node)
+{
+  count_seens_temp++;
+}
+
+static int tcl_killseen STDVAR
+{
+  Context;
+  BADARGS(2, 2, " nick");
+  findseen_temp.nick = argv[1];
+  btree_remove(&seentree, &findseen_temp);
+  return TCL_OK;
+}
+
+static tcl_cmds seendebugtcls[] =
+{
+  {"killseen", tcl_killseen},
+  {0, 0}
+};
diff -Nur src/mod/gseen.mod/sensors.c src/mod/gseen.mod/sensors.c
--- ./src/mod/gseen.mod/sensors.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/sensors.c	2002-10-26 13:18:00.000000000 +0200
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+static int get_spent(char *nick, char *chan)
+{
+  struct chanset_t *ch = NULL;
+  memberlist *m = NULL;
+
+  int spent;
+  ch = findchan_by_dname(chan);
+  if (ch)
+    m = ismember(ch, nick);
+  if (m && m->joined)
+    spent = now - m->joined;
+  else
+    spent = -1;
+  return spent;
+}
+
+static int secretchan(char *chan)
+{
+  struct chanset_t *ch;
+
+  ch = findchan_by_dname(chan);
+  if (!ch)
+    return 0;
+  if (ch->status & CHAN_SECRET)
+    return 1;
+  return 0;
+}
+
+static int nolog(char *chan)
+{
+  char buf[121], *b;
+
+  Context;
+  strncpy(buf, no_log, 120);
+  buf[120] = 0;
+  b = buf;
+  while (b[0])
+    if (!strcasecmp(chan, newsplit(&b)))
+      return 1;
+#if EGG_IS_MIN_VER(10503)
+  if (ngetudef("noseendata", chan))
+    return 1;
+#endif
+  return 0;
+}
+
+static int gseen_join(char *nick, char *uhost, char *hand, char *chan)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_JOIN, nick, uhost, chan, "", now, get_spent(nick, chan));
+  report_seenreq(chan, nick);
+  if ((hand[0] == '*') && strcasecmp(nick, hand))
+    report_seenreq(chan, hand);
+  return 0;
+}
+
+static int gseen_kick(char *nick, char *uhost, char *hand, char *chan,
+		       char *victim, char *reason)
+{
+  struct chanset_t *ch = NULL;
+  memberlist *m = NULL;
+  char msg[1024], *s;
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  ch = findchan_by_dname(chan);
+  if (!ch) {
+    debug2("Unable to seen %s getting kicked from %s", victim, chan);
+    return 0;
+  }
+  if (secretchan(chan))
+    chan = buf;
+  s = msg;
+  s[0] = 0;
+  m = ismember(ch, victim);
+  if (!m) {
+    debug2("Unable to seen %s getting kicked from %s", victim, chan);
+    return 0;
+  }
+  if ((strlen(nick) + strlen(reason) + 2) < 1024)
+    sprintf(s, "%s %s", nick, reason);
+  add_seen(SEEN_KICK, victim, m->userhost, chan, s, now,
+  	   get_spent(victim, chan));
+  return 0;
+}
+
+static int gseen_nick(char *nick, char *uhost, char *hand, char *chan,
+		       char *newnick)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_NICK, nick, uhost, chan, newnick, now, get_spent(nick, chan));
+  if (!(use_handles && (hand[0] != '*')))
+    add_seen(SEEN_NCKF, newnick, uhost, chan, nick, now, get_spent(nick, chan));
+  report_seenreq(chan, newnick);
+  if ((hand[0] != '*') && strcasecmp(newnick, hand))
+    report_seenreq(chan, hand);
+  return 0;
+}
+
+#if EGG_IS_MIN_VER(10502)
+static int gseen_part(char *nick, char *uhost, char *hand, char *chan,
+		       char *reason)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_PART, nick, uhost, chan, reason, now, get_spent(nick, chan));
+  return 0;
+}
+#else
+static int gseen_part(char *nick, char *uhost, char *hand, char *chan)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_PART, nick, uhost, chan, "", now, get_spent(nick, chan));
+  return 0;
+}
+#endif
+
+static int gseen_sign(char *nick, char *uhost, char *hand, char *chan,
+		       char *reason)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_SIGN, nick, uhost, chan, reason, now, get_spent(nick, chan));
+  return 0;
+}
+
+static int gseen_splt(char *nick, char *uhost, char *hand, char *chan)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_SPLT, nick, uhost, chan, "", now, get_spent(nick, chan));
+  return 0;
+}
+
+static int gseen_rejn(char *nick, char *uhost, char *hand, char *chan)
+{
+  char buf[10] = "[secret]";
+
+  Context;
+  if (nolog(chan))
+    return 0;
+  if (use_handles && (hand[0] != '*'))
+    nick = hand;
+  if (secretchan(chan))
+    chan = buf;
+  add_seen(SEEN_REJN, nick, uhost, chan, "", now, get_spent(nick, chan));
+  return 0;
+}
+
+static int gseen_chjn STDVAR
+{
+  Context;
+  BADARGS(7, 7, " bot hand chan flag idx host");
+  add_seen(SEEN_CHJN, argv[2], argv[6], argv[3], argv[1], now, -1);
+  return 0;
+}
+
+static int gseen_chpt STDVAR
+{
+  Context;
+  BADARGS(5, 5, " bot hand idx chan");
+  add_seen(SEEN_CHPT, argv[2], "unknown", argv[4], argv[1], now, -1);
+  return 0;
+}
+
+static cmd_t seen_kick[] =
+{
+  {"*", "", (Function) gseen_kick, "gseen"},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_nick[] =
+{
+  {"*", "", (Function) gseen_nick, "gseen"},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_join[] =
+{
+  {"*", "", (Function) gseen_join, "gseen"},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_part[] =
+{
+  {"*", "", (Function) gseen_part, "gseen"},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_sign[] =
+{
+  {"*", "", (Function) gseen_sign, "gseen"},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_splt[] =
+{
+  {"*", "", (Function) gseen_splt, "gseen"},
+  {0, 0, 0, 0}
+};
+
+static cmd_t seen_rejn[] =
+{
+  {"*", "", (Function) gseen_rejn, "gseen"},
+  {0, 0, 0, 0}
+};
diff -Nur src/mod/gseen.mod/slang.c src/mod/gseen.mod/slang.c
--- ./src/mod/gseen.mod/slang.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang.c	2002-10-26 13:18:03.000000000 +0200
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+static struct slang_header *slang_find(struct slang_header *, char *);
+
+#include "slang_text.c"
+#include "slang_multitext.c"
+#include "slang_ids.c"
+#ifndef SLANG_NOTYPES
+#include "slang_types.c"
+#endif
+#include "slang_duration.c"
+#ifndef SLANG_NOFACTS
+#include "slang_facts_places.c"
+#include "slang_facts.c"
+#endif
+#include "slang_chanlang.c"
+
+
+struct slang_header {
+  struct slang_header *next;
+  char *lang;
+  char *desc;
+  struct slang_id *ids;
+#ifndef SLANG_NOTYPES
+  struct slang_type *types;
+#endif
+  struct slang_duration *durations;
+};
+
+static void slang_glob_init()
+{
+  glob_slang_cmd_list = NULL;
+}
+
+static int slang_glob_expmem()
+{
+  return slang_commands_list_expmem(glob_slang_cmd_list);
+}
+
+static void slang_glob_free()
+{
+  slang_commands_list_free(glob_slang_cmd_list);
+  glob_slang_cmd_list = NULL;
+}
+
+static struct slang_header *slang_create(struct slang_header *list, char *lang, char *desc)
+{
+  struct slang_header *nslang, *l;
+
+  Assert(lang);
+  debug2("Creating language '%s' starting by %d", lang, (int) list);
+  for (nslang = list; nslang; nslang = nslang->next)
+    if (!strcasecmp(nslang->lang, lang))
+      return list;
+  nslang = nmalloc(sizeof(struct slang_header));
+  nslang->next = NULL;
+  nslang->desc = NULL;
+  nslang->lang = nmalloc(strlen(lang) + 1);
+  strcpy(nslang->lang, lang);
+  nslang->desc = nmalloc(strlen(desc) + 1);
+  strcpy(nslang->desc, desc);
+  nslang->ids = NULL;
+#ifndef SLANG_NOTYPES
+  nslang->types = NULL;
+#endif
+  nslang->durations = NULL;
+  for (l = list; l && l->next; l = l->next);
+  if (l)
+    l->next = nslang;
+  else {
+    Assert(!list);
+    list = nslang;
+  }
+  return list;
+}
+
+static int slang_expmem(struct slang_header *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct slang_header);
+    size += strlen(what->lang) + 1;
+    size += strlen(what->desc) + 1;
+    size += slang_id_expmem(what->ids);
+#ifndef SLANG_NOTYPES
+    size += slang_type_expmem(what->types);
+#endif
+    size += slang_duration_expmem(what->durations);
+    what = what->next;
+  }
+  return size;
+}
+
+static void slang_free(struct slang_header *what)
+{
+  struct slang_header *next;
+
+  while (what) {
+    next = what->next;
+    slang_id_free(what->ids);
+#ifndef SLANG_NOTYPES
+    slang_type_free(what->types);
+#endif
+    slang_duration_free(what->durations);
+    nfree(what->lang);
+    nfree(what->desc);
+    nfree(what);
+    what = next;
+  }
+}
+
+static int slang_load(struct slang_header *slang, char *filename)
+{
+  FILE *f;
+  char *buffer, *s;
+  char *cmd, *sid, *strtol_ret;
+#ifndef SLANG_NOTYPES
+  char *type;
+#endif
+  int line, id;
+
+  Assert(slang);
+  putlog(LOG_MISC, "*", "Loading language \"%s\" from %s...", slang->lang, filename);
+  f = fopen(filename, "r");
+  if (!f) {
+    putlog(LOG_MISC, "*", "Couldn't open slangfile \"%s\"!", filename);
+    return 0;
+  }
+  buffer = nmalloc(2000);
+  line = 0;
+  while (!feof(f)) {
+    s = buffer;
+    if (fgets(s, 2000, f)) {
+      line++;
+      // at first, kill those stupid line feeds and carriage returns...
+      if (s[strlen(s) - 1] == '\n')
+        s[strlen(s) - 1] = 0;
+      if (s[strlen(s) - 1] == '\r')
+        s[strlen(s) - 1] = 0;
+      if (!s[0])
+        continue;
+      cmd = newsplit(&s);
+
+      if (!strcasecmp(cmd, "T")) {
+#ifndef SLANG_NOTYPES
+        type = newsplit(&s);
+        slang->types = slang_type_add(slang->types, type, s);
+#endif
+      } else if (!strcasecmp(cmd, "D")) {
+        sid = newsplit(&s);
+        id = strtol(sid, &strtol_ret, 10);
+        if (strtol_ret == sid) {
+          putlog(LOG_MISC, "*", "ERROR in slangfile \"%s\", line %d: %s is not a valid "
+                 "duration index!", filename, line, sid);
+          continue;
+        }
+        slang->durations = slang_duration_add(slang->durations, id, s);
+      } else {
+        id = strtol(cmd, &strtol_ret, 10);
+        if (strtol_ret == cmd)
+          continue;
+        slang->ids = slang_id_add(slang->ids, id, s);
+      }
+    }
+  }
+  fclose(f);
+  nfree(buffer);
+  return 1;
+}
+
+static struct slang_header *slang_find(struct slang_header *where, char *language)
+{
+  struct slang_header *slang = NULL;
+
+  // at first, search for the specified language
+  for (slang = where; slang; slang = slang->next)
+    if (!strcasecmp(slang->lang, language))
+      return slang;
+  // oops... language seems to be invalid. Let's find the default.
+  Assert(default_slang);
+  for (slang = where; slang; slang = slang->next)
+    if (!strcasecmp(slang->lang, default_slang))
+      return slang;
+  // default_slang wasn't found either? *sigh*
+  // Let's return the first known language then.
+  return where;
+}
+
+#ifndef SLANG_NOVALIDATE
+/* slang_valid():
+ * check if the given language is a valid one
+ */
+static int slang_valid(struct slang_header *where, char *language)
+{
+  struct slang_header *slang = NULL;
+
+  for (slang = where; slang; slang = slang->next)
+    if (!strcasecmp(slang->lang, language))
+      return 1;
+  return 0;
+}
+#endif
+
+static char getslang_error[12];
+static char *getslang(int id)
+{
+  char *text;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getslang())");
+    return "NOLANG";
+  }
+  text = slang_id_get(glob_slang->ids, id);
+  if (!text) {
+    snprintf(getslang_error, sizeof(getslang_error), "SLANG%d", id);
+    return getslang_error;
+  }
+  return text;
+}
+
+static char *getdur(int idx)
+{
+  char *text;
+
+  Assert((idx >= 0) && (idx < DURATIONS));
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getdur())");
+    return "NOLANG";
+  }
+  text = slang_duration_get(glob_slang->durations, idx);
+  if (!text) {
+    snprintf(getslang_error, sizeof(getslang_error), "DUR%d", idx);
+    return getslang_error;
+  }
+  return text;
+}
+
+#ifndef SLANG_NOTYPES
+static char *getslangtype(char *type)
+{
+  char *stype;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getslangtype())");
+    return "NOLANG";
+  }
+  stype = slang_type_get(glob_slang->types, type);
+  if (stype)
+    return stype;
+  else
+    return type;
+}
+
+static int slangtypetoi(char *slangtype)
+{
+  char *type;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (slangtypetoi())");
+    return T_ERROR;
+  }
+  type = slang_type_slang2type(glob_slang->types, slangtype);
+  if (type) {
+    debug1("type: %s", type);
+    return typetoi(type);
+  } else
+    return typetoi(slangtype);
+}
+#endif
+
+#ifndef SLANG_NOGETALL
+static char *getslang_first(int id)
+{
+  char *text;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getslang())");
+    return "NOLANG";
+  }
+  text = slang_id_get_first(glob_slang->ids, id);
+  if (!text) {
+    snprintf(getslang_error, sizeof(getslang_error), "SLANG%d", id);
+    return getslang_error;
+  }
+  return text;
+}
+
+static char *getslang_next()
+{
+  return slang_id_get_next();
+}
+#endif
diff -Nur src/mod/gseen.mod/slang_chanlang.c src/mod/gseen.mod/slang_chanlang.c
--- ./src/mod/gseen.mod/slang_chanlang.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang_chanlang.c	2002-10-26 13:18:02.000000000 +0200
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+struct slang_chanlang {
+  struct slang_chanlang *next;
+  char *chan;
+  char *lang;
+};
+
+static struct slang_chanlang *chanlangs = NULL;
+
+static struct slang_chanlang *slang_chanlang_add(struct slang_chanlang *, char *, char *);
+static int slang_chanlang_expmem(struct slang_chanlang *);
+static void slang_chanlang_free(struct slang_chanlang *);
+static char *slang_chanlang_get(struct slang_chanlang *, char *);
+
+static struct slang_chanlang *slang_chanlang_add(struct slang_chanlang *where, char *chan, char *lang)
+{
+  struct slang_chanlang *item;
+
+  for (item = where; item; item = item->next)
+    if (!rfc_casecmp(item->chan, chan))
+      break;
+  if (!item) {
+    item = nmalloc(sizeof(struct slang_chanlang));
+    item->chan = nmalloc(strlen(chan) + 1);
+    strcpy(item->chan, chan);
+    item->lang = nmalloc(strlen(lang) + 1);
+    strcpy(item->lang, lang);
+    item->next = where;
+    where = item;
+  } else {
+    Assert(item->lang);
+    item->lang = nrealloc(item->lang, strlen(lang) + 1);
+    strcpy(item->lang, lang);
+  }
+  return where;
+}
+
+static int slang_chanlang_expmem(struct slang_chanlang *what)
+{
+  int size = 0;
+
+  while (what) {
+    Assert(what);
+    Assert(what->chan);
+    Assert(what->lang);
+    size += sizeof(struct slang_chanlang);
+    size += strlen(what->chan) + 1;
+    size += strlen(what->lang) + 1;
+    what = what->next;
+  }
+  return size;
+}
+
+static void slang_chanlang_free(struct slang_chanlang *what)
+{
+  struct slang_chanlang *next;
+
+  while (what) {
+    Assert(what);
+    Assert(what->chan);
+    Assert(what->lang);
+    next = what->next;
+    nfree(what->chan);
+    nfree(what->lang);
+    nfree(what);
+    what = next;
+  }
+}
+
+static char *slang_chanlang_get(struct slang_chanlang *where, char *chan)
+{
+  while (where) {
+    if (!rfc_casecmp(where->chan, chan))
+      return where->lang;
+    where = where->next;
+  }
+  return default_slang;
+}
+
+/* slang_getbynick():
+ * tries to find an appropriate language for nick by searching
+ * him on a channel and using the language of this channel.
+ */
+static struct slang_header *slang_getbynick(struct slang_header *where, char *nick)
+{
+  struct chanset_t *chan;
+
+  for (chan = chanset; chan; chan = chan->next)
+    if (ismember(chan, nick))
+#if EGG_IS_MIN_VER(10500)
+      return slang_find(where, slang_chanlang_get(chanlangs, chan->dname));
+#else
+      return slang_find(where, slang_chanlang_get(chanlangs, chan->name));
+#endif
+  return slang_find(where, default_slang);
+}
diff -Nur src/mod/gseen.mod/slang_duration.c src/mod/gseen.mod/slang_duration.c
--- ./src/mod/gseen.mod/slang_duration.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang_duration.c	2002-10-26 13:18:01.000000000 +0200
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+#define DURATIONS 13
+
+struct slang_duration {
+  char *durs[DURATIONS];
+};
+
+static struct slang_duration *slang_duration_add(struct slang_duration *where, int idx, char *text)
+{
+  int i;
+
+  if ((idx < 0) || (idx >= DURATIONS)) {
+    putlog(LOG_MISC, "*", "Warning: Invalid duration index \"%d\".", idx);
+    return where;
+  }
+  debug2("Adding duration[%d]: %s", idx, text);
+  if (!where) {
+    where = nmalloc(sizeof(struct slang_duration));
+    for (i = 0; i < DURATIONS; i++)
+      where->durs[i] = NULL;
+  }
+  if (where->durs[idx])
+    nfree(where->durs[idx]);
+  where->durs[idx] = nmalloc(strlen(text) + 1);
+  strcpy(where->durs[idx], text);
+  return where;
+}
+
+static int slang_duration_expmem(struct slang_duration *what)
+{
+  int i, size = 0;
+
+  if (!what)
+    return 0;
+  size += sizeof(struct slang_duration);
+  for (i = 0; i < DURATIONS; i++)
+    if (what->durs[i])
+      size += strlen(what->durs[i]) + 1;
+  return size;
+}
+
+static void slang_duration_free(struct slang_duration *what)
+{
+  int i;
+
+  if (what) {
+    for (i = 0; i < DURATIONS; i++)
+      if (what->durs[i])
+        nfree(what->durs[i]);
+    nfree(what);
+  }
+}
+
+static char *slang_duration_get(struct slang_duration *where, int idx)
+{
+  if (!where) {
+    debug0("no where");
+    return NULL;
+  }
+  if ((idx < 0) || (idx >= DURATIONS)) {
+    debug1("invalid duration index: %d", idx);
+    return NULL;
+  }
+  return where->durs[idx];
+}
diff -Nur src/mod/gseen.mod/slang_gseen_commands.c src/mod/gseen.mod/slang_gseen_commands.c
--- ./src/mod/gseen.mod/slang_gseen_commands.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang_gseen_commands.c	2002-10-26 13:18:06.000000000 +0200
@@ -0,0 +1,235 @@
+static void slang_send_botnick()
+{
+  strncat(slang_text_buf, botname, sizeof(slang_text_buf));
+}
+
+static void slang_send_query()
+{
+  if (glob_query)
+    strncat(slang_text_buf, glob_query, sizeof(slang_text_buf));
+}
+
+static void slang_send_laston()
+{
+  if (glob_laston)
+    strncat(slang_text_buf, glob_laston, sizeof(slang_text_buf));
+}
+
+static void slang_send_otherchan()
+{
+  if (glob_otherchan)
+    strncat(slang_text_buf, glob_otherchan, sizeof(slang_text_buf));
+}
+
+static void slang_send_othernick()
+{
+  if (glob_othernick)
+    strncat(slang_text_buf, glob_othernick, sizeof(slang_text_buf));
+}
+
+static void slang_send_remotebot()
+{
+  if (glob_remotebot)
+    strncat(slang_text_buf, glob_remotebot, sizeof(slang_text_buf));
+}
+
+static void slang_send_snick()
+{
+  if (glob_seendat)
+    strncat(slang_text_buf, glob_seendat->nick, sizeof(slang_text_buf));
+}
+
+static void slang_send_shost()
+{
+  if (glob_seendat)
+    strncat(slang_text_buf, glob_seendat->host, sizeof(slang_text_buf));
+}
+
+static void slang_send_schan()
+{
+  if (glob_seendat)
+    strncat(slang_text_buf, glob_seendat->chan, sizeof(slang_text_buf));
+}
+
+static void slang_send_swhen()
+{
+  char *dur;
+
+  if (glob_seendat) {
+    dur = gseen_duration(now - glob_seendat->when);
+    strncat(slang_text_buf, dur, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_stime()
+{
+  time_t tt;
+  char t[20];
+  
+  if (glob_seendat) {
+    tt = glob_seendat->when;
+    strftime(t, 19, "%d.%m. %H:%M", localtime(&tt));
+    strncat(slang_text_buf, t, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_spent()
+{
+  char *dur;
+
+  if (glob_seendat) {
+    dur = gseen_duration(glob_seendat->spent);
+    strncat(slang_text_buf, dur, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_smsg()
+{
+  if (glob_seendat)
+    strncat(slang_text_buf, glob_seendat->msg, sizeof(slang_text_buf));
+}
+
+static void slang_send_numresults()
+{
+  char buf[7];
+  
+  snprintf(buf, sizeof(buf), "%d", numresults);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_punisher()
+{
+  char *reason;
+  int len;
+  
+  if (glob_seendat) {
+    reason = strchr(glob_seendat->msg, ' ');
+    if (!reason)
+      strncat(slang_text_buf, glob_seendat->msg, sizeof(slang_text_buf));
+    else {
+      len = (int) reason - (int) glob_seendat->msg;
+      strncat(slang_text_buf, glob_seendat->msg, (sizeof(slang_text_buf) < len) ? sizeof(slang_text_buf) : len);
+    }
+  }
+}
+
+static void slang_send_kickreason()
+{
+  char *reason;
+  
+  if (glob_seendat) {
+    reason = strchr(glob_seendat->msg, ' ');
+    if (reason)
+      strncat(slang_text_buf, reason, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_rnick()
+{
+  if (glob_seenrequest) {
+    Assert(glob_seenrequest->by);
+    Assert(glob_seenrequest->by->who);
+    strncat(slang_text_buf, glob_seenrequest->by->who, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_rchan()
+{
+  if (glob_seenrequest) {
+    Assert(glob_seenrequest->by);
+    Assert(glob_seenrequest->by->chan);
+    strncat(slang_text_buf, glob_seenrequest->by->chan, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_rhost()
+{
+  if (glob_seenrequest) {
+    Assert(glob_seenrequest->by);
+    Assert(glob_seenrequest->by->host);
+    strncat(slang_text_buf, glob_seenrequest->by->host, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_rtime()
+{
+  time_t tt;
+  char t[20];
+  
+  if (glob_seenrequest) {
+    Assert(glob_seenrequest->by);
+    tt = glob_seenrequest->by->when;
+    strftime(t, sizeof(t), "%d.%m. %H:%M", localtime(&tt));
+    strncat(slang_text_buf, t, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_rwhen()
+{
+  if (glob_seenrequest) {
+    Assert(glob_seenrequest->by);
+    strncat(slang_text_buf, gseen_duration(now - glob_seenrequest->by->when), sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_requests()
+{
+  char buf[7];
+  
+  snprintf(buf, sizeof(buf), "%d", glob_seenrequests);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_totalnicks()
+{
+  char buf[7];
+  
+  snprintf(buf, sizeof(buf), "%d", glob_totalnicks);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_totalbytes()
+{
+  char buf[20];
+  
+  snprintf(buf, sizeof(buf), "%d", glob_totalbytes);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_nick()
+{
+  if (glob_nick)
+    strncat(slang_text_buf, glob_nick, sizeof(slang_text_buf));
+}
+
+struct slang_text_commands slang_text_gseen_command_table[] =
+{
+  {"botnick", slang_send_botnick},
+  {"query", slang_send_query},
+  {"laston", slang_send_laston},
+  {"otherchan", slang_send_otherchan},
+  {"othernick", slang_send_othernick},
+  {"remotebot", slang_send_remotebot},
+  {"snick", slang_send_snick},
+  {"swhen", slang_send_swhen},
+  {"stime", slang_send_stime},
+  {"shost", slang_send_shost},
+  {"schan", slang_send_schan},
+  {"spent", slang_send_spent},
+  {"smsg", slang_send_smsg},
+  {"numresults", slang_send_numresults},
+  {"snick2", slang_send_smsg},
+  {"bnbot", slang_send_smsg},
+  {"punisher", slang_send_punisher},
+  {"kickreason", slang_send_kickreason},
+  {"rnick", slang_send_rnick},
+  {"rchan", slang_send_rchan},
+  {"rhost", slang_send_rhost},
+  {"rtime", slang_send_rtime},
+  {"rwhen", slang_send_rwhen},
+  {"requests", slang_send_requests},
+  {"totalnicks", slang_send_totalnicks},
+  {"totalbytes", slang_send_totalbytes},
+  {"nick", slang_send_nick},
+  {0, 0}
+};
diff -Nur src/mod/gseen.mod/slang_ids.c src/mod/gseen.mod/slang_ids.c
--- ./src/mod/gseen.mod/slang_ids.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang_ids.c	2002-10-26 13:18:04.000000000 +0200
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+struct slang_id {
+  struct slang_id *next;
+  int id;
+  struct slang_multitext *mtext;
+};
+
+static struct slang_id* slang_id_add(struct slang_id *, int, char *);
+static int slang_id_expmem(struct slang_id *);
+static void slang_id_free(struct slang_id *);
+static char *slang_id_get(struct slang_id *, int);
+
+static struct slang_id* slang_id_add(struct slang_id *where, int id, char *text)
+{
+  struct slang_id *newitem;
+
+  newitem = NULL;
+  if (where) {
+    for (newitem = where; newitem; newitem = newitem->next)
+      if (newitem->id == id)
+        break;
+  }
+  if (!newitem) {
+    newitem = nmalloc(sizeof(struct slang_id));
+    newitem->next = NULL;
+    newitem->id = id;
+    newitem->mtext = NULL;
+    if (where)
+      newitem->next = where;
+    else
+      newitem->next = NULL;
+    where = newitem;
+  }
+  newitem->mtext = slang_mtext_add(newitem->mtext, text);
+  return where;
+}
+
+static int slang_id_expmem(struct slang_id *what)
+{
+  int size = 0;
+
+  for (; what; what = what->next) {
+    size += sizeof(struct slang_id);
+    size += slang_multitext_expmem(what->mtext);
+  }
+  return size;
+}
+
+static void slang_id_free(struct slang_id *what)
+{
+  struct slang_id *next;
+
+  while (what) {
+    next = what->next;
+    slang_multitext_free(what->mtext);
+    nfree(what);
+    what = next;
+  }
+}
+
+static char *slang_id_get(struct slang_id *where, int i)
+{
+  while (where) {
+    if (where->id == i)
+      return slang_multitext_getrandomtext(where->mtext);
+    where = where->next;
+  }
+  return NULL;
+}
+
+#ifndef SLANG_NOGETALL
+static char *slang_id_get_first(struct slang_id *where, int id)
+{
+  while (where) {
+    if (where->id == id) {
+      return slang_multitext_get_first(where->mtext);
+    }
+    where = where->next;
+  }
+  return NULL;
+}
+
+static char *slang_id_get_next()
+{
+  return slang_multitext_get_next();
+}
+#endif
diff -Nur src/mod/gseen.mod/slang_multitext.c src/mod/gseen.mod/slang_multitext.c
--- ./src/mod/gseen.mod/slang_multitext.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang_multitext.c	2002-10-26 13:18:05.000000000 +0200
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+struct slang_mt_content {
+  struct slang_mt_content *next;
+  struct slang_text *text;
+};
+
+struct slang_multitext {
+  int nr;
+  struct slang_mt_content *contents;
+};
+
+static struct slang_multitext *slang_mtext_add(struct slang_multitext *, char *);
+static int slang_multitext_expmem(struct slang_multitext *);
+static void slang_multitext_free(struct slang_multitext *);
+static char *slang_multitext_getrandomtext(struct slang_multitext *);
+#ifndef SLANG_NOTYPES
+static struct slang_text *slang_multitext_find(struct slang_multitext *, char *);
+#endif
+#ifndef SLANG_NOGETALL
+static char *slang_multitext_get_first(struct slang_multitext *);
+static char *slang_multitext_get_next();
+#endif
+
+static struct slang_multitext *slang_mtext_add(struct slang_multitext *where, char *text)
+{
+  struct slang_mt_content *oc, *nc;
+
+  if (!where) {
+    where = nmalloc(sizeof(struct slang_multitext));
+    where->nr = 0;
+    where->contents = NULL;
+  }
+  nc = nmalloc(sizeof(struct slang_mt_content));
+  nc->next = NULL;
+  nc->text = slang_text_parse(text);
+  for (oc = where->contents; oc && oc->next; oc = oc->next);
+  if (oc) {
+    Assert(!oc->next);
+    oc->next = nc;
+  } else
+    where->contents = nc;
+  where->nr++;
+  return where;
+}
+
+static int slang_multitext_expmem(struct slang_multitext *what)
+{
+  struct slang_mt_content *content;
+  int size = 0;
+
+  if (!what) {
+    debug0("WARNING! slang_multitext_expmem() called with NULL pointer!");
+    return 0;
+  }
+  size += sizeof(struct slang_multitext);
+  for (content = what->contents; content; content = content->next) {
+    size += sizeof(struct slang_mt_content);
+    size += slang_text_expmem(content->text);
+  }
+  return size;
+}
+
+static void slang_multitext_free(struct slang_multitext *what)
+{
+  struct slang_mt_content *content, *next;
+
+  if (!what) {
+    debug0("WARNING! slang_multitext_free() called with NULL pointer!");
+    return;
+  }
+  content = what->contents;
+  while (content) {
+    next = content->next;
+    slang_text_free(content->text);
+    nfree(content);
+    content = next;
+  }
+  nfree(what);
+}
+
+static char *slang_multitext_getrandomtext(struct slang_multitext *where)
+{
+  struct slang_mt_content *content;
+  unsigned long x;
+
+  if (!where)
+    return NULL;
+  x = random() % where->nr;
+  for (content = where->contents; content; content = content->next)
+    if (!x)
+      return slang_text_get(content->text);
+    else
+      x--;
+  // we should never reach this part
+  debug0("warning: getrandomtext didn't find anything!");
+  return NULL;
+}
+
+#ifndef SLANG_NOTYPES
+static struct slang_text *slang_multitext_find(struct slang_multitext *where, char *what)
+{
+  struct slang_mt_content *content;
+
+  Assert(where);
+  for (content = where->contents; content; content = content->next) {
+    Assert(content->text);
+    if (!slang_text_strcasecmp(content->text, what))
+      return content->text;
+  }
+  return NULL;
+}
+#endif
+
+#ifndef SLANG_NOGETALL
+static struct slang_mt_content *glob_mtext_content;
+static char *slang_multitext_get_first(struct slang_multitext *where)
+{
+  Assert(where);
+  glob_mtext_content = where->contents;
+  if (glob_mtext_content)
+    return slang_text_get(glob_mtext_content->text);
+  else
+    return NULL;
+}
+
+static char *slang_multitext_get_next()
+{
+  glob_mtext_content = glob_mtext_content->next;
+  if (glob_mtext_content)
+    return slang_text_get(glob_mtext_content->text);
+  else
+    return NULL;
+}
+#endif
diff -Nur src/mod/gseen.mod/slang_text.c src/mod/gseen.mod/slang_text.c
--- ./src/mod/gseen.mod/slang_text.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/slang_text.c	2002-10-26 13:18:07.000000000 +0200
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+struct slang_text {
+  struct slang_text *next;
+  char *string;
+  void (*command) ();
+};
+
+struct slang_text_commands {
+  char *command;
+  void (*targetfunc) ();
+};
+
+struct slang_command_list {
+  struct slang_command_list *next;
+  struct slang_text_commands *commands;
+};
+
+static struct slang_text *slang_text_parse(char *);
+static struct slang_text *slang_text_create(struct slang_text *);
+static void slang_text_add_string(struct slang_text *, char *);
+static void slang_text_add_command(struct slang_text *, char *);
+static void slang_text_free(struct slang_text *);
+static int slang_text_expmem(struct slang_text *);
+static char *slang_text_get(struct slang_text *);
+#ifndef SLANG_NOTYPES
+static int slang_text_strcasecmp(struct slang_text *, char *);
+#endif
+
+static struct slang_text *slang_text_parse(char *text)
+{
+  char *cmdstart, *cmdend;
+  struct slang_text *firstitem, *item;
+
+  firstitem = slang_text_create(NULL);
+  item = firstitem;
+  while ((cmdstart = strstr(text, "<?"))) {
+    cmdstart[0] = 0;
+    slang_text_add_string(item, text);
+    item = slang_text_create(item);
+    text += 2;
+    cmdstart += 2;
+    cmdend = strstr(cmdstart, "/?>");
+    if (!cmdend) {
+      putlog(LOG_MISC, "*", "ERROR parsing slang text: unterminated command \"%s\"!", cmdstart);
+      break;
+    }
+    cmdend[0] = 0;
+    slang_text_add_command(item, cmdstart);
+    item = slang_text_create(item);
+    text = cmdend + 3;
+  }
+  slang_text_add_string(item, text);
+  return firstitem;
+}
+
+static struct slang_text *slang_text_create(struct slang_text *where)
+{
+  struct slang_text *newpart;
+
+  newpart = nmalloc(sizeof(struct slang_text));
+  newpart->next = NULL;
+  newpart->string = NULL;
+  newpart->command = NULL;
+  while (where && where->next)
+    where = where->next;
+  if (where)
+    where->next = newpart;
+  return newpart;
+}
+
+static void slang_text_add_string(struct slang_text *item, char *s)
+{
+  Assert(item);
+  Assert(!item->string);
+  item->string = nmalloc(strlen(s) + 1);
+  strcpy(item->string, s);
+}
+
+static void slang_text_free(struct slang_text *item)
+{
+  if (!item)
+    return;
+  slang_text_free(item->next);
+  if (item->string)
+    nfree(item->string);
+  nfree(item);
+}
+
+static int slang_text_expmem(struct slang_text *item)
+{
+  int size = 0;
+
+  while (item) {
+    size += sizeof(struct slang_text);
+    if (item->string)
+      size += strlen(item->string) + 1;
+    item = item->next;
+  }
+  return size;
+}
+
+#ifndef SLANG_NOTYPES
+static int slang_text_strcasecmp(struct slang_text *item, char *text)
+{
+  Assert(item);
+  debug2("s_t_sc: '%s', '%s'", text, item->string);
+  if (item->command || item->next)
+    return 1;
+  return strcasecmp(item->string, text);
+}
+#endif
+
+static char slang_text_buf[500];
+static char *slang_text_get(struct slang_text *item)
+{
+  slang_text_buf[0] = 0;
+  while (item) {
+    if (item->string)
+      strncat(slang_text_buf, item->string, sizeof(slang_text_buf));
+    else if (item->command)
+      item->command();
+    item = item->next;
+  }
+  return slang_text_buf;
+}
+
+/*****************************************************/
+
+
+static struct slang_command_list *glob_slang_cmd_list;
+
+static struct slang_command_list *slang_commands_list_add(struct slang_command_list *where, struct slang_text_commands *what)
+{
+  struct slang_command_list *newcommandlist;
+
+  newcommandlist = nmalloc(sizeof(struct slang_command_list));
+  newcommandlist->commands = what;
+  newcommandlist->next = where;
+  return newcommandlist;
+}
+
+static int slang_commands_list_expmem(struct slang_command_list *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct slang_command_list);
+    what = what->next;
+  }
+  return size;
+}
+
+static void slang_commands_list_free(struct slang_command_list *what)
+{
+  struct slang_command_list *next;
+
+  while (what) {
+    next = what->next;
+    nfree(what);
+    what = next;
+  }
+}
+
+static void slang_text_add_command(struct slang_text *item, char *s)
+{
+  struct slang_command_list *cmdlist;
+  char *cmd;
+  int i;
+
+  cmd = newsplit(&s);
+  i = 0;
+  for (cmdlist = glob_slang_cmd_list; cmdlist; cmdlist = cmdlist->next) {
+    for (i = 0; 1; i++) {
+      if (!cmdlist->commands[i].command)
+        break;
+      if (!strcasecmp(cmdlist->commands[i].command, cmd)) {
+        item->command = cmdlist->commands[i].targetfunc;
+        return;
+      }
+    }
+  }
+  putlog(LOG_MISC, "*", "ERROR! Unknown slang-command: '%s'", cmd);
+}
diff -Nur src/mod/gseen.mod/tclcmds.c src/mod/gseen.mod/tclcmds.c
--- ./src/mod/gseen.mod/tclcmds.c	1970-01-01 01:00:00.000000000 +0100
+++ ./src/mod/gseen.mod/tclcmds.c	2002-10-26 13:18:08.000000000 +0200
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * 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.
+ */
+
+static int tcl_setchanseenlang STDVAR
+{
+  Context;
+  BADARGS(3, 3, " channel language");
+  chanlangs = slang_chanlang_add(chanlangs, argv[1], argv[2]);
+  return TCL_OK;
+}
+
+static int tcl_loadseenslang STDVAR
+{
+//  int ret = 0;
+  char *shortname, *longname, *filename;
+  struct slang_header *slang;
+
+  Context;
+  BADARGS(4, 4, " language description langfile");
+  shortname = argv[1];
+  longname = argv[2];
+  filename = argv[3];
+  coreslangs = slang_create(coreslangs, shortname, longname);
+  slang = slang_find(coreslangs, shortname);
+  Assert(slang);
+  if (!slang_load(slang, filename)) {
+    Tcl_AppendResult(irp, "Couldn't open seenslang file!!!", NULL);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+static tcl_cmds gseentcls[] =
+{
+  {"loadseenslang", tcl_loadseenslang},
+  {"setchanseenlang", tcl_setchanseenlang},
+  {0, 0}
+};
