Better argv parsing in factoids

This commit is contained in:
Neale Pickett 2012-11-21 14:25:40 -07:00
parent b48152c085
commit bed36ae75b
16 changed files with 74 additions and 328 deletions

4
.gitignore vendored
View File

@ -1,4 +1,4 @@
*~ *~
*.o *.o
dispatch bot
irc factoids

View File

@ -1,11 +1,11 @@
CFLAGS = -Wall -Werror CFLAGS = -Wall -Werror
TARGETS = bot TARGETS = bot
TARGETS += infobot TARGETS += extras/factoids
all: $(TARGETS) all: $(TARGETS)
infobot: infobot.o cdb.o cdbmake.o extras/factoids: extras/factoids.o extras/cdb.o extras/cdbmake.o
.PHONY: clean .PHONY: clean
clean: clean:
rm -f $(TARGETS) *.o rm -f $(TARGETS) *.o extras/*.o

26
README
View File

@ -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 Handlers are launched in parallel to each other. IRC is an asynchronous
protocol, and while messages do tend to arrive in a particular order, 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 `newmont` is a very simple handler script to reply to any PRIVMSG with
the substring "strawberry", in the (public) forum it was sent. 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 Caution
======= =======
@ -98,6 +103,25 @@ much more difficult to accidentally create an exploit. Or create a new
handler in Python, Ruby, etc. 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 Author
====== ======

2
bot.c
View File

@ -15,8 +15,6 @@
#include <dirent.h> #include <dirent.h>
#include <fcntl.h> #include <fcntl.h>
#include "dump.h"
#define MAX_ARGS 50 #define MAX_ARGS 50
#define MAX_SUBPROCS 50 #define MAX_SUBPROCS 50

View File

@ -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

View File

@ -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 <<EOF
!stats Show statistics
!list KEY List all factoids stored for KEY
!append KEY += VALUE Add VALUE to KEY's factoids
!remove KEY -= VALUE Remove *VALUE* from KEY's factoids
!forget KEY Remove all factoids for KEY
EOF
;;
!s*)
cdb -s $db | head -n 1
;;
!l*)
printf "factoids for \"%s\": " "$args"
cdb -q -m $db "$args" | awk '{printf("|%s", $0);}'
echo
;;
$nickname:\ *\ is\ *)
s=${text#$nickname: }
db_append "${s%% is *}" "${s#* is }"
;;
!a*)
db_append "${args% +=*}" "${args#*+= }"
;;
!r*)
key=${args% -=*}
val=${args#*-= }
re=":$key->.*$val"
n=$(cdb -d $db | grep -c "$re")
if [ $n -gt 0 ]; then
cdb -d $db | grep -a -v ":$key->.*$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

View File

@ -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

View File

@ -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

View File

@ -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

20
dump.h
View File

@ -1,20 +0,0 @@
#ifndef __DUMP_H__
#define __DUMP_H__
#include <stdio.h>
/* 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

View File

@ -2,7 +2,6 @@
#include <string.h> #include <string.h>
#include <stdint.h> #include <stdint.h>
#include "cdb.h" #include "cdb.h"
#include "dump.h"
/* /*
* *

View File

View File

@ -3,7 +3,6 @@
#include <string.h> #include <string.h>
#include <stdlib.h> // XXX: remove if malloc() is gone #include <stdlib.h> // XXX: remove if malloc() is gone
#include "cdbmake.h" #include "cdbmake.h"
#include "dump.h"
static uint32_t static uint32_t
hash(char *s, size_t len) hash(char *s, size_t len)

View File

@ -9,12 +9,19 @@
#include <sysexits.h> #include <sysexits.h>
#include "cdb.h" #include "cdb.h"
#include "cdbmake.h" #include "cdbmake.h"
#include "dump.h"
int 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; return EX_USAGE;
} }
@ -215,12 +222,29 @@ del(char *filename, char *key, char *glob)
return 0; 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 { enum action {
ACT_ONE, ACT_ONE,
ACT_ALL, ACT_ALL,
ACT_ADD, ACT_ADD,
ACT_DEL ACT_DEL,
ACT_NEW
}; };
int int
@ -232,7 +256,7 @@ main(int argc, char *argv[])
enum action act = ACT_ONE; enum action act = ACT_ONE;
for (;;) { for (;;) {
int opt = getopt(argc, argv, "la:r:"); int opt = getopt(argc, argv, "hlna:r:");
if (-1 == opt) { if (-1 == opt) {
break; break;
@ -241,6 +265,9 @@ main(int argc, char *argv[])
case 'l': case 'l':
act = ACT_ALL; act = ACT_ALL;
break; break;
case 'n':
act = ACT_NEW;
break;
case 'a': case 'a':
act = ACT_ADD; act = ACT_ADD;
val = optarg; val = optarg;
@ -253,8 +280,16 @@ main(int argc, char *argv[])
return usage(argv[0]); 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 // Seed PRNG with some crap
@ -265,9 +300,6 @@ main(int argc, char *argv[])
srand((unsigned int)(tv.tv_sec * tv.tv_usec)); srand((unsigned int)(tv.tv_sec * tv.tv_usec));
} }
filename = argv[optind];
key = argv[optind + 1];
switch (act) { switch (act) {
case ACT_ONE: case ACT_ONE:
return choose(filename, key); return choose(filename, key);
@ -277,6 +309,8 @@ main(int argc, char *argv[])
return add(filename, key, val); return add(filename, key, val);
case ACT_DEL: case ACT_DEL:
return del(filename, key, val); return del(filename, key, val);
case ACT_NEW:
return create(filename);
} }
return 0; return 0;