[project @ 2002-08-02 04:31:30 by unknown_lamer]
authorunknown_lamer <unknown>
Fri, 2 Aug 2002 04:31:30 +0000 (04:31 +0000)
committerunknown_lamer <unknown>
Fri, 2 Aug 2002 04:31:30 +0000 (04:31 +0000)
A few things this time. Hooks can be named (really cool), a few bug
fixes (logs work again now), the docs were worked on a little,
Socket::readLine now automatically expands the buffer when the length
gets over 512 (it uses a static std::string that will _not_ have to be
expanded for most uses, but _might_ need to be expanded when I get DCC
to work), the bot now runs hooks on its own text so you have to
protect against your hooks triggering other hooks by their output;
this can be done using the new (not-from-me) syntax (see the manual
for how it works). I think that is it.

17 files changed:
ChangeLog
NEWS
TODO
bobot++.info
bobot++.texinfo
scripts/bobot-utils.scm
source/Bot.C
source/Bot.H
source/BotInterp.C
source/BotInterp.H
source/Commands.C
source/Interp.C
source/Parser.C
source/ScriptCommands.C
source/ScriptCommands.H
source/ServerQueue.C
source/Socket.C

index 0bf49d3..cae48af 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+2002-08-01  Clinton Ebadi  <clinton@unknownlamer.org>
+
+       * source/Bot.C (set_log_file): Oops! Fixed logging. Now the bot
+       logs again.
+
+       * source/Socket.C (readLine): Now uses a static std::string that
+       starts out with 512 chars. This will never need to be resized for
+       usualy IRC messages, but might be for DCC messages
+
+2002-07-31  Clinton Ebadi  <clinton@unknownlamer.org>
+
+       * scripts/bobot-utils.scm (not-from-me): New syntax.
+
+       * source/ScriptCommands.C (AddHook): Now takes another optional
+       arg--name that will name the hook (see the hooks section in the
+       manual for what this does).
+
+       * source/BotInterp.H: Added name field to Hook
+
+2002-07-29  Clinton Ebadi  <clinton@unknownlamer.org>
+
+       * source/ServerQueue.C (sendPrivmsg): Now calls hooks on own
+       PRIVMSGes because the IRC server doesn't echo them back to the
+       bot. This could be useful for something (e.g. log script).
+
 2002-07-27  Clinton Ebadi  <clinton@unknownlamer.org>
 
        * source/Interp.C (Shutdown): Runs bot:exit-hook hooks
diff --git a/NEWS b/NEWS
index 1c8d68e..da04de1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,23 @@ IMPORTANT NOTES:
 ====== The News =====================================================
 =====================================================================
 
+Version 2.1.1: foom
+- Hooks are now executed when the bot sends a privmsg. This now makes
+  log scripts able to log what the bot said. This probably has other
+  uses too and shouldn't have any real impact on performance (since it
+  has to execute hooks on all incoming messages anyway, and there are
+  probably a lot more incoming than outgoing).
+- You can now "name" a hook using an extra arg to bot:addhook. This
+  name can be used to have multiple hooks of the same type with the
+  same regexp. The default name is "DEFAULT" so don't use that as the
+  name for your hooks.
+- There is a new macro for scripts--"not-from-me". This allows you to
+  protect your hooks from calling themselves because they trigger
+  themselves.  See the manual for more about what it does 
+  (Scripting->Misc Scripting Stuff).
+- Logging now works again (oops, I didn't realize I broke it until I
+  started to work on DCC).
+
 Version 2.1.0: Zug Zug
 - Hooks can now be fallthrough or non fallthrough. You can set a hooks
   priority and whether or not it falls through (i.e. continues hook
diff --git a/TODO b/TODO
index a7409c0..8b374d7 100644 (file)
--- a/TODO
+++ b/TODO
@@ -20,8 +20,6 @@ Scripting:
 * Finish adding commands to Scheme for sending messages
    (e.g. bot:send-CTCP to send a CTCP message)
 * Add util functions for doing stuff like quoting CTCP messages
-* Call hooks/public when bot sends a privmsg to a channel (read IRC
-   protocol spec for other stuff that might need this)
 
 Networking:
 * Add a networked interface to guile repl
@@ -37,7 +35,6 @@ Networking:
      fly) or rename readLine to ircReadLine and add a more general
      readLine? I think I could use a static std::string and have it
      grow as needed, with a default size of 512.
-* Add Channel logging (log full text of channel if enabled) script
 * Make connecting to irc.oftc.net work...I wonder if their ircd is b0rked
 
 Documentation:
index 7ee40b8..6c352c9 100644 (file)
@@ -199,15 +199,20 @@ Creating a Hook
 ---------------
 
    To add a new hook you use the function `bot:addhook'. `bot:addhook'
-is prototyped as `(bot:addhook type regex function pri fall)'. `type'
-specifies the type of hook (the types of hooks are listed in *Note Hook
-Types::). `regex' is a standard regular expression. If `regex' is
-matched, `function' will be called. `function' will take a different
-number of args depending on the hook type. `pri' specifies the priority
-of the hook---higher priority hooks are executed first. This argument is
-optional and defaults to `0'. `fall' is `#t' if the hook is a
-fallthrough hook and `#f' is the hook is not a fallthrough hook. This
-arg is also optional and default to `#t'.
+is prototyped as `(bot:addhook type regex function pri fall name)'.
+`type' specifies the type of hook (the types of hooks are listed in
+*Note Hook Types::). `regex' is a standard regular expression. If
+`regex' is matched, `function' will be called. `function' will take a
+different number of args depending on the hook type. `pri' specifies
+the priority of the hook---higher priority hooks are executed first.
+This argument is optional and defaults to `0'. `fall' is `#t' if the
+hook is a fallthrough hook and `#f' is the hook is not a fallthrough
+hook. This arg is also optional and default to `#t'. `name' is the
+optional name of the hook that defaults to ``DEFAULT''. If you set the
+name then you can have more than one hook that matches the same regexp,
+as long as they have the same name. E.g. in a log script you could have
+the regexps for the log function all be `".*"' and set their names to
+`"log"' to avoid a conflict with other hooks.
 
 \1f
 File: bobot++.info,  Node: Hook Types,  Prev: Creating a Hook,  Up: Hooks
@@ -427,6 +432,32 @@ might want to know.
 bot:exit-hook THUNK' where THUNK is an argumentless procedure (a
 thunk). When the bot exits your thunk will be called.
 
+   Since a bot calls hooks on things it says, you have to be careful
+about hooks that output text that might match itself. E.g. if you have
+a hook that matches `"foo"' and the hook displays `"foo to the
+whatsit?"', then the hook will call itself over and over until the
+stack overflows! To protect against this I wrote the macro
+`not-from-me'. You call it like this: `(not-from-me from (stmts if not
+from bot) (stmts if from bot))'. E.g.
+
+     (bot:addhook hooks/public "foo"
+                  (lambda (f t p)
+                    (not-from-me f ((bot:say t "foo to the what!")))))
+
+   This say ``foo to the what!'' to the channel that ``foo'' was said in
+and do nothing otherwise. You can optionally specify an action to be
+executed if the message is from the bot:
+
+     (bot:addhook hooks/public "foo"
+                  (lambda (f t p)
+                    (not-from-me f ((bot:say t "foo to the what!"))
+                                   ((bot:say t "moof")))))
+
+   That will do the same thing as the first example, but the bot will
+say ``moof'' if it said ``foo'' before. That probably isn't a very nice
+thing to do, but it works as an example. You can have as many staments
+as you want in the clauses.
+
 \1f
 File: bobot++.info,  Node: Concept Index,  Next: Function Index,  Prev: Scripting,  Up: Top
 
@@ -495,14 +526,14 @@ Node: Scripting\7f3677
 Node: Adding New Commands\7f4585
 Node: Hooks\7f5828
 Node: Creating a Hook\7f6766
-Node: Hook Types\7f7558
-Node: Scheme User Levels\7f10031
-Node: Sending Messages\7f11160
-Node: High Level Message Functions\7f11757
-Node: Low Level Message Functions\7f11975
-Node: Misc Scripting Stuff\7f12734
-Node: Concept Index\7f13153
-Node: Function Index\7f13335
-Node: Variable Index\7f13596
+Node: Hook Types\7f7908
+Node: Scheme User Levels\7f10381
+Node: Sending Messages\7f11510
+Node: High Level Message Functions\7f12107
+Node: Low Level Message Functions\7f12325
+Node: Misc Scripting Stuff\7f13084
+Node: Concept Index\7f14710
+Node: Function Index\7f14892
+Node: Variable Index\7f15153
 \1f
 End Tag Table
index cdcfe01..56bbf55 100644 (file)
@@ -206,7 +206,7 @@ processing of hooks stops.
 @findex addhook
 To add a new hook you use the function
 @code{bot:addhook}. @code{bot:addhook} is prototyped as
-@code{(bot:addhook type regex function pri fall)}. @code{type}
+@code{(bot:addhook type regex function pri fall name)}. @code{type}
 specifies the type of hook (the types of hooks are listed in @ref{Hook
 Types}). @code{regex} is a standard regular expression. If
 @code{regex} is matched, @code{function} will be
@@ -215,7 +215,13 @@ on the hook type. @code{pri} specifies the priority of the
 hook---higher priority hooks are executed first. This argument is
 optional and defaults to @code{0}. @code{fall} is @code{#t} if the
 hook is a fallthrough hook and @code{#f} is the hook is not a
-fallthrough hook. This arg is also optional and default to @code{#t}. 
+fallthrough hook. This arg is also optional and default to
+@code{#t}. @code{name} is the optional name of the hook that defaults
+to ``DEFAULT''. If you set the name then you can have more than one
+hook that matches the same regexp, as long as they have the same
+name. E.g. in a log script you could have the regexps for the log
+function all be @code{".*"} and set their names to @code{"log"} to
+avoid a conflict with other hooks.
 
 @node Hook Types,  , Creating a Hook, Hooks
 @subsection Hook Types
@@ -594,6 +600,36 @@ If you want to execute code when the bot exits, just do
 argumentless procedure (a thunk). When the bot exits your thunk will
 be called.
 
+Since a bot calls hooks on things it says, you have to be careful
+about hooks that output text that might match itself. E.g. if you have
+a hook that matches @code{"foo"} and the hook displays @code{"foo to
+the whatsit?"}, then the hook will call itself over and over until the
+stack overflows! To protect against this I wrote the macro
+@code{not-from-me}. You call it like this: @code{(not-from-me from
+(stmts if not from bot) (stmts if from bot))}. E.g. 
+
+@example
+(bot:addhook hooks/public "foo"
+             (lambda (f t p)
+               (not-from-me f ((bot:say t "foo to the what!")))))
+@end example
+
+This say ``foo to the what!'' to the channel that ``foo'' was said in
+and do nothing otherwise. You can optionally specify an action to be
+executed if the message is from the bot:
+
+@example
+(bot:addhook hooks/public "foo"
+             (lambda (f t p)
+               (not-from-me f ((bot:say t "foo to the what!"))
+                              ((bot:say t "moof")))))
+@end example
+
+That will do the same thing as the first example, but the bot will
+say ``moof'' if it said ``foo'' before. That probably isn't a very
+nice thing to do, but it works as an example. You can have as many
+staments as you want in the clauses.
+
 @node Concept Index, Function Index, Scripting, Top
 @unnumbered Concept Index
 @printindex cp
index d1963a3..97b9404 100644 (file)
@@ -8,6 +8,8 @@
 ;;; must be GPLed, so all of your scripts have to be GPLed anyway
 ;;; because you are really linking with Bobot++, a GPLed program.
 
+(use-modules (ice-9 syncase))
+
 ;;; Bot load (loads a file from %bot:loadpath)
 
 (define %bot:loadpath (list
    messages )
   (bot:flushport))
 
+;;; executes body if not from the bot
+(define-syntax not-from-me
+  (syntax-rules ()
+    ((_ from (not-body1 ...)
+       (from-body1 ...))
+     (cond ((not (string=? from (bot:getnickname)))
+           not-body1 ...)
+          (else from-body1 ...)))
+    ((_ from (not-body1 ...))
+     (cond ((not (string=? from (bot:getnickname)))
+           not-body1 ...)))))
+
+
 ;;; Message sending utils
 
 ;;; returns the CTCP quoted message
index 0a9f3b4..ec98068 100644 (file)
@@ -247,13 +247,16 @@ Bot::readConfig()
     }
     else if (command == "LOGFILE")
       {
-       if (parameters[0] == '/')
+       if (parameters != logFileName)
          {
-           set_log_dir ("/");
-           set_log_file (parameters.subString (1));
+           if (parameters[0] == '/')
+             {
+               set_log_dir ("/");
+               set_log_file (parameters.subString (1));
+             }
+           else
+             set_log_file (parameters);
          }
-       else
-         set_log_file (parameters);
       }
 #ifdef USESCRIPTS
     else if (command == "SCRIPTLOGFILE")
@@ -525,8 +528,11 @@ Bot::addDCC(Person * from, unsigned long address, int port)
                                         address, port);
 
   if (!d->connect())
-    return;
-
+    {
+      logLine ("DCC Connection failed from " + from->getAddress ());
+      return;
+    }
+  logLine ("DCC Connection worked!");
   dccConnections.push_back(d);
 }
 
@@ -679,6 +685,8 @@ void
 Bot::set_log_file (String name)
 {
   logFileName = name;
+  logFile.close ();
+  logFile.clear ();
 #if HAVE_IOSBASE
   logFile.open(logs_dir + logFileName, std::ios_base::out | 
               std::ios_base::ate | std::ios_base::app);
index 5abd072..7234817 100644 (file)
@@ -89,7 +89,6 @@ public:
 #ifdef USESCRIPTS
   BotInterp * botInterp;
 #endif
-  //  std::list<class userFunction *> userFunctions;
   std::map<std::string, class userFunction*, 
           std::less<std::string> > userFunctions;
   std::map<String, wantedChannel *, std::less<String> > wantedChannels;
index d678660..e6659a3 100644 (file)
@@ -70,7 +70,8 @@ namespace
 }
 
 bool
-BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall) {
+BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall,
+                  String name) {
   if (scm_string_p(regex) == SCM_BOOL_F)
     return false;
   String rx = Utils::scm2String(regex).toUpper();
@@ -85,7 +86,7 @@ BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall) {
 
   for ( ; it != it2; ++it)
     // It exists, we replace it.
-    if ((*it)->regex_str == rx) {
+    if ((*it)->regex_str == rx && (*it)->name == name) {
       scm_gc_unprotect_object((*it)->function);
       scm_gc_unprotect_object (r);
       (*it)->function = function;
@@ -96,7 +97,7 @@ BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall) {
     }
   // It does not exist, we create it
   hooksMap[hooktype].push_back (new Hook(hooktype, rx, r, 
-                                        function, pri, fall));
+                                        function, pri, fall, name));
   hooksMap[hooktype].sort (hptr_lt);
   return true;
 }
index c0b37e8..f5ff1e5 100644 (file)
@@ -37,12 +37,13 @@ struct Hook {
   bool fallthru;
 
   String regex_str;
+  String name;
   SCM regex;
   SCM function;
 
-  Hook(int t, String rs, SCM r, SCM f, int p, bool ft)
+  Hook(int t, String rs, SCM r, SCM f, int p, bool ft, String n="DEFAULT")
     : type(t), priority (p), fallthru (ft), regex_str(rs), 
-      regex(r), function(f)  { }
+      name (n), regex(r), function(f)  { }
 
   bool operator< (const Hook &h) const
   {
@@ -90,7 +91,7 @@ public:
   void Execute(String);
   void LoadScript(String);
 
-  bool AddHook(int, SCM, SCM, int, bool);
+  bool AddHook(int, SCM, SCM, int, bool, String);
   bool RunHooks(int, String, SCM);
 
   SCM AddTimer(int, SCM);
index 5d381ba..9fc6cd1 100644 (file)
@@ -62,8 +62,7 @@ Commands::Action(Bot *bot, String channel, String message)
   if (message.length() == 0)
     return InvalidParameters;
 
-  QUEUE->sendPrivmsg(channel, String("\001ACTION ") +
-                     message + "\001");
+  QUEUE->sendCTCP (channel, "ACTION", message);
 
   return Ok;
 }
index 2443311..c00041c 100644 (file)
@@ -147,7 +147,7 @@ Interp::Startup()
   bot_new_procedure ("bot:delcommand", (SCMFunc)ScriptCommands::delCommand, 
                     1, 0, 0);
   bot_new_procedure ("bot:addhook", (SCMFunc)ScriptCommands::AddHook, 
-                    3, 2, 0);
+                    3, 3, 0);
   bot_new_procedure ("bot:addtimer", (SCMFunc)ScriptCommands::AddTimer, 
                     2, 0, 0);
   bot_new_procedure ("bot:deltimer", (SCMFunc)ScriptCommands::DelTimer, 
index 8fae1f3..7cbb766 100644 (file)
@@ -852,13 +852,15 @@ Parser::parseCTCP (ServerConnection * cnx,
       command = st2.nextToken ().toUpper ();
       if (command == "CHAT")
        {
-         // FIXME: Re-activate and debug DCC
+         // FIXME: debug DCC
          st2.nextToken ();
          unsigned long address =
            ntohl (strtoul ((const char *) st2.nextToken (), 0, 0));
          int port = atoi ((const char *) st2.nextToken ());
          if (port >= 1024 && Utils::getLevel (cnx->bot, from->getAddress ()))
            cnx->bot->addDCC (from, address, port);
+         else
+           cnx->bot->logLine ("DCC Chat Failed in Parser");
        }
     }
 #ifdef USESCRIPTS
index 2c90b8e..bb43426 100644 (file)
@@ -555,18 +555,22 @@ ScriptCommands::delCommand(SCM scm_commandName)
 }
 
 SCM
-ScriptCommands::AddHook(SCM type, SCM regex, SCM function, SCM pri, SCM fall)
+ScriptCommands::AddHook(SCM type, SCM regex, SCM function, SCM pri, SCM fall,
+                       SCM name)
 {
   int priority = 0;
   bool fallt = true; // does this hook fall through?
+  String rname = "DEFAULT";
 
   if (!SCM_UNBNDP (pri))
     priority = scm_num2int (pri, SCM_ARG1, "ScriptCommands::AddHook");
   if (!SCM_UNBNDP (fall))
     fallt = SCM_NFALSEP (fall);
+  if (!SCM_UNBNDP (name))
+    rname = Utils::scm2String (name);
   return SCM_BOOL (Interp::bot->botInterp->AddHook(gh_scm2long(type),
                                                   regex, function,
-                                                  priority, fallt));
+                                                  priority, fallt, rname));
 }
 
 SCM
index d9ec71f..f24b193 100644 (file)
@@ -74,7 +74,7 @@ public:
   static SCM random(SCM);
   static SCM addCommand(SCM, SCM, SCM, SCM, SCM);
   static SCM delCommand(SCM);
-  static SCM AddHook(SCM, SCM, SCM, SCM, SCM);
+  static SCM AddHook(SCM, SCM, SCM, SCM, SCM, SCM);
   static SCM AddTimer(SCM, SCM);
   static SCM DelTimer(SCM);
 
index 1042c77..e04fb0e 100644 (file)
@@ -22,6 +22,7 @@
 
 //#include <limits>
 #include "ServerQueue.H"
+#include "Utils.H"
 
 ServerQueue::ServerQueue(Socket * s, bool d)
   : Queue(s,d), penalty(0)
@@ -84,12 +85,35 @@ ServerQueue::flush()
   return true;
 }
 
+#define MNICK (Interp::bot->nickName + "!" + Interp::bot->userHost)
 void
 ServerQueue::sendCTCP(String to, String command,
                       String message)
 {
   sendPrivmsg(to, String("\001") + command + " " + message + "\001");
+  // hook stuff (only get action for now)
+
+  // I don't think it is useful to generate messages for other types
+  // of CTCP stuff.
+  puts (command);
+#ifdef USESCRIPTS
+  if (command == "ACTION")
+    {
+      Interp::bot->botInterp->RunHooks (Hook::ACTION,
+                                       MNICK+ " " + to +
+                                       " " + message,
+                                       scm_listify (Utils::
+                                                 string2SCM (MNICK),
+                                                 Utils::
+                                                 string2SCM (to),
+                                                 Utils::
+                                                 string2SCM (message),
+                                                 SCM_UNDEFINED));
+    }
+#endif
+
 }
+#undef MNICK
 
 void
 ServerQueue::sendCTCPReply(String to, String command,
@@ -184,6 +208,30 @@ ServerQueue::sendPrivmsg(String dest, String message)
 {
   addLine(String("PRIVMSG ") + dest + " :" + message,
           PRIVMSG_PRIORITY, PRIVMSG_PENALTY, ServerQueueItem::PRIVMSG);
+  // hook stuff
+#ifdef USESCRIPTS
+  if (message[0] != '\001')
+    if (Utils::isChannel (dest))
+      Interp::bot->botInterp->RunHooks (Hook::PUBLIC,
+                                       Interp::bot->nickName + " " + dest +
+                                       " " + message,
+                                       scm_listify (Utils::
+                                                    string2SCM (Interp::bot->nickName),
+                                                    Utils::
+                                                    string2SCM (dest),
+                                                    Utils::
+                                                    string2SCM
+                                                    (message), SCM_UNDEFINED));
+    else
+      Interp::bot->botInterp->RunHooks (Hook::MESSAGE,
+                                       Interp::bot->nickName + " " +
+                                       message,
+                                       scm_listify (Utils::
+                                                    string2SCM (Interp::bot->nickName),
+                                                    Utils::
+                                                    string2SCM
+                                                    (message), SCM_UNDEFINED));
+#endif
 }
 
 void
index 3e83568..2cacfd3 100644 (file)
@@ -17,6 +17,7 @@
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 
 #include "Socket.H"
+#include <string>
 
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -190,7 +191,11 @@ Socket::connect()
   addr.sin_addr.s_addr = htonl(remoteAddress);
   addr.sin_port = htons(remotePort);
   if (::connect(fd->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
-    return false;
+    {
+      // I'd rather log this to the log file
+      std::cerr << strerror (errno) << std::endl;
+      return false;
+    }
   
   return true;
 }
@@ -243,10 +248,11 @@ Socket::write(String s, bool m)
 String
 Socket::readLine()
 {
-  static char buf[512];
+  static std::string buf (512, ' ');
   int pos = 0, nb;
   char r;
-  
+  std::size_t length = buf.length ();
+
   do 
     {
       nb = ::read(fd->fd, &r, 1);
@@ -260,7 +266,14 @@ Socket::readLine()
       }
       
       if (nb != -1)
-       buf[pos++] = r;
+       if (pos < length)
+         buf[pos++] = r;
+       else
+         {
+           buf.resize (length * 2);
+           length = buf.length ();
+           buf[pos++] = r;
+         }
     } while (r != '\n');
   
 if (pos > 1 && buf[pos-2] == '\r')