From The Compiler, 7 Years ago, written in Python.
Embed
  1. ###
  2. # Copyright (c) 2012, Florian Bruhin
  3. # All rights reserved.
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions are met:
  7. #
  8. #   * Redistributions of source code must retain the above copyright notice,
  9. #     this list of conditions, and the following disclaimer.
  10. #   * Redistributions in binary form must reproduce the above copyright notice,
  11. #     this list of conditions, and the following disclaimer in the
  12. #     documentation and/or other materials provided with the distribution.
  13. #   * Neither the name of the author of this software nor the name of
  14. #     contributors to this software may be used to endorse or promote products
  15. #     derived from this software without specific prior written consent.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. # ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. # POSSIBILITY OF SUCH DAMAGE.
  28.  
  29. ###
  30.  
  31. import os
  32. import time
  33. import shlex
  34. import string
  35.  
  36. from cStringIO import StringIO
  37.  
  38. import supybot.conf as conf
  39. import supybot.ircdb as ircdb
  40. import supybot.utils as utils
  41. from supybot.commands import *
  42. import supybot.plugins as plugins
  43. import supybot.ircutils as ircutils
  44. import supybot.callbacks as callbacks
  45. from supybot.i18n import PluginInternationalization, internationalizeDocstring
  46.  
  47. _ = PluginInternationalization('InfobotFactoids')
  48.  
  49. allchars = string.maketrans('', '')
  50. class OptionList(object):
  51.     validChars = allchars.translate(allchars, '|()')
  52.     def _insideParens(self, lexer):
  53.         ret = []
  54.         while True:
  55.             token = lexer.get_token()
  56.             if not token:
  57.                 return '(%s' % ''.join(ret) #)
  58.             elif token == ')':
  59.                 if '|' in ret:
  60.                     L = map(''.join,
  61.                             utils.iter.split('|'.__eq__, ret,
  62.                                              yieldEmpty=True))
  63.                     return utils.iter.choice(L)
  64.                 else:
  65.                     return '(%s)' % ''.join(ret)
  66.             elif token == '(':
  67.                 ret.append(self._insideParens(lexer))
  68.             elif token == '|':
  69.                 ret.append(token)
  70.             else:
  71.                 ret.append(token)
  72.  
  73.     def tokenize(self, s):
  74.         lexer = shlex.shlex(StringIO(s))
  75.         lexer.commenters = ''
  76.         lexer.quotes = ''
  77.         lexer.whitespace = ''
  78.         lexer.wordchars = self.validChars
  79.         ret = []
  80.         while True:
  81.             token = lexer.get_token()
  82.             if not token:
  83.                 break
  84.             elif token == '(':
  85.                 ret.append(self._insideParens(lexer))
  86.             else:
  87.                 ret.append(token)
  88.         return ''.join(ret)
  89.  
  90. def pickOptions(s):
  91.     return OptionList().tokenize(s)
  92.  
  93. class SqliteInfobotDB(object):
  94.     def __init__(self, filename):
  95.         self.filename = filename
  96.         self.dbs = ircutils.IrcDict()
  97.  
  98.     def close(self):
  99.         for db in self.dbs.itervalues():
  100.             db.close()
  101.         self.dbs.clear()
  102.  
  103.     def _getDb(self, channel):
  104.         try:
  105.             import sqlite3
  106.         except ImportError:
  107.             from pysqlite2 import dbapi2 as sqlite3 # for python2.4
  108.  
  109.         if channel in self.dbs:
  110.             return self.dbs[channel]
  111.         filename = plugins.makeChannelFilename(self.filename, channel)
  112.        
  113.         if os.path.exists(filename):
  114.             db = sqlite3.connect(filename, check_same_thread = False)
  115.             db.text_factory = str
  116.             self.dbs[channel] = db
  117.             return db
  118.         db = sqlite3.connect(filename, check_same_thread = False)
  119.         db.text_factory = str
  120.         self.dbs[channel] = db
  121.         cursor = db.cursor()
  122.         cursor.execute("""CREATE TABLE factoids (
  123.                          factoid_key TEXT PRIMARY KEY NOT NULL,
  124.                          requested_by TEXT default NULL,
  125.                          requested_time TIMESTAMP default NULL,
  126.                          requested_count INTEGER NOT NULL default '0',
  127.                          created_by TEXT default NULL,
  128.                          created_time TIMESTAMP default NULL,
  129.                          modified_by TEXT default NULL,
  130.                          modified_time TIMESTAMP default NULL,
  131.                          locked_by TEXT default NULL,
  132.                          locked_time TIMESTAMP default NULL,
  133.                          factoid_value TEXT NOT NULL
  134.                          )""")
  135.         db.commit()
  136.         return db
  137.  
  138.     def getFactoid(self, channel, key):
  139.         db = self._getDb(channel)
  140.         cursor = db.cursor()
  141.         cursor.execute("""SELECT factoid_value FROM factoids
  142.                          WHERE factoid_key LIKE ?""", (key,))
  143.         results = cursor.fetchall()
  144.         if len(results) == 0:
  145.             return None
  146.         else:
  147.             return results[0]
  148.  
  149.     def getFactinfo(self, channel, key):
  150.         db = self._getDb(channel)
  151.         cursor = db.cursor()
  152.         cursor.execute("""SELECT created_by, created_time,
  153.                                 modified_by, modified_time,
  154.                                 requested_by, requested_time,
  155.                                 requested_count, locked_by, locked_time
  156.                          FROM factoids
  157.                          WHERE factoid_key LIKE ?""", (key,))
  158.         results = cursor.fetchall()
  159.         if len(results) == 0:
  160.             return None
  161.         else:
  162.             return results[0]
  163.  
  164.     def randomFactoid(self, channel):
  165.         db = self._getDb(channel)
  166.         cursor = db.cursor()
  167.         cursor.execute("""SELECT factoid_value, factoid_key FROM factoids
  168.                          ORDER BY random() LIMIT 1""")
  169.         results = cursor.fetchall()
  170.         if len(results) == 0:
  171.             return None
  172.         else:
  173.             return results[0]
  174.  
  175.     def addFactoid(self, channel, key, value, creator_id):
  176.         db = self._getDb(channel)
  177.         cursor = db.cursor()
  178.         cursor.execute("""INSERT INTO factoids VALUES
  179.                          (?, NULL, NULL, 0, ?, ?, NULL,
  180.                           NULL, NULL, NULL, ?)""",
  181.                            (key, creator_id, int(time.time()), value))
  182.         db.commit()
  183.  
  184.     def updateFactoid(self, channel, key, newvalue, modifier_id):
  185.         db = self._getDb(channel)
  186.         cursor = db.cursor()
  187.         cursor.execute("""UPDATE factoids
  188.                          SET factoid_value=?, modified_by=?,
  189.                          modified_time=? WHERE factoid_key LIKE ?""",
  190.                           (newvalue, modifier_id, int(time.time()), key))
  191.         db.commit()
  192.  
  193.     def updateRequest(self, channel, key, hostmask):
  194.         db = self._getDb(channel)
  195.         cursor = db.cursor()
  196.         cursor.execute("""UPDATE factoids SET
  197.                          requested_by = ?,
  198.                          requested_time = ?,
  199.                          requested_count = requested_count + 1
  200.                          WHERE factoid_key = ?""",
  201.                           (hostmask, int(time.time()), key))
  202.         db.commit()
  203.  
  204.     def removeFactoid(self, channel, key):
  205.         db = self._getDb(channel)
  206.         cursor = db.cursor()
  207.         cursor.execute("""DELETE FROM factoids WHERE factoid_key LIKE ?""",
  208.                           (key,))
  209.         db.commit()
  210.  
  211.     def locked(self, channel, key):
  212.         db = self._getDb(channel)
  213.         cursor = db.cursor()
  214.         cursor.execute ("""SELECT locked_by FROM factoids
  215.                           WHERE factoid_key LIKE ?""", (key,))
  216.         if cursor.fetchone()[0] is None:
  217.             return False
  218.         else:
  219.             return True
  220.  
  221.     def lock(self, channel, key, locker_id):
  222.         db = self._getDb(channel)
  223.         cursor = db.cursor()
  224.         cursor.execute("""UPDATE factoids
  225.                          SET locked_by=?, locked_time=?
  226.                          WHERE factoid_key LIKE ?""",
  227.                           (locker_id, int(time.time()), key))
  228.         db.commit()
  229.  
  230.     def unlock(self, channel, key):
  231.         db = self._getDb(channel)
  232.         cursor = db.cursor()
  233.         cursor.execute("""UPDATE factoids
  234.                          SET locked_by=?, locked_time=?
  235.                          WHERE factoid_key LIKE ?""", (None, None, key))
  236.         db.commit()
  237.  
  238.     def mostAuthored(self, channel, limit):
  239.         db = self._getDb(channel)
  240.         cursor = db.cursor()
  241.         cursor.execute("""SELECT created_by, count(factoid_key) FROM factoids
  242.                          GROUP BY created_by
  243.                          ORDER BY count(factoid_key) DESC LIMIT ?""", (limit,))
  244.         return cursor.fetchall()
  245.  
  246.     def mostRecent(self, channel, limit):
  247.         db = self._getDb(channel)
  248.         cursor = db.cursor()
  249.         cursor.execute("""SELECT factoid_key FROM factoids
  250.                          ORDER BY created_time DESC LIMIT ?""", (limit,))
  251.         return cursor.fetchall()
  252.  
  253.     def mostPopular(self, channel, limit):
  254.         db = self._getDb(channel)
  255.         cursor = db.cursor()
  256.         cursor.execute("""SELECT factoid_key, requested_count FROM factoids
  257.                          WHERE requested_count > 0
  258.                          ORDER BY requested_count DESC LIMIT ?""", (limit,))
  259.         results = cursor.fetchall()
  260.         return results
  261.  
  262.     def getKeysByAuthor(self, channel, authorId):
  263.         db = self._getDb(channel)
  264.         cursor = db.cursor()
  265.         cursor.execute("""SELECT factoid_key FROM factoids WHERE created_by=?
  266.                          ORDER BY factoid_key""", (authorId,))
  267.         results = cursor.fetchall()
  268.         return results
  269.  
  270.     def getKeysByGlob(self, channel, glob):
  271.         db = self._getDb(channel)
  272.         cursor = db.cursor()
  273.         glob = '%%%s%%' % glob
  274.         cursor.execute("""SELECT factoid_key FROM factoids WHERE factoid_key LIKE ?
  275.                          ORDER BY factoid_key""", (glob,))
  276.         results = cursor.fetchall()
  277.         return results
  278.  
  279.     def getKeysByValueGlob(self, channel, glob):
  280.         db = self._getDb(channel)
  281.         cursor = db.cursor()
  282.         glob = '%%%s%%' % glob
  283.         cursor.execute("""SELECT factoid_key FROM factoids WHERE factoid_value LIKE ?
  284.                          ORDER BY factoid_key""", (glob,))
  285.         results = cursor.fetchall()
  286.         return results
  287.  
  288. InfobotDB = plugins.DB('InfobotFactoids', {'sqlite3': SqliteInfobotDB})
  289.  
  290. class InfobotFactoids(callbacks.Plugin):
  291.     """Add the help for "@help InfobotFactoids" here (assuming you don't
  292.    implement a InfobotFactoids command).  This should describe *how* to use
  293.    this plugin."""
  294.     callBefore = ['Dunno']
  295.     def __init__(self, irc):
  296.         self.db = InfobotDB()
  297.         self.__parent = super(InfobotFactoids, self)
  298.         self.__parent.__init__(irc)
  299.  
  300.     def die(self):
  301.         self.__parent.die()
  302.         self.db.close()
  303.  
  304.     def reset(self):
  305.         self.db.close()
  306.  
  307.     _replyTag = '<reply>'
  308.     _actionTag = '<action>'
  309.     def _parseFactoid(self, irc, msg, fact):
  310.         type = 'define'  # Default is to just spit the factoid back as a
  311.                          # definition of what the key is (i.e., "foo is bar")
  312.         newfact = pickOptions(fact)
  313.         if newfact.startswith(self._replyTag):
  314.             newfact = newfact[len(self._replyTag):]
  315.             type = 'reply'
  316.         elif newfact.startswith(self._actionTag):
  317.             newfact = newfact[len(self._actionTag):]
  318.             type = 'action'
  319.         newfact = newfact.strip()
  320.         newfact = ircutils.standardSubstitute(irc, msg, newfact)
  321.         return (type, newfact)
  322.  
  323.     def invalidCommand(self, irc, msg, tokens):
  324.         if '=~' in tokens:
  325.             self.changeFactoid(irc, msg, tokens)
  326.         elif tokens and tokens[0] in ('no', 'no,'):
  327.             self.replaceFactoid(irc, msg, tokens)
  328.         elif ['is', 'also'] in utils.seq.window(tokens, 2):
  329.             self.augmentFactoid(irc, msg, tokens)
  330.         else:
  331.             key = ' '.join(tokens)
  332.             key = self._sanitizeKey(key)
  333.             channel = plugins.getChannel(msg.args[0])
  334.             fact = self.db.getFactoid(channel, key)
  335.             if fact:
  336.                 self.db.updateRequest(channel, key, msg.prefix)
  337.                 # getFactoid returns "all results", so we need to extract the
  338.                 # first one.
  339.                 fact = fact[0]
  340.                 # Update the requested count/requested by for this key
  341.                 hostmask = msg.prefix
  342.                 # Now actually get the factoid and respond accordingly
  343.                 (type, text) = self._parseFactoid(irc, msg, fact)
  344.                 if type == 'action':
  345.                     irc.reply(text, action=True)
  346.                 elif type == 'reply':
  347.                     irc.reply(text, prefixNick=False)
  348.                 elif type == 'define':
  349.                     irc.reply(format('%s is %s', key, text), prefixNick=False)
  350.                 else:
  351.                     assert False, 'Spurious type from _parseFactoid'
  352.             else:
  353.                 if 'is' in tokens or '_is_' in tokens:
  354.                     self.addFactoid(irc, msg, tokens)
  355.  
  356.     def _getUserId(self, irc, prefix):
  357.         try:
  358.             return ircdb.users.getUserId(prefix)
  359.         except KeyError:
  360.             irc.errorNotRegistered(Raise=True)
  361.  
  362.     def _sanitizeKey(self, key):
  363.         return key.rstrip('!? ')
  364.  
  365.     def _checkNotLocked(self, irc, channel, key):
  366.         if self.db.locked(channel, key):
  367.             irc.error(format('Factoid %q is locked.', key), Raise=True)
  368.  
  369.     def _getFactoid(self, irc, channel, key):
  370.         fact = self.db.getFactoid(channel, key)
  371.         if fact is not None:
  372.             return fact
  373.         else:
  374.             irc.error(format('Factoid %q not found.', key), Raise=True)
  375.  
  376.     def _getKeyAndFactoid(self, tokens):
  377.         if '_is_' in tokens:
  378.             p = '_is_'.__eq__
  379.         elif 'is' in tokens:
  380.             p = 'is'.__eq__
  381.         else:
  382.             self.log.debug('Invalid tokens for {add,replace}Factoid: %s.',
  383.                            tokens)
  384.             s = 'Missing an \'is\' or \'_is_\'.'
  385.             raise ValueError, s
  386.         (key, newfact) = map(' '.join, utils.iter.split(p, tokens, maxsplit=1))
  387.         key = self._sanitizeKey(key)
  388.         return (key, newfact)
  389.  
  390.     def addFactoid(self, irc, msg, tokens):
  391.         # First, check and see if the entire message matches a factoid key
  392.         channel = plugins.getChannel(msg.args[0])
  393.         id = msg.prefix
  394.         try:
  395.             (key, fact) = self._getKeyAndFactoid(tokens)
  396.         except ValueError, e:
  397.             irc.error(str(e), Raise=True)
  398.         # Check and make sure it's not in the DB already
  399.         if self.db.getFactoid(channel, key):
  400.             irc.error(format('Factoid %q already exists.', key), Raise=True)
  401.         self.db.addFactoid(channel, key, fact, id)
  402.         irc.replySuccess()
  403.  
  404.     def changeFactoid(self, irc, msg, tokens):
  405.         id = msg.prefix
  406.         (key, regexp) = map(' '.join,
  407.                             utils.iter.split('=~'.__eq__, tokens, maxsplit=1))
  408.         channel = plugins.getChannel(msg.args[0])
  409.         # Check and make sure it's in the DB
  410.         fact = self._getFactoid(irc, channel, key)
  411.         self._checkNotLocked(irc, channel, key)
  412.         # It's fair game if we get to here
  413.         try:
  414.             r = utils.str.perlReToReplacer(regexp)
  415.         except ValueError, e:
  416.             irc.errorInvalid('regexp', regexp, Raise=True)
  417.         fact = fact[0]
  418.         new_fact = r(fact)
  419.         self.db.updateFactoid(channel, key, new_fact, id)
  420.         irc.replySuccess()
  421.  
  422.     def augmentFactoid(self, irc, msg, tokens):
  423.         # Must be registered!
  424.         id = msg.prefix
  425.         pairs = list(utils.seq.window(tokens, 2))
  426.         isAlso = pairs.index(['is', 'also'])
  427.         key = ' '.join(tokens[:isAlso])
  428.         new_text = ' '.join(tokens[isAlso+2:])
  429.         channel = plugins.getChannel(msg.args[0])
  430.         fact = self._getFactoid(irc, channel, key)
  431.         self._checkNotLocked(irc, channel, key)
  432.         # It's fair game if we get to here
  433.         fact = fact[0]
  434.         new_fact = format('%s, or %s', fact, new_text)
  435.         self.db.updateFactoid(channel, key, new_fact, id)
  436.         irc.replySuccess()
  437.  
  438.     def replaceFactoid(self, irc, msg, tokens):
  439.         # Must be registered!
  440.         channel = plugins.getChannel(msg.args[0])
  441.         id = msg.prefix
  442.         del tokens[0] # remove the "no,"
  443.         try:
  444.             (key, fact) = self._getKeyAndFactoid(tokens)
  445.         except ValueError, e:
  446.             irc.error(str(e), Raise=True)
  447.         _ = self._getFactoid(irc, channel, key)
  448.         self._checkNotLocked(irc, channel, key)
  449.         self.db.removeFactoid(channel, key)
  450.         self.db.addFactoid(channel, key, fact, id)
  451.         irc.replySuccess()
  452.  
  453.     def literal(self, irc, msg, args, channel, key):
  454.         """[<channel>] <factoid key>
  455.  
  456.        Returns the literal factoid for the given factoid key.  No parsing of
  457.        the factoid value is done as it is with normal retrieval.  <channel>
  458.        is only necessary if the message isn't sent in the channel itself.
  459.        """
  460.         fact = self._getFactoid(irc, channel, key)
  461.         fact = fact[0]
  462.         irc.reply(fact)
  463.     literal = wrap(literal, ['channeldb', 'text'])
  464.  
  465.     def factinfo(self, irc, msg, args, channel, key):
  466.         # FIXME last modified by NULL
  467.         """[<channel>] <factoid key>
  468.  
  469.        Returns the various bits of info on the factoid for the given key.
  470.        <channel> is only necessary if the message isn't sent in the channel
  471.        itself.
  472.        """
  473.         # Start building the response string
  474.         s = key + ': '
  475.         # Next, get all the info and build the response piece by piece
  476.         info = self.db.getFactinfo(channel, key)
  477.         if not info:
  478.             irc.error(format('No such factoid: %q', key))
  479.             return
  480.         (created_by, created_at, modified_by, modified_at, last_requested_by,
  481.          last_requested_at, requested_count, locked_by, locked_at) = info
  482.         # First, creation info.
  483.         # Map the integer created_by to the username
  484.         #created_by = plugins.getUserName(created_by)
  485.         created_at = time.strftime(conf.supybot.reply.format.time(),
  486.                                    time.localtime(int(created_at)))
  487.         s += format('Created by %s on %s.', created_by, created_at)
  488.         # Next, modification info, if any.
  489.         if modified_by is not None:
  490.             #modified_by = plugins.getUserName(modified_by)
  491.             modified_at = time.strftime(conf.supybot.reply.format.time(),
  492.                                    time.localtime(int(modified_at)))
  493.             s += format(' Last modified by %s on %s.',modified_by, modified_at)
  494.         # Next, last requested info, if any
  495.         if last_requested_by is not None:
  496.             last_by = last_requested_by  # not an int user id
  497.             last_at = time.strftime(conf.supybot.reply.format.time(),
  498.                                     time.localtime(int(last_requested_at)))
  499.             req_count = requested_count
  500.             s += format(' Last requested by %s on %s, requested %n.',
  501.                         last_by, last_at, (requested_count, 'time'))
  502.         # Last, locked info
  503.         if locked_at is not None:
  504.             lock_at = time.strftime(conf.supybot.reply.format.time(),
  505.                                      time.localtime(int(locked_at)))
  506.             lock_by = plugins.getUserName(locked_by)
  507.             s += format(' Locked by %s on %s.', lock_by, lock_at)
  508.         irc.reply(s)
  509.     factinfo = wrap(factinfo, ['channeldb', 'text'])
  510.  
  511.     def _lock(self, irc, msg, channel, user, key, locking=True):
  512.         #self.log.debug('in _lock')
  513.         #self.log.debug('id: %s', id)
  514.         id = user.id
  515.         info = self.db.getFactinfo(channel, key)
  516.         if not info:
  517.             irc.error(format('No such factoid: %q', key))
  518.             return
  519.         (created_by, _, _, _, _, _, _, locked_by, _) = info
  520.         # Don't perform redundant operations
  521.         if locking and locked_by is not None:
  522.                irc.error(format('Factoid %q is already locked.', key))
  523.                return
  524.         if not locking and locked_by is None:
  525.                irc.error(format('Factoid %q is not locked.', key))
  526.                return
  527.         # Can only lock/unlock own factoids unless you're an admin
  528.         #self.log.debug('admin?: %s', ircdb.checkCapability(id, 'admin'))
  529.         #self.log.debug('created_by: %s', created_by)
  530.         if not (ircdb.checkCapability(id, 'admin')): # FIXME allow to lock own factoids
  531.             if locking:
  532.                 s = 'lock'
  533.             else:
  534.                 s = 'unlock'
  535.             irc.error(format('Cannot %s someone else\'s factoid unless you '
  536.                              'are an admin.', s))
  537.             return
  538.         # Okay, we're done, ready to lock/unlock
  539.         if locking:
  540.            self.db.lock(channel, key, id)
  541.         else:
  542.            self.db.unlock(channel, key)
  543.         irc.replySuccess()
  544.  
  545.     def lock(self, irc, msg, args, channel, user, key):
  546.         """[<channel>] <factoid key>
  547.  
  548.        Locks the factoid with the given factoid key.  Requires that the user
  549.        be registered and have created the factoid originally.  <channel> is
  550.        only necessary if the message isn't sent in the channel itself.
  551.        """
  552.         self._lock(irc, msg, channel, user, key, True)
  553.     lock = wrap(lock, ['channeldb', 'user', 'text'])
  554.  
  555.     def unlock(self, irc, msg, args, channel, user, key):
  556.         """[<channel>] <factoid key>
  557.  
  558.        Unlocks the factoid with the given factoid key.  Requires that the
  559.        user be registered and have locked the factoid.  <channel> is only
  560.        necessary if the message isn't sent in the channel itself.
  561.        """
  562.         self._lock(irc, msg, channel, user, key, False)
  563.     unlock = wrap(unlock, ['channeldb', 'user', 'text'])
  564.  
  565.     def most(self, irc, msg, args, channel, method):
  566.         """[<channel>] {popular|authored|recent}
  567.  
  568.        Lists the most {popular|authored|recent} factoids.  "popular" lists the
  569.        most frequently requested factoids.  "authored" lists the author with
  570.        the most factoids.  "recent" lists the most recently created factoids.
  571.        <channel> is only necessary if the message isn't sent in the channel
  572.        itself.
  573.        """
  574.         method = method.capitalize()
  575.         method = getattr(self, '_most%s' % method, None)
  576.         if method is None:
  577.             raise callbacks.ArgumentError
  578.         limit = self.registryValue('mostCount', channel)
  579.         method(irc, channel, limit)
  580.     most = wrap(most, ['channeldb',
  581.                        ('literal', ('popular', 'authored', 'recent'))])
  582.  
  583.     def _mostAuthored(self, irc, channel, limit):
  584.         results = self.db.mostAuthored(channel, limit)
  585.         L = ['%s (%s)' % (plugins.getUserName(t[0]), int(t[1]))
  586.              for t in results]
  587.         if L:
  588.             author = 'author'
  589.             if len(L) != 1:
  590.                 author = 'authors'
  591.             irc.reply(format('Most prolific %s: %L', author, L))
  592.         else:
  593.             irc.error('There are no factoids in my database.')
  594.  
  595.     def _mostRecent(self, irc, channel, limit):
  596.         results = self.db.mostRecent(channel, limit)
  597.         L = [format('%q', t[0]) for t in results]
  598.         if L:
  599.             irc.reply(format('%n: %L', (len(L), 'latest', 'factoid'), L))
  600.         else:
  601.             irc.error('There are no factoids in my database.')
  602.  
  603.     def _mostPopular(self, irc, channel, limit):
  604.         results = self.db.mostPopular(channel, limit)
  605.         L = [format('%q (%s)', t[0], t[1]) for t in results]
  606.         if L:
  607.             irc.reply(
  608.                 format('Top %n: %L', (len(L), 'requested', 'factoid'), L))
  609.         else:
  610.             irc.error('No factoids have been requested from my database.')
  611.  
  612.     def listauth(self, irc, msg, args, channel, author):
  613.         """[<channel>] <author name>
  614.  
  615.        Lists the keys of the factoids with the given author.  Note that if an
  616.        author has an integer name, you'll have to use that author's id to use
  617.        this function (so don't use integer usernames!).  <channel> is only
  618.        necessary if the message isn't sent in the channel itself.
  619.        """
  620.         try:
  621.             id = ircdb.users.getUserId(author) # FIXME no id's
  622.         except KeyError:
  623.             irc.errorNoUser(name=author, Raise=True)
  624.         results = self.db.getKeysByAuthor(channel, id)
  625.         if not results:
  626.             irc.reply(format('No factoids by %q found.', author))
  627.             return
  628.         keys = [format('%q', t[0]) for t in results]
  629.         s = format('Author search for %q (%i found): %L',
  630.                    author, len(keys), keys)
  631.         irc.reply(s)
  632.     listauth = wrap(listauth, ['channeldb', 'something'])
  633.  
  634.     def listkeys(self, irc, msg, args, channel, search):
  635.         """[<channel>] <text>
  636.  
  637.        Lists the keys of the factoids whose key contains the provided text.
  638.        <channel> is only necessary if the message isn't sent in the channel
  639.        itself.
  640.        """
  641.         results = self.db.getKeysByGlob(channel, search)
  642.         if not results:
  643.             irc.reply(format('No keys matching %q found.', search))
  644.         elif len(results) == 1 and \
  645.              self.registryValue('showFactoidIfOnlyOneMatch', channel):
  646.             key = results[0][0]
  647.             self.invalidCommand(irc, msg, [key])
  648.         else:
  649.             keys = [format('%q', tup[0]) for tup in results]
  650.             s = format('Key search for %q (%i found): %L',
  651.                        search, len(keys), keys)
  652.             irc.reply(s)
  653.     listkeys = wrap(listkeys, ['channeldb', 'text'])
  654.  
  655.     def listvalues(self, irc, msg, args, channel, search):
  656.         """[<channel>] <text>
  657.  
  658.        Lists the keys of the factoids whose value contains the provided text.
  659.        <channel> is only necessary if the message isn't sent in the channel
  660.        itself.
  661.        """
  662.         results = self.db.getKeysByValueGlob(channel, search)
  663.         if not results:
  664.             irc.reply(format('No values matching %q found.', search))
  665.             return
  666.         keys = [format('%q', tup[0]) for tup in results]
  667.         s = format('Value search for %q (%i found): %L',
  668.                    search, len(keys), keys)
  669.         irc.reply(s)
  670.     listvalues = wrap(listvalues, ['channeldb', 'text'])
  671.  
  672.     def remove(self, irc, msg, args, channel, _, key):
  673.         """[<channel>] <factoid key>
  674.  
  675.        Deletes the factoid with the given key.  <channel> is only necessary
  676.        if the message isn't sent in the channel itself.
  677.        """
  678.         _ = self._getFactoid(irc, channel, key)
  679.         self._checkNotLocked(irc, channel, key)
  680.         self.db.removeFactoid(channel, key)
  681.         irc.replySuccess()
  682.     remove = wrap(remove, ['channeldb', 'user', 'text'])
  683.  
  684.     def random(self, irc, msg, args, channel):
  685.         """[<channel>]
  686.  
  687.        Displays a random factoid (along with its key) from the database.
  688.        <channel> is only necessary if the message isn't sent in the channel
  689.        itself.
  690.        """
  691.         results = self.db.randomFactoid(channel)
  692.         if not results:
  693.             irc.error('No factoids in the database.')
  694.             return
  695.         (fact, key) = results
  696.         irc.reply(format('Random factoid: %q is %q', key, fact))
  697.     random = wrap(random, ['channeldb'])
  698.  
  699.  
  700. Class = InfobotFactoids
  701.  
  702. @internationalizeDocstring
  703. class InfobotFactoids(callbacks.Plugin):
  704.     """Add the help for "@plugin help InfobotFactoids" here
  705.    This should describe *how* to use this plugin."""
  706.     pass
  707.  
  708. # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: