diff --git a/.gitignore b/.gitignore index 49316e3..1f40927 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ *~ *.o -dispatch -irc +bot +factoids diff --git a/Makefile b/Makefile index 78ff81b..5979161 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ CFLAGS = -Wall -Werror TARGETS = bot -TARGETS += infobot +TARGETS += extras/factoids all: $(TARGETS) -infobot: infobot.o cdb.o cdbmake.o +extras/factoids: extras/factoids.o extras/cdb.o extras/cdbmake.o .PHONY: clean clean: - rm -f $(TARGETS) *.o + rm -f $(TARGETS) *.o extras/*.o diff --git a/README b/README index 8c83a9b..47c1780 100644 --- a/README +++ b/README @@ -73,11 +73,16 @@ writing something to stdout, and then it should exit. Handlers are launched in parallel to each other. IRC is an asynchronous protocol, and while messages do tend to arrive in a particular order, - don't count on it, especially with this framework. +don't count on it, especially with this framework. `newmont` is a very simple handler script to reply to any PRIVMSG with the substring "strawberry", in the (public) forum it was sent. +I don't provide any cool handler frameworks because I want you to enjoy +designing your own. It's not difficult, you can use any language you +want, and you don't even need to restart anything to apply your changes, +since a new handler is launched for each message. + Caution ======= @@ -98,6 +103,25 @@ much more difficult to accidentally create an exploit. Or create a new handler in Python, Ruby, etc. +Extras +====== + +Included AT NO ADDITIONAL COST, in the `extras/` directory, are: + + +newmont +------- + +A simple handler, written in lua. + + +factoids +-------- + +A program to maintain a low-overhead, read-optimized database file, which +can store multiple values for each key. This was written to help develop +infobots, but can also be used for any other key/value store needed. + Author ====== diff --git a/bot.c b/bot.c index f1dbad5..a15e677 100644 --- a/bot.c +++ b/bot.c @@ -15,8 +15,6 @@ #include #include -#include "dump.h" - #define MAX_ARGS 50 #define MAX_SUBPROCS 50 diff --git a/contrib/bot.lua b/contrib/bot.lua deleted file mode 100755 index c65cd6c..0000000 --- a/contrib/bot.lua +++ /dev/null @@ -1,107 +0,0 @@ --- --- Bot object --- --- require() this file, and you get a `bot` object with everything --- you need to run a bot. You can either switch on bot.command, --- or invoke bot:run(), which will run self:handle_$COMMAND. --- -bot = {} -bot.debug = false - -bot.nick = "newmont" -bot.user = "WoozleBot" -bot.desc = "A woozle.org bot" - -bot.prefix = os.getenv("prefix") -bot.forum = os.getenv("forum") -bot.sender = os.getenv("sender") -bot.command = os.getenv("command") -bot.text = os.getenv("text") -bot.args = arg - --- --- Log to stderr --- -function bot:log(text) - io.stderr:write(text .. "\n") -end - --- --- Log what we are working with --- -function bot:debug_input(text) - self:log(("< %-8s %8s/%-8s :%s"):format( - self.command, - self.sender or "-", - self.forum or "-", - self.text or "")) -end - --- --- Send a raw IRC command --- -function bot:raw(text) - if self.debug then - self:log(" > " .. text) - end - print(text) -end - --- --- Send a message to the forum, falling back to sender if we're --- spamming the channel. --- -function bot:msg(text) - self.msgs_sent = self.msgs_sent + 1 - if ((self.msgs_sent == 5) and (self.forum ~= self.sender)) then - self:raw("PRIVMSG " .. forum .. " :Sending the rest in private") - self.msg_recip = self.sender - end - self:raw("PRIVMSG " .. self.msg_recip .. " :" .. text) -end -bot.msgs_sent = 0 -bot.msg_recip = bot.forum - --- --- Emote, like "* botname explodes" --- -function bot:emote(text) - self:msg("\001ACTION " .. text .. "\001") -end - --- --- Use introspection to dispatch a command --- -function bot:run() - local func = self["handle_" .. self.command:lower()] - - if (self.debug) then - self:debug_input() - end - - if (func) then - func(self) - else - self:handle_default() - end -end - --- Log in to IRC -function bot:handle__init_() - self:raw("NICK " .. self.nick) - self:raw(("USER %s %s %s :%s"):format(self.user, self.user, self.user, self.desc)) -end - --- Deal with nickname collision -function bot:handle_433() - self.raw("NICK " .. self.nick .. (os.time() % 500)) -end - --- Override this to handle messages -function bot:handle_privmsg() - self:log(("%s/%s: %s"):format(self.sender, self.forum, self.text)) -end - --- Override this to handle any undefined command -function bot:handle_default() -end diff --git a/contrib/infobot b/contrib/infobot deleted file mode 100755 index 99884f0..0000000 --- a/contrib/infobot +++ /dev/null @@ -1,90 +0,0 @@ -#! /bin/sh - -db=$1; shift -text="$1" - -[ -f $db ] || echo | cdb -c $db - -lookup () { - if ! cdb -q -m $db "$1"; then - t="$1" - while [ "$t" != "$n" ]; do - n=$t - t=${n%[?!. ]} - done - if [ "$t" != "$1" ]; then - cdb -q -m $db "$t" - fi - fi -} - -db_append () { - (printf "+%d,%d:%s->%s\n" ${#1} ${#2} "$1" "$2"; cdb -d $db) | cdb -c $db - echo "Okay, $sender, I added a factoid to $1." -} - -nickname=${nickname:-infobot} -args=${text#* } - -case "$text" in - !h*) - cat <.*$val" | cdb -c $db - echo "Okay, $sender, I removed $n factoids from $key" - else - echo "Nothing matched, $sender." - fi - ;; - !forget\ *) - cdb -d $db | grep -a -F -v ":$args->" | cdb -c $db - echo "I removed all factoids from $args" - ;; - *) - resp=$(lookup "$text" | shuf -n 1 | sed "s/\$sender/$sender/") - case "$resp" in - "") - exit 1 - ;; - ''*) - echo "Someone's up to no good!" - ;; - '\'*) - printf "%s\n" "${resp#\\}" - ;; - :*) - printf '\001ACTION %s\001\n' "${resp#:}" - ;; - *) - echo "It's been said that $text is $resp" - ;; - esac - ;; -esac diff --git a/contrib/notes b/contrib/notes deleted file mode 100755 index 4baeb4f..0000000 --- a/contrib/notes +++ /dev/null @@ -1,27 +0,0 @@ -#! /bin/sh - -db=$1; shift -text=$1 - -lc () { - printf "%s" "$1" | tr A-Z a-z -} - -sender=$(lc "$sender") -if [ -f $db/$sender ]; then - echo "Welcome back, $sender. Your messages:" - cat $db/$sender - rm $db/$sender -fi - -case "$text" in - note\ *) - args=${text#note } - who=$(lc "${args%% *}") - what=${args#* } - when=$(date) - - echo "($when) <$prefix> $what" >> $db/$who - echo "Okay, $sender, I've left $who a note." - ;; -esac diff --git a/contrib/utilbot b/contrib/utilbot deleted file mode 100755 index 4567d53..0000000 --- a/contrib/utilbot +++ /dev/null @@ -1,23 +0,0 @@ -#! /bin/sh - -exec 2>&1 - -cmd=${1%% *} -[ "$cmd" = "$1" ] || args=${1#* } -case $cmd in - calc) - printf "%s = " "$args" - echo "$args" | bc -l - ;; - units) - src=$(printf "%s" "$args" | sed 's/ ->.*//') - dst=$(printf "%s" "$args" | sed 's/.*-> //') - units -1 -v -- "$src" "$dst" - ;; - *) - exit 1 - ;; -esac - - - diff --git a/contrib/whuffie b/contrib/whuffie deleted file mode 100755 index 7d25294..0000000 --- a/contrib/whuffie +++ /dev/null @@ -1,41 +0,0 @@ -#! /bin/sh - -db=$1; shift -text="$1" - -get () { - cdb -q $db "$1" || echo 0 -} - -put () { - (printf "+%d,%d:%s->%s\n" ${#1} ${#2} "$1" $2; - cdb -d $db) | cdb -c -u $db -} - -adj () { - who=${text%%$1$1*} - if [ "$who" = "$sender" ]; then - echo "Nice try, $sender." - else - put "$who" $(expr $(get "$who") $1 1) - fi -} - - -case "$text" in - whuffie\ *) - who=${text#whuffie } - amt=$(get "$who") - echo "$who has whuffie score of $amt" - ;; - *++|*++\ *) - adj + - ;; - *--|*--\ *) - adj - - ;; - *) - exit 1 - ;; -esac - diff --git a/dump.h b/dump.h deleted file mode 100644 index 3505e99..0000000 --- a/dump.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef __DUMP_H__ -#define __DUMP_H__ - -#include - -/* Some things I use for debugging */ -#ifdef NODUMP -# define DUMPf(fmt, args...) -#else -# define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args) -#endif -#define DUMP() DUMPf("") -#define DUMP_d(v) DUMPf("%s = %d", #v, v) -#define DUMP_l(v) DUMPf("%s = %ld", #v, v) -#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v) -#define DUMP_s(v) DUMPf("%s = %s", #v, v) -#define DUMP_c(v) DUMPf("%s = '%c' (0x%02x)", #v, v, v) -#define DUMP_p(v) DUMPf("%s = %p", #v, v) - -#endif diff --git a/cdb.c b/extras/cdb.c similarity index 99% rename from cdb.c rename to extras/cdb.c index 55a31e7..89cab78 100644 --- a/cdb.c +++ b/extras/cdb.c @@ -2,7 +2,6 @@ #include #include #include "cdb.h" -#include "dump.h" /* * diff --git a/cdb.h b/extras/cdb.h similarity index 100% rename from cdb.h rename to extras/cdb.h diff --git a/cdbmake.c b/extras/cdbmake.c similarity index 99% rename from cdbmake.c rename to extras/cdbmake.c index 437aa83..cc16a3a 100644 --- a/cdbmake.c +++ b/extras/cdbmake.c @@ -3,7 +3,6 @@ #include #include // XXX: remove if malloc() is gone #include "cdbmake.h" -#include "dump.h" static uint32_t hash(char *s, size_t len) diff --git a/cdbmake.h b/extras/cdbmake.h similarity index 100% rename from cdbmake.h rename to extras/cdbmake.h diff --git a/infobot.c b/extras/factoids.c similarity index 81% rename from infobot.c rename to extras/factoids.c index 1723582..0ce6ca9 100644 --- a/infobot.c +++ b/extras/factoids.c @@ -9,12 +9,19 @@ #include #include "cdb.h" #include "cdbmake.h" -#include "dump.h" int -usage() +usage(char *self) { - fprintf(stderr, "Usage: infobot factoids.cdb \"text\"\n"); + fprintf(stderr, "Usage: %s [OPTIONS] CDB \"KEY\"\n", self); + fprintf(stderr, "\n"); + fprintf(stderr, "Default: Display one randomly-picked entry for KEY\n"); + fprintf(stderr, "-n Create database from scratch, ignoring KEY\n"); + fprintf(stderr, "-l Display all entries for KEY\n"); + fprintf(stderr, "-a VAL Append VAL to entries for KEY\n"); + fprintf(stderr, "-r GLOB Remove entries matching GLOB from KEY\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "KEY is always converted to lowercase (Latin-1 only)\n"); return EX_USAGE; } @@ -215,12 +222,29 @@ del(char *filename, char *key, char *glob) return 0; } +int +create(char *filename) +{ + FILE *f = fopen(filename, "wb"); + struct cdbmake_ctx outc; + + if (! f) { + perror("Creating database"); + return EX_CANTCREAT; + } + + cdbmake_init(&outc, f); + cdbmake_finalize(&outc); + + return 0; +} enum action { ACT_ONE, ACT_ALL, ACT_ADD, - ACT_DEL + ACT_DEL, + ACT_NEW }; int @@ -232,7 +256,7 @@ main(int argc, char *argv[]) enum action act = ACT_ONE; for (;;) { - int opt = getopt(argc, argv, "la:r:"); + int opt = getopt(argc, argv, "hlna:r:"); if (-1 == opt) { break; @@ -241,6 +265,9 @@ main(int argc, char *argv[]) case 'l': act = ACT_ALL; break; + case 'n': + act = ACT_NEW; + break; case 'a': act = ACT_ADD; val = optarg; @@ -253,8 +280,16 @@ main(int argc, char *argv[]) return usage(argv[0]); } } - if (argc - optind != 2) { - return usage(argv[0]); + + if (! (filename = argv[optind++])) { + usage(argv[0]); + } + if ((act != ACT_NEW) && + (! (key = argv[optind++]))) { + usage(argv[0]); + } + if (argv[optind]) { + usage(argv[0]); } // Seed PRNG with some crap @@ -265,9 +300,6 @@ main(int argc, char *argv[]) srand((unsigned int)(tv.tv_sec * tv.tv_usec)); } - filename = argv[optind]; - key = argv[optind + 1]; - switch (act) { case ACT_ONE: return choose(filename, key); @@ -277,6 +309,8 @@ main(int argc, char *argv[]) return add(filename, key, val); case ACT_DEL: return del(filename, key, val); + case ACT_NEW: + return create(filename); } return 0; diff --git a/contrib/newmont b/extras/newmont similarity index 100% rename from contrib/newmont rename to extras/newmont