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
dispatch
irc
bot
factoids

View File

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

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

2
bot.c
View File

@ -15,8 +15,6 @@
#include <dirent.h>
#include <fcntl.h>
#include "dump.h"
#define MAX_ARGS 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 <stdint.h>
#include "cdb.h"
#include "dump.h"
/*
*

View File

View File

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

View File

@ -9,12 +9,19 @@
#include <sysexits.h>
#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;