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)

Reply via email to