+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
====== 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
* 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
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:
---------------
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
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
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
@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
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
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
;;; 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
}
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")
address, port);
if (!d->connect())
- return;
-
+ {
+ logLine ("DCC Connection failed from " + from->getAddress ());
+ return;
+ }
+ logLine ("DCC Connection worked!");
dccConnections.push_back(d);
}
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);
#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;
}
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();
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;
}
// 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;
}
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
{
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);
if (message.length() == 0)
return InvalidParameters;
- QUEUE->sendPrivmsg(channel, String("\001ACTION ") +
- message + "\001");
+ QUEUE->sendCTCP (channel, "ACTION", message);
return Ok;
}
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,
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
}
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
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);
//#include <limits>
#include "ServerQueue.H"
+#include "Utils.H"
ServerQueue::ServerQueue(Socket * s, bool d)
: Queue(s,d), penalty(0)
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,
{
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
// 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>
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;
}
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);
}
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')