mirror of https://github.com/nealey/irc-bot
Better argv parsing in factoids
This commit is contained in:
parent
b48152c085
commit
bed36ae75b
|
@ -1,4 +1,4 @@
|
|||
*~
|
||||
*.o
|
||||
dispatch
|
||||
irc
|
||||
bot
|
||||
factoids
|
||||
|
|
6
Makefile
6
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
|
||||
|
|
26
README
26
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
|
||||
======
|
||||
|
|
2
bot.c
2
bot.c
|
@ -15,8 +15,6 @@
|
|||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "dump.h"
|
||||
|
||||
#define MAX_ARGS 50
|
||||
#define MAX_SUBPROCS 50
|
||||
|
||||
|
|
107
contrib/bot.lua
107
contrib/bot.lua
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
20
dump.h
|
@ -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
|
|
@ -2,7 +2,6 @@
|
|||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "cdb.h"
|
||||
#include "dump.h"
|
||||
|
||||
/*
|
||||
*
|
|
@ -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)
|
|
@ -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;
|
Loading…
Reference in New Issue