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)