mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-03 01:39:23 +01:00
Complete rewrite.
This commit is contained in:
parent
b4f977e2dd
commit
e3643e0d54
@ -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 cursor.fetchone()[0]
|
|
||||||
|
|
||||||
def getFactoid(self, key):
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
cursor.execute('SELECT value FROM is_factoids WHERE key=%s', key)
|
|
||||||
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):
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
sql = 'SELECT COUNT(*) FROM %s_factoids WHERE key=%%s' % isAre
|
|
||||||
cursor.execute(sql, key)
|
|
||||||
return int(cursor.fetchone()[0])
|
|
||||||
|
|
||||||
def insertFactoid(self, key, isAre, value):
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
sql = 'INSERT INTO %s_factoids VALUES (%%s, %%s)' % isAre
|
|
||||||
cursor.execute(sql, key, value)
|
|
||||||
self.db.commit()
|
|
||||||
|
|
||||||
def forget(self, irc, msg, match):
|
|
||||||
r"^forget\s+(.+?)(?!\?+)[?.! ]*$"
|
|
||||||
key = match.group(1)
|
|
||||||
cursor = self.db.cursor()
|
|
||||||
cursor.execute('DELETE FROM is_factoids WHERE key=%s', key)
|
|
||||||
cursor.execute('DELETE FROM are_factoids WHERE key=%s', key)
|
|
||||||
irc.reply(self.getRandomSaying('confirms'))
|
|
||||||
|
|
||||||
def factoid(self, irc, msg, match):
|
|
||||||
r"^(no[ :,-]+)?(.+?)\s+(was|is|am|were|are)\s+(also\s+)?(.+?)(?!\?+)$"
|
|
||||||
(correction, key, isAre, addition, value) = match.groups()
|
|
||||||
if self.hasFactoid(key, isAre):
|
|
||||||
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:
|
|
||||||
self.insertFactoid(key, isAre, value)
|
|
||||||
irc.reply(self.getRandomSaying('confirms'))
|
|
||||||
return
|
return
|
||||||
|
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 confirm(self, irc=None, msg=None):
|
||||||
|
# XXX
|
||||||
|
self.reply('Roger that!', irc=irc, msg=msg)
|
||||||
|
|
||||||
|
def dunno(self, irc=None, msg=None):
|
||||||
|
# XXX
|
||||||
|
self.reply('I dunno, dude.', irc=irc, msg=msg)
|
||||||
|
|
||||||
|
def factoid(self, key, irc=None, msg=None):
|
||||||
|
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
|
||||||
|
isAre = None
|
||||||
|
key = plugins.standardSubstitute(irc, msg, key)
|
||||||
|
if self.db.hasIs(key):
|
||||||
|
isAre = 'is'
|
||||||
|
value = self.db.getIs(key)
|
||||||
|
elif self.db.hasAre(key):
|
||||||
|
isAre = 'are'
|
||||||
|
value = self.db.getAre(key)
|
||||||
|
if isAre is None:
|
||||||
|
if self.addressed:
|
||||||
|
self.dunno(irc=irc, msg=msg)
|
||||||
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 unknown(self, irc, msg, match):
|
def normalize(self, s):
|
||||||
r"^(.+?)\?[?.! ]*$"
|
s = ircutils.stripFormatting(s)
|
||||||
key = match.group(1)
|
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:
|
try:
|
||||||
irc.reply(self.getFactoid(key))
|
print 'Payload:', payload
|
||||||
except KeyError:
|
print 'Force:', self.force
|
||||||
irc.reply(self.getRandomSaying('dont_knows'))
|
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 info(self, irc, msg, match):
|
def callCommand(self, f, irc, msg, *L, **kwargs):
|
||||||
r"^info$"
|
try:
|
||||||
cursor = self.db.cursor()
|
self.irc = irc
|
||||||
cursor.execute("SELECT COUNT(*) FROM is_factoids")
|
self.msg = msg
|
||||||
numIs = int(cursor.fetchone()[0])
|
callbacks.PrivmsgCommandAndRegexp.callCommand(self, f, irc, msg,
|
||||||
cursor.execute("SELECT COUNT(*) FROM are_factoids")
|
*L, **kwargs)
|
||||||
numAre = int(cursor.fetchone()[0])
|
finally:
|
||||||
s = 'I have %s is factoids and %s are factoids' % (numIs, numAre)
|
self.irc = None
|
||||||
irc.reply(s)
|
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
|
||||||
|
else:
|
||||||
|
value = '%s or %s' % (self.db.getIs(key), value)
|
||||||
|
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 stats(self, irc, msg, args):
|
||||||
|
"""takes no arguments
|
||||||
|
|
||||||
|
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:
|
||||||
|
Loading…
Reference in New Issue
Block a user