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
|
*.o
|
||||||
dispatch
|
bot
|
||||||
irc
|
factoids
|
||||||
|
|
6
Makefile
6
Makefile
|
@ -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
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
|
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
2
bot.c
|
@ -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
|
||||||
|
|
||||||
|
|
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 <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "cdb.h"
|
#include "cdb.h"
|
||||||
#include "dump.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
|
@ -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)
|
|
@ -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;
|
Loading…
Reference in New Issue