Complete rewrite.

This commit is contained in:
Jeremy Fincher 2004-05-06 15:54:26 +00:00
parent b4f977e2dd
commit e3643e0d54
1 changed files with 240 additions and 142 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/python
### ###
# Copyright (c) 2002, Jeremiah Fincher # Copyright (c) 2004, Jeremiah Fincher
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -30,175 +30,273 @@
### ###
""" """
A plugin that tries to emulate Infobot somewhat faithfully. Infobot compatibility, for the parts that we don't support already.
""" """
deprecated = True
__revision__ = "$Id$" __revision__ = "$Id$"
__author__ = 'Jeremy Fincher (jemfinch) <jemfinch@users.sf.net>'
import plugins import plugins
import os
import re import re
import os.path import cPickle as pickle
import conf import conf
import utils
import ircmsgs import ircmsgs
import ircutils
import privmsgs
import callbacks import callbacks
try: conf.registerPlugin('Infobot')
import sqlite
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>'
dbfilename = os.path.join(conf.supybot.directories.data(), 'Infobot.db') def configure(advanced):
# This will be called by setup.py to configure this module. Advanced is
# a bool that specifies whether the user identified himself as an advanced
# user or not. You should effect your configuration by manipulating the
# registry as appropriate.
from questions import expect, anything, something, yn
conf.registerPlugin('Infobot', True)
def makeDb(filename): filename = os.path.join(conf.supybot.directories.data(), 'Infobot.db')
if os.path.exists(filename):
return sqlite.connect(filename)
db = sqlite.connect(filename)
cursor = db.cursor()
cursor.execute("""CREATE TABLE is_factoids (
key TEXT UNIQUE ON CONFLICT REPLACE,
value TEXT
)""")
cursor.execute("""CREATE TABLE are_factoids (
key TEXT UNIQUE ON CONFLICT REPLACE,
value TEXT
)""")
cursor.execute("""CREATE TABLE dont_knows (saying TEXT)""")
for s in ('I don\'t know.', 'No idea.', 'I don\'t have a clue.', 'Dunno.'):
cursor.execute("""INSERT INTO dont_knows VALUES (%s)""", s)
cursor.execute("""CREATE TABLE statements (saying TEXT)""")
for s in ('I heard', 'Rumor has it', 'I think', 'I\'m pretty sure'):
cursor.execute("""INSERT INTO statements VALUES (%s)""", s)
cursor.execute("""CREATE TABLE confirms (saying TEXT)""")
for s in ('Gotcha', 'Ok', '10-4', 'I hear ya', 'Got it'):
cursor.execute("""INSERT INTO confirms VALUES (%s)""", s)
cursor.execute("""CREATE INDEX is_key ON is_factoids (key)""")
cursor.execute("""CREATE INDEX are_key ON are_factoids (key)""")
db.commit()
return db
class Infobot(callbacks.PrivmsgRegexp): class InfobotDB(object):
def __init__(self): def __init__(self):
callbacks.PrivmsgRegexp.__init__(self) try:
self.db = makeDb(dbfilename) fd = file(filename)
except EnvironmentError:
self._is = utils.InsensitivePreservingDict()
self._are = utils.InsensitivePreservingDict()
else:
(self._is, self._are) = pickle.load(fd)
self._changes = 0
self._responses = 0
def flush(self):
fd = file(filename, 'w')
pickle.dump((self._is, self._are), fd)
fd.close()
def close(self):
self.flush()
def getIs(self, factoid):
ret = self._is[factoid]
self._responses += 1
return ret
def setIs(self, fact, oid):
self._changes += 1
self._is[fact] = oid
self.flush()
def delIs(self, factoid):
del self._is[factoid]
self._changes += 1
self.flush()
def hasIs(self, factoid):
return factoid in self._is
def getAre(self, factoid):
ret = self._are[factoid]
self._responses += 1
return ret
def hasAre(self, factoid):
return factoid in self._are
def setAre(self, fact, oid):
self._changes += 1
self._are[fact] = oid
self.flush()
def delAre(self, factoid):
del self._are[factoid]
self._changes += 1
self.flush()
def getChangeCount(self):
return self._changes
def getResponseCount(self):
return self._responses
class Infobot(callbacks.PrivmsgCommandAndRegexp):
regexps = ['doForget', 'doFactoid', 'doUnknown']
def __init__(self):
self.db = InfobotDB()
self.irc = None
self.msg = None
self.force = False
self.replied = False
self.addressed = False
callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self): def die(self):
self.db.commit()
self.db.close() self.db.close()
def getRandomSaying(self, table): def reply(self, s, irc=None, msg=None):
cursor = self.db.cursor() if self.replied:
sql = 'SELECT saying FROM %s ORDER BY random() LIMIT 1' % table self.log.debug('Already replied, not replying again.')
cursor.execute(sql) return
return cursor.fetchone()[0] if irc is None:
assert self.irc is not None
irc = self.irc
if msg is None:
assert self.msg is not None
msg = self.msg
self.replied = True
irc.reply(plugins.standardSubstitute(irc, msg, s))
def getFactoid(self, key): def confirm(self, irc=None, msg=None):
cursor = self.db.cursor() # XXX
cursor.execute('SELECT value FROM is_factoids WHERE key=%s', key) self.reply('Roger that!', irc=irc, msg=msg)
if cursor.rowcount != 0:
statement = self.getRandomSaying('statements')
value = cursor.fetchone()[0]
return '%s %s is %s' % (statement, key, value)
cursor.execute('SELECT value FROM are_factoids WHERE key=%s', key)
if cursor.rowcount != 0:
statement = self.getRandomSaying('statements')
value = cursor.fetchone()[0]
return '%s %s are %s' % (statement, key, value)
raise KeyError, key
def hasFactoid(self, key, isAre): def dunno(self, irc=None, msg=None):
cursor = self.db.cursor() # XXX
sql = 'SELECT COUNT(*) FROM %s_factoids WHERE key=%%s' % isAre self.reply('I dunno, dude.', irc=irc, msg=msg)
cursor.execute(sql, key)
return int(cursor.fetchone()[0])
def insertFactoid(self, key, isAre, value): def factoid(self, key, irc=None, msg=None):
cursor = self.db.cursor() if irc is None:
sql = 'INSERT INTO %s_factoids VALUES (%%s, %%s)' % isAre assert self.irc is not None
cursor.execute(sql, key, value) irc = self.irc
self.db.commit() if msg is None:
assert self.msg is not None
def forget(self, irc, msg, match): msg = self.msg
r"^forget\s+(.+?)(?!\?+)[?.! ]*$" isAre = None
key = match.group(1) key = plugins.standardSubstitute(irc, msg, key)
cursor = self.db.cursor() if self.db.hasIs(key):
cursor.execute('DELETE FROM is_factoids WHERE key=%s', key) isAre = 'is'
cursor.execute('DELETE FROM are_factoids WHERE key=%s', key) value = self.db.getIs(key)
irc.reply(self.getRandomSaying('confirms')) elif self.db.hasAre(key):
isAre = 'are'
def factoid(self, irc, msg, match): value = self.db.getAre(key)
r"^(no[ :,-]+)?(.+?)\s+(was|is|am|were|are)\s+(also\s+)?(.+?)(?!\?+)$" if isAre is None:
(correction, key, isAre, addition, value) = match.groups() if self.addressed:
if self.hasFactoid(key, isAre): self.dunno(irc=irc, msg=msg)
if not correction:
factoid = self.getFactoid(key)
irc.reply('No, %s %s %s' % (key, isAre, factoid))
elif addition:
factoid = self.getFactoid(key)
newFactoid = '%s, or %s' % (factoid, value)
self.insertFactoid(key, isAre, newFactoid)
irc.reply(self.getRandomSaying('confirms'))
else: else:
self.insertFactoid(key, isAre, value) # XXX
irc.reply(self.getRandomSaying('confirms')) self.reply('%s %s %s, $who.' % (key,isAre,value), irc=irc, msg=msg)
def normalize(self, s):
s = ircutils.stripFormatting(s)
s = s.strip() # After stripFormatting for formatted spaces.
s = utils.normalizeWhitespace(s)
contractions = [('what\'s', 'what is'), ('where\'s', 'where is'),
('who\'s', 'who is'),]
for (contraction, replacement) in contractions:
if s.startswith(contraction):
s = replacement + s[len(contraction):]
return s
_forceRe = re.compile(r'no[,: -]+', re.I)
def doPrivmsg(self, irc, msg):
maybeAddressed = callbacks.addressed(irc.nick, msg,
whenAddressedByNick=True)
if maybeAddressed:
self.addressed = True
payload = maybeAddressed
else:
payload = msg.args[1]
print '*', payload
payload = self.normalize(payload)
print '**', payload
maybeForced = self._forceRe.sub('', payload)
if maybeForced != payload:
self.force = True
payload = maybeForced
print '***', payload
if payload.endswith(irc.nick):
self.addressed = True
payload = payload[:-len(irc.nick)]
payload = payload.strip(', ') # Strip punctuation separating nick.
payload += '?' # So doUnknown gets called.
print '****', payload
try:
print 'Payload:', payload
print 'Force:', self.force
print 'Addressed:', self.addressed
msg = ircmsgs.privmsg(msg.args[0], payload, prefix=msg.prefix)
callbacks.PrivmsgCommandAndRegexp.doPrivmsg(self, irc, msg)
finally:
self.force = False
self.replied = False
self.addressed = False
def callCommand(self, f, irc, msg, *L, **kwargs):
try:
self.irc = irc
self.msg = msg
callbacks.PrivmsgCommandAndRegexp.callCommand(self, f, irc, msg,
*L, **kwargs)
finally:
self.irc = None
self.msg = None
def doForget(self, irc, msg, match):
r"^forget\s+(.+?)[?!. ]*$"
fact = match.group(1)
for method in [self.db.delIs, self.db.delAre]:
try:
method(fact)
except KeyError:
pass
self.confirm()
def doUnknown(self, irc, msg, match):
r"^(.+?)\?[?!. ]*$"
key = match.group(1)
key = plugins.standardSubstitute(irc, msg, key)
self.factoid(key) # Does the dunno'ing for us itself.
# TODO: Add invalidCommand.
def doFactoid(self, irc, msg, match):
r"^(.+)\s+(was|is|am|were|are)\s+(also\s+)?(.+?)[?!. ]*$"
(key, isAre, maybeForce, value) = match.groups()
if key.lower() in ('where', 'what', 'who'):
# It's a question.
self.factoid(value)
return
isAre = isAre.lower()
self.force = self.force or bool(maybeForce)
key = plugins.standardSubstitute(irc, msg, key)
value = plugins.standardSubstitute(irc, msg, value)
if isAre in ('was', 'is', 'am'):
if self.db.hasIs(key):
if not self.force:
value = self.db.getIs(key)
self.reply('But %s is %s.' % (key, value))
return return
else: else:
self.insertFactoid(key, isAre, value) value = '%s or %s' % (self.db.getIs(key), value)
irc.reply(self.getRandomSaying('confirms')) self.db.setIs(key, value)
else:
if self.db.hasAre(key):
if not self.force:
value = self.db.getAre(key)
self.reply('But %s are %s.' % (key, value))
return
else:
value = '%s or %s' % (self.db.getAre(key), value)
self.db.setAre(key, value)
if self.addressed or self.force:
self.confirm()
def unknown(self, irc, msg, match): def stats(self, irc, msg, args):
r"^(.+?)\?[?.! ]*$" """takes no arguments
key = match.group(1)
try:
irc.reply(self.getFactoid(key))
except KeyError:
irc.reply(self.getRandomSaying('dont_knows'))
def info(self, irc, msg, match):
r"^info$"
cursor = self.db.cursor()
cursor.execute("SELECT COUNT(*) FROM is_factoids")
numIs = int(cursor.fetchone()[0])
cursor.execute("SELECT COUNT(*) FROM are_factoids")
numAre = int(cursor.fetchone()[0])
s = 'I have %s is factoids and %s are factoids' % (numIs, numAre)
irc.reply(s)
Returns the number of changes and requests made to the Infobot database
since the plugin was loaded.
"""
irc.reply('There have been %s answered and %s made '
'to the database since this plugin was loaded.' %
(utils.nItems('request', self.db.getChangeCount()),
utils.nItems('change', self.db.getResponseCount())))
Class = Infobot Class = Infobot
if __name__ == '__main__':
import sys
if len(sys.argv) < 2 and sys.argv[1] not in ('is', 'are'):
print 'Usage: %s <is|are> <factpack> [<factpack> ...]' % sys.argv[0]
sys.exit(-1)
r = re.compile(r'\s+=>\s+')
db = makeDb(dbfilename)
cursor = db.cursor()
if sys.argv[1] == 'is':
table = 'is_factoids'
else:
table = 'are_factoids'
sql = 'INSERT INTO %s VALUES (%%s, %%s)' % table
for filename in sys.argv[2:]:
fd = file(filename)
for line in fd:
line = line.strip()
if not line or line[0] in ('*', '#'):
continue
else:
try:
(key, value) = r.split(line, 1)
cursor.execute(sql, key, value)
except Exception, e:
print 'Invalid line (%s): %r' %(utils.exnToString(e),line)
db.commit()
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: