irc bot for interacting with a triple store
Kragen Sitaker
kragen at pobox.com
Fri Jul 14 00:08:29 EDT 2006
This bot has a lot of bugs, but here it is:
#!/usr/bin/python
from __future__ import nested_scopes
### backward compatibility stuff
if not globals().has_key('file'): file = open
if not globals().has_key('True'): True = 1; False = 0
import sys
sys.path.insert(0, '/home/kragen/lib/python')
from twisted.protocols import irc
from twisted.internet import reactor, protocol
import time, re, cgi, os, string, urllib, stat
### file stuff
seek_cur = 1
class store:
"An append-only sequence of strings, kept up-to-date with a text file."
def __init__(self, fileobj):
self.lines = []
self.fileobj = fileobj
for line in fileobj.readlines():
if line.endswith('\n'):
self.lines.append(line[:-1])
else:
fileobj.seek(-len(line), seek_cur)
fileobj.truncate()
def append(self, line):
assert "\n" not in line
self.lines.append(line)
self.fileobj.write(line)
self.fileobj.write("\n")
self.fileobj.flush()
### triple store stuff
class triplestore:
def __init__(self, store):
self.triples = []
self.store = store
for line in self.store.lines: self.triples.append(line.split(','))
def add_triple(self, subject, verb, object):
self.triples.append((subject, verb, object))
self.store.append(','.join((subject, verb, object)))
class ts_cli:
def __init__(self, ts):
self.ts = ts
def add_triple(self, *args): return self.ts.add_triple(*args)
def handle_line(self, line, respond):
while line.endswith('?') or line.endswith('.'): line = line[:-1]
sentence = self.find_sentence(line)
if not sentence: return self.handle_nonsentence(line, respond)
s, v, o = sentence
if self.no_pronouns(s) and self.no_pronouns(o):
self.add_triple(s, v, o)
opposite = o, self.opposite(v), s
self.add_triple(*opposite)
respond('ok; also %s %s %s?' % opposite)
else:
if s in self.pronouns and o not in self.pronouns:
ses = []
for ss, vv, oo in self.triples(): # i kidnapped persephone
if v == vv and o == oo: ses.append(ss)
respond('%s %s %s' % (self.list(ses), v, o))
elif o in self.pronouns and s not in self.pronouns:
os = []
for ss, vv, oo in self.triples():
if v == vv and s == ss: os.append(oo)
respond('%s %s %s' % (s, v, self.list(os)))
else:
respond('too hard for me yet!')
def triples(self): return self.ts.triples
def list(self, items):
if len(items) == 0: return "nothing"
if len(items) == 1: return items[0]
if len(items) == 2: return '%s and %s' % (items[0], items[1])
return ', '.join(items[:-1] + ['and %s' % items[-1]])
def handle_nonsentence(self, line, respond):
onsubj = re.match('what about (.*)', line)
if onsubj: respond(self.describe(onsubj.group(1)))
def describe(self, subject):
answers = []
for s, v, o in self.triples():
if s == subject or v == subject:
answers.append('%s %s %s' % (s, v, o))
if not answers: return "nothing known about %s" % subject
return '; '.join(answers) + '.'
pronouns = ['that', 'it', 'this', 'there', 'he', 'who',
'what', 'she', 'they', 'them']
sentence_patterns = [
re.compile(r'\bis ([a-zA-Z]+( [a-zA-Z]+)?)ed by\b'),
re.compile(r'\bis ((?:a|an|the) [a-zA-Z ]+?) of\b'),
]
def find_sentence(self, line):
for pattern in self.sentence_patterns:
myline = line.lower()
while myline:
mo = pattern.search(myline)
if not mo: break
s, v, o = myline[:mo.start(0)], mo.group(0), myline[mo.end(0):]
if (self.acceptable_term(s) and self.acceptable_term(o)):
return (s.strip(), v, o.strip())
myline = o # that's goofy!
return None
def no_pronouns(self, term):
termwords = term.split()
for pronoun in self.pronouns:
if pronoun in termwords: return False
return True
def acceptable_term(self, term):
if '.' in term or ':' in term: return False
return True
def opposite(self, predicate):
patterns = {
'is (.*)ed by': '%ses', # sometimes no e?
'is (.*) of': 'has %s',
'(.*)es': 'is %sed by',
'has (.*)': 'is %s of',
}
for pat in patterns.keys():
mo = re.match(pat, predicate)
if mo: return patterns[pat] % mo.group(1)
raise "Weird predicate", predicate
### bot stuff
# config parameters:
# channel, near the top
# "localhost", 6667, near the bottom, in main(): the IRC servers to talk to
class infobot(irc.IRCClient):
def __init__(self, cli, channels=[('#probolog', None)]):
#self.last_timestamp = time.time()
# ircclient stuff:
self.nickname = 'probolog'
self.realname = 'kragen+probolog'
self.versionName = 'probolog'
self.versionNum = '0'
self.cli = cli
self.channels = channels
print "initted", self.channels
def signedOn(self):
print "joining"
for channel, key in self.channels:
print " - ", channel
self.join(channel, key=key)
def noticed(self, user, channel, message): pass
def privmsg(self, user, channel, message):
self.cli.handle_line(message, lambda msg: self.say(channel, msg))
class botfactory(protocol.ClientFactory):
def clientConnectionLost(self, connector, reason):
reactor.callLater(5, connector.connect)
def clientConnectionFailed(self, connector, reason):
#reactor.stop()
self.clientConnectionLost(connector, reason)
def main(argv):
me = argv[0]
server = argv[1]
channels = argv[2:]
botfilename = 'probolog.csv'
try: botfile = file('probolog.csv', 'r+')
except: botfile = file('probolog.csv', 'w+')
cli = ts_cli(triplestore(store(botfile)))
# You can have more than one client running at once, which is a good idea
#undernetcf = botfactory()
#undernetcf.protocol = lambda: infobot(cli, channels=[
#('#probolog', None), ('#geekcentral', 'compaq')])
#reactor.connectTCP("irc.undernet.org", 6667, undernetcf)
clientf = botfactory()
clientf.protocol = lambda: infobot(cli, channels=[
(chan, None) for chan in channels])
reactor.connectTCP(server, 6667, clientf)
reactor.run()
if __name__ == "__main__": main(sys.argv)
More information about the Kragen-hacks
mailing list