firebot/infobot.py

302 lines
11 KiB
Python

from bindingsbot import BindingsBot
import re
import irc
import seedyb
import time
class InfoBot(BindingsBot):
"""A cheap knock-off of the famous InfoBot.
"""
msg_cat = {}
msg_cat.update(BindingsBot.msg_cat)
bindings = []
def __init__(self, host, nicks, gecos, channels, dbname='info.cdb'):
BindingsBot.__init__(self, host, nicks, gecos, channels)
self._db = seedyb.open(dbname)
self.seen = {}
self.ignore_case = True
def sync(self):
now = time.time()
self._db.sync()
def close(self):
self.sync()
BindingsBot.close(self)
def cmd_ping(self, sender, forum, addl):
BindingsBot.cmd_ping(self, sender, forum, addl)
self.sync()
msg_cat['unknown'] = ("I don't know anything about %(key)s, %(sender)s.",)
msg_cat['stats'] = ("I know about %(things)s things.",)
msg_cat['is'] = ("Rumor has it that %(key)s is %(val)s",
"I believe %(key)s is %(val)s",
"My sources tell me %(key)s is %(val)s",
'Gosh, %(sender)s, I think %(key)s is %(val)s',
"%(key)s is %(val)s")
msg_cat['_is_'] = ("%(key)s is %(val)s",)
msg_cat['dunno'] = ("Search me, %(sender)s.",
"I have no earthly idea, %(sender)s.",
"I wish I knew, %(sender)s.")
msg_cat['same'] = ('I already had it that way, %(sender)s.',
"That's what I have for %(key)s too, %(sender)s.")
msg_cat['but'] = ('...but %(key)s is %(old)s',)
msg_cat['locked'] = ('Sorry, %(sender)s, %(key)s is locked.',)
msg_cat['tell'] = ('%(sender)s wants you to know: %(string)s',)
msg_cat['synced'] = ('Synchronized in %(time)f seconds.',)
def do_sync(self, sender, forum, addl, match):
now = time.time()
self.sync()
forum.msg(self.gettext('synced',
sender=sender.name(),
time=(time.time() - now)))
bindings.append((re.compile(r"^\008[,: ]+(sync|synchronize|flush)$"),
do_sync))
def encode_key(self, key):
if self.ignore_case:
key = key.lower()
return key
def get(self, key, *args, **kwargs):
return self._db.get(self.encode_key(key), *args, **kwargs)
def getall(self, key, **kwargs):
return self._db.getall(self.encode_key(key), **kwargs)
def set(self, key, val, **kwargs):
return self._db.set(self.encode_key(key), val, **kwargs)
def delete(self, key, **kwargs):
self._db.delete(self.encode_key(key), **kwargs)
def lock(self, key):
return self._db.lock(self.encode_key(key))
def unlock(self, key):
return self._db.unlock(self.encode_key(key))
def stats(self, sender, forum, addl, match):
forum.msg(self.gettext('stats', things=len(self._db)))
bindings.append((re.compile(r"^\008[,: ]+statu?s$"),
stats))
# Delete part of an entry
def forget_from(self, sender, forum, key, substr):
val = self.getall(key)
if not val:
raise KeyError()
possibilities = []
newval = []
for i in val:
if substr in i:
possibilities.append(i)
else:
newval.append(i)
if len(possibilities) == 1:
try:
self.set(key, tuple(newval))
except seedyb.Locked:
forum.msg(self.gettext('locked', key=key,
sender=sender.name()))
return
forum.msg(self.gettext('forgot',
key=key,
val=possibilities[0],
sender=sender.name()))
elif len(possibilities) == 0:
forum.msg(self.gettext('not in',
key=key,
substr=substr,
sender=sender.name()))
else:
forum.msg(self.gettext('ambiguous forget',
key=key,
substr=substr,
num=len(possibilities),
sender=sender.name()))
msg_cat['not in'] = ("I don't see any entries for %(key)s containing %(substr)s, %(sender)s",)
msg_cat['ambiguous forget'] = ("There are %(num)d matches for %(substr)s in %(key)s. Try a more specific substring!",)
msg_cat['forgot'] = ('Okay, %(sender)s, I forgot \"%(val)s\" from \"%(key)s\".',)
# Delete an entry
def forget(self, sender, forum, addl, match):
key = match.group('key')
ekey = self.encode_key(key)
try:
self.delete(ekey)
forum.msg(self.gettext('okay', key=key, sender=sender.name()))
except KeyError:
if ' from ' in key:
substr, k = key.split(' from ', 1)
try:
return self.forget_from(sender, forum, k, substr)
except KeyError:
pass
forum.msg(self.gettext('unknown', key=key, sender=sender.name()))
except seedyb.Locked:
forum.msg(self.gettext('locked', key=key,
sender=sender.name()))
bindings.append((re.compile(r"^\008[,: ]+forget (?P<key>.+)$", re.IGNORECASE),
forget))
# Lock an entry
def lock_entry(self, sender, forum, addl, match):
key = match.group('key')
self.lock(key)
forum.msg(self.gettext('okay', key=key, sender=sender.name()))
bindings.append((re.compile(r"^\008[,: ]+lock (?P<key>.+)$", re.IGNORECASE),
lock_entry))
# Unlock an entry
def unlock_entry(self, sender, forum, addl, match):
key = match.group('key')
self.unlock(key)
forum.msg(self.gettext('okay', key=key, sender=sender.name()))
bindings.append((re.compile(r"^\008[,: ]+unlock (?P<key>.+)$", re.IGNORECASE),
unlock_entry))
# Literal entry
def literal(self, sender, forum, addl, match):
key = match.group('key')
val = self.getall(key)
if val:
sv = `val`
out = []
while len(sv) > 300:
s = sv[:300]
sv = sv[300:]
out.append('db[%r] == %s ...' % (key, s))
out.append('db[%r] == %s' % (key, sv))
self.despool(forum, out)
else:
forum.msg(self.gettext('unknown', key=key, sender=sender.name()))
bindings.append((re.compile(r"^\008[,: ]+literal (?P<key>.+)$", re.IGNORECASE),
literal))
# Look something up in the DB
def lookup(self, sender, forum, addl, match):
key = match.group('key')
# Try looking it up verbatim
val = self.get(key)
if not val:
# Try the cleaned version
key = key.rstrip('.?! ')
val = self.get(key)
if val:
val = val % {'me': self.nick,
'forum': forum.name(),
'sender': sender.name()}
if len(val) > 300:
val = val[:297] + '...'
if val[0] == '\\':
forum.msg(val[1:])
elif val[0] == ':':
forum.act(val[1:])
else:
forum.msg(self.gettext('is', key=key, val=val, sender=sender.name()))
elif match.group('me'):
forum.msg(self.gettext('dunno', key=key, sender=sender.name()))
elif match.group('question'):
# Don't allow storage of things like 'what is that?'
pass
else:
return True
def do_store(self, sender, forum, key, val, me, no, also):
resp = False
old = self.getall(key)
okay = self.gettext('okay', sender=sender.name())
if old:
if val in old:
if me:
resp = self.gettext('same', key=key, val=val, old=old,
sender=sender.name())
else:
# Ignore duplicates
resp = self.gettext('same', key=key, val=val, old=old,
sender=sender.name())
pass
elif me:
if also:
self.set(key, old + [val])
resp = okay
elif no:
self.set(key, [val])
resp = okay
else:
if len(old) == 1:
old = old[0]
resp = self.gettext('but', key=key, val=val, old=old,
sender=sender.name())
else:
self.set(key, old + [val])
resp = okay
else:
self.set(key, (val,))
resp = okay
if resp:
if me:
forum.msg(resp)
return False
return True
# Write a new value to the DB
def store(self, sender, forum, addl, match):
key = match.group('key')
val = match.group('val')
# Change % to %%, except for %(
val = val.replace('%', '%%')
val = val.replace('%(', '(')
me = match.group('me')
no = match.group('no') and me
also = val.startswith('also ')
if also:
val = val[5:]
return self.do_store(sender, forum, key, val, me, no, also)
def append_cmd(self, sender, forum, addl, match):
key = match.group('key')
val = match.group('val')
return self.do_store(sender, forum, key, val, me=True, no=True, also=True)
# Pull in BindingsBot things
bindings.extend(BindingsBot.bindings)
# This is first to prevent storing "firebot: what is foo?"
bindings.append((re.compile(r"^(?P<me>\008[,: ]+)?(?P<question>(what|who|where|wtf).*('s|'re| is| are) )(?P<key>.+)$",
re.IGNORECASE),
lookup))
bindings.append((re.compile(r"^(?P<me>\008[,: ]+)append (?P<key>.+) <= (?P<val>.+)",
re.IGNORECASE),
append_cmd))
bindings.append((re.compile((r"^(?P<me>\008)[,: ]+(?P<no>no, *)"
r"(?P<key>.+?) (is|are) (?P<val>.+)$"),
re.IGNORECASE),
store))
bindings.append((re.compile((r"^(?P<no>no,? *)?(?P<me>\008)[,: ]+"
r"(?P<key>.+?) (is|are) (?P<val>.+)$"),
re.IGNORECASE),
store))
bindings.append((re.compile(r"^([^:, ]+[:,] *)?(?P<no>)(?P<me>)(?P<key>.+) (is|are) (?P<val>.+)$",
re.IGNORECASE),
store))
bindings.append((re.compile(r"^(?P<me>\008[,: ]+)?(?P<question>)(?P<key>.+)$",
re.IGNORECASE),
lookup))