#!/usr/bin/env python ### # Copyright (c) 2002, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Silently listens to a channel, building an SQL database of Markov Chains for later hijinks. To read more about Markov Chains, check out . When the database is large enough, you can have it make fun little random messages from it. """ __revision__ = "$Id$" import plugins import os.path import ircmsgs import ircutils import privmsgs import callbacks try: import sqlite except ImportError: raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ 'plugin. Download it at ' def configure(onStart, afterConnect, advanced): # This will be called by setup.py to configure this module. onStart and # afterConnect are both lists. Append to onStart the commands you would # like to be run when the bot is started; append to afterConnect the # commands you would like to be run when the bot has finished connecting. from questions import expect, anything, something, yn onStart.append('load Markov') class Markov(plugins.ChannelDBHandler, callbacks.Privmsg): threaded = True def __init__(self): plugins.ChannelDBHandler.__init__(self) callbacks.Privmsg.__init__(self) def makeDb(self, filename): if os.path.exists(filename): return sqlite.connect(filename) db = sqlite.connect(filename) cursor = db.cursor() cursor.execute("""CREATE TABLE pairs ( id INTEGER PRIMARY KEY, first TEXT, second TEXT, is_first BOOLEAN, UNIQUE (first, second) ON CONFLICT IGNORE )""") cursor.execute("""CREATE TABLE follows ( id INTEGER PRIMARY KEY, pair_id INTEGER, word TEXT )""") cursor.execute("""CREATE INDEX follows_pair_id ON follows (pair_id)""") db.commit() return db def doPrivmsg(self, irc, msg): if not ircutils.isChannel(msg.args[0]): return channel = msg.args[0] db = self.getDb(channel) cursor = db.cursor() if ircmsgs.isAction(msg): words = ircmsgs.unAction(msg).split() else: words = msg.args[1].split() isFirst = True for (first, second, follower) in window(words, 3): if isFirst: cursor.execute("""INSERT OR REPLACE INTO pairs VALUES (NULL, %s, %s, 1)""", first, second) isFirst = False else: cursor.execute("INSERT INTO pairs VALUES (NULL, %s, %s, 0)", first, second) cursor.execute("""SELECT id FROM pairs WHERE first=%s AND second=%s""", first, second) id = int(cursor.fetchone()[0]) cursor.execute("""INSERT INTO follows VALUES (NULL, %s, %s)""", id, follower) if not isFirst: # i.e., if the loop iterated at all. cursor.execute("""INSERT INTO pairs VALUES (NULL, %s, %s, 0)""", second, follower) cursor.execute("""SELECT id FROM pairs WHERE first=%s AND second=%s""", second,follower) id = int(cursor.fetchone()[0]) cursor.execute("INSERT INTO follows VALUES (NULL, %s, NULL)", id) db.commit() _maxMarkovLength = 80 _minMarkovLength = 7 def markov(self, irc, msg, args): """[] Returns a randomly-generated Markov Chain generated sentence from the data kept on (which is only necessary if not sent in the channel itself). """ channel = privmsgs.getChannel(msg, args) db = self.getDb(channel) cursor = db.cursor() words = [] cursor.execute("""SELECT id, first, second FROM pairs WHERE is_first=1 ORDER BY random() LIMIT 1""") if cursor.rowcount == 0: irc.error(msg, 'I have no records for that channel.') return (id, first, second) = cursor.fetchone() id = int(id) words.append(first) words.append(second) sql = """SELECT follows.word FROM pairs, follows WHERE pairs.first=%s AND pairs.second=%s AND pairs.id=follows.pair_id ORDER BY random() LIMIT 1""" while len(words) < self._maxMarkovLength: cursor.execute(sql, words[-2], words[-1]) results = cursor.fetchone() if not results: break word = results[0] if word is None: break words.append(word) if len(words) < self._minMarkovLength: self.markov(irc, msg, args) else: irc.reply(msg, ' '.join(words)) def pairs(self, irc, msg, args): """[] Returns the number of Markov's chain links in the database for . """ channel = privmsgs.getChannel(msg, args) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM pairs""") n = cursor.fetchone()[0] s = 'There are %s pairs in my Markov database for %s' % (n, channel) irc.reply(msg, s) def firsts(self, irc, msg, args): """[] Returns the number of Markov's first links in the database for . """ channel = privmsgs.getChannel(msg, args) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM pairs WHERE is_first=1""") n = cursor.fetchone()[0] s = 'There are %s first pairs in my Markov database for %s'%(n,channel) irc.reply(msg, s) def follows(self, irc, msg, args): """[] Returns the number of Markov's third links in the database for . """ channel = privmsgs.getChannel(msg, args) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM follows""") n = cursor.fetchone()[0] s = 'There are %s follows in my Markov database for %s' % (n, channel) irc.reply(msg, s) def lasts(self, irc, msg, args): """[] Returns the number of Markov's last links in the database for . """ channel = privmsgs.getChannel(msg, args) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM follows WHERE word ISNULL""") n = cursor.fetchone()[0] s = 'There are %s lasts in my Markov database for %s' % (n, channel) irc.reply(msg, s) Class = Markov # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: