From fd7440f158a05a4b85227f9828b78ce4282d68bc Mon Sep 17 00:00:00 2001 From: unknown_lamer Date: Fri, 2 Aug 2002 04:31:30 +0000 Subject: [PATCH] [project @ 2002-08-02 04:31:30 by unknown_lamer] 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. --- ChangeLog | 25 +++++++++++++++ NEWS | 17 +++++++++++ TODO | 3 -- bobot++.info | 67 ++++++++++++++++++++++++++++++----------- bobot++.texinfo | 40 ++++++++++++++++++++++-- scripts/bobot-utils.scm | 15 +++++++++ source/Bot.C | 22 +++++++++----- source/Bot.H | 1 - source/BotInterp.C | 7 +++-- source/BotInterp.H | 7 +++-- source/Commands.C | 3 +- source/Interp.C | 2 +- source/Parser.C | 4 ++- source/ScriptCommands.C | 8 +++-- source/ScriptCommands.H | 2 +- source/ServerQueue.C | 48 +++++++++++++++++++++++++++++ source/Socket.C | 21 ++++++++++--- 17 files changed, 244 insertions(+), 48 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0bf49d3..cae48af 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,28 @@ +2002-08-01 Clinton Ebadi + + * 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 + + * 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 + + * 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 * source/Interp.C (Shutdown): Runs bot:exit-hook hooks diff --git a/NEWS b/NEWS index 1c8d68e..da04de1 100644 --- 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 --- 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: diff --git a/bobot++.info b/bobot++.info index 7ee40b8..6c352c9 100644 --- a/bobot++.info +++ b/bobot++.info @@ -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.  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. +  File: bobot++.info, Node: Concept Index, Next: Function Index, Prev: Scripting, Up: Top @@ -495,14 +526,14 @@ Node: Scripting3677 Node: Adding New Commands4585 Node: Hooks5828 Node: Creating a Hook6766 -Node: Hook Types7558 -Node: Scheme User Levels10031 -Node: Sending Messages11160 -Node: High Level Message Functions11757 -Node: Low Level Message Functions11975 -Node: Misc Scripting Stuff12734 -Node: Concept Index13153 -Node: Function Index13335 -Node: Variable Index13596 +Node: Hook Types7908 +Node: Scheme User Levels10381 +Node: Sending Messages11510 +Node: High Level Message Functions12107 +Node: Low Level Message Functions12325 +Node: Misc Scripting Stuff13084 +Node: Concept Index14710 +Node: Function Index14892 +Node: Variable Index15153  End Tag Table diff --git a/bobot++.texinfo b/bobot++.texinfo index cdcfe01..56bbf55 100644 --- a/bobot++.texinfo +++ b/bobot++.texinfo @@ -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 diff --git a/scripts/bobot-utils.scm b/scripts/bobot-utils.scm index d1963a3..97b9404 100644 --- a/scripts/bobot-utils.scm +++ b/scripts/bobot-utils.scm @@ -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 @@ -61,6 +63,19 @@ 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 diff --git a/source/Bot.C b/source/Bot.C index 0a9f3b4..ec98068 100644 --- a/source/Bot.C +++ b/source/Bot.C @@ -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); diff --git a/source/Bot.H b/source/Bot.H index 5abd072..7234817 100644 --- a/source/Bot.H +++ b/source/Bot.H @@ -89,7 +89,6 @@ public: #ifdef USESCRIPTS BotInterp * botInterp; #endif - // std::list userFunctions; std::map > userFunctions; std::map > wantedChannels; diff --git a/source/BotInterp.C b/source/BotInterp.C index d678660..e6659a3 100644 --- a/source/BotInterp.C +++ b/source/BotInterp.C @@ -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; } diff --git a/source/BotInterp.H b/source/BotInterp.H index c0b37e8..f5ff1e5 100644 --- a/source/BotInterp.H +++ b/source/BotInterp.H @@ -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); diff --git a/source/Commands.C b/source/Commands.C index 5d381ba..9fc6cd1 100644 --- a/source/Commands.C +++ b/source/Commands.C @@ -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; } diff --git a/source/Interp.C b/source/Interp.C index 2443311..c00041c 100644 --- a/source/Interp.C +++ b/source/Interp.C @@ -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, diff --git a/source/Parser.C b/source/Parser.C index 8fae1f3..7cbb766 100644 --- a/source/Parser.C +++ b/source/Parser.C @@ -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 diff --git a/source/ScriptCommands.C b/source/ScriptCommands.C index 2c90b8e..bb43426 100644 --- a/source/ScriptCommands.C +++ b/source/ScriptCommands.C @@ -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 diff --git a/source/ScriptCommands.H b/source/ScriptCommands.H index d9ec71f..f24b193 100644 --- a/source/ScriptCommands.H +++ b/source/ScriptCommands.H @@ -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); diff --git a/source/ServerQueue.C b/source/ServerQueue.C index 1042c77..e04fb0e 100644 --- a/source/ServerQueue.C +++ b/source/ServerQueue.C @@ -22,6 +22,7 @@ //#include #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 diff --git a/source/Socket.C b/source/Socket.C index 3e83568..2cacfd3 100644 --- a/source/Socket.C +++ b/source/Socket.C @@ -17,6 +17,7 @@ // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. #include "Socket.H" +#include #include #include @@ -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') -- 2.20.1