Encyclopedia made neat.

This commit is contained in:
Dennis Kaarsemaker 2007-02-04 18:35:40 +01:00
parent 139d186c83
commit f3ffcabca9
15 changed files with 922 additions and 616 deletions

View File

@ -721,7 +721,7 @@ class Str(IBugtracker):
assignee = strip_tags(l[l.find('<td>')+4:l.find('</td>')])
if assignee == 'Unassigned':
assignee = 'nobody'
return [(id, package, title, severity, status, assignee, "%s/L%d" % (self.url, id))]
return [(id, package, title, severity, status, assignee, "%s?L%d" % (self.url, id))]
sfre = re.compile(r"""

View File

@ -1,2 +1,71 @@
This plugin is developed as factoid and package info plugin but has grown a few
extra functions that are a bit out of place.
Copyright (c) 2006-2007, Dennis Kaarsemaker
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
This plugin is a rather intricate combination of a factoid plugin and package
info lookup via apt. Here's how to set it up:
Pick a name for your database. A lowercase-only name without spaces is probably
best, this example wil use myfactoids as name. Then create a directory to store
your databases in (somewere in $botdir/data would be best). In the new directory
create an sqlite database with the following command:
sqlite myfactoids.db
CREATE TABLE facts (
id INTEGER PRIMARY KEY,
author VARCHAR(100) NOT NULL,
name VARCHAR(20) NOT NULL,
added DATETIME,
value VARCHAR(200) NOT NULL,
popularity INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE log (
id INTEGER PRIMARY KEY,
author VARCHAR(100) NOT NULL,
name VARCHAR(20) NOT NULL,
added DATETIME,
oldvalue VARCHAR(200) NOT NULL
);
If you want to create more databases, repeat these last two steps.
When the databases exist, you need to configure the bots to actually use them.
To do that, set the global value supybot.plugins.encyclopedia.datadir to the new
dirand the channel value supybot.plugins.encyclopedia.database to the name of
the database (without the .db suffix).
Documentation on adding/editing factoids can be found on
https://wiki.ubuntu.com/UbuntuBots To give people edit access, let them register
with your bot and use: %addeditor nickname_here (replace % with your prefix
char). Similarly you can use removeeditor :).
The web interface is a simple cgi script with some templates, css and the
commoncgi.py file from the bzr tree. Make sure you set the variables datadir and
database in factoids.cgi to the correct values. Also set default_db to the one
you want to show by default.
To get package lookup working, you need to set the variable
supybot.plugins.encyclopedia.aptdir to the name of a new, empty directory. In
this directory, you creae sources.list files for every distrorelease you want to
search. The name of the file is important, since the filename (without the .list
suffix) is the name that is used to refer to the distrorelease.
Whenever you create a new .list file, it is important to run the update_apt
and update_apt_file scripts that comes with this plugin. Before you run these,
you have to edit them to point to your apt dir. It's also useful to run them
periodically from cron (say, once per week for update_apt and once per moth for
update_apt_file). You also need to reload the plugin to make it pick up the new
releases.
It is very useful to set the supybot.plugins.encyclopedia.searchorder value to a
space separated list of release names. That way you can limit the (expensive)
searching for packages to a small set of releases.

View File

@ -1,43 +1,37 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
"""
Add a description of the plugin (to be presented to the user inside the wizard)
here. This should describe *what* the plugin does.
This plugin is a factoid encyclopedia and has Ubuntu/Debian package&file lookup
funtionality
"""
import supybot
import supybot.world as world
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
__version__ = ""
# XXX Replace this with an appropriate author or supybot.Author instance.
__author__ = supybot.authors.unknown
# This is a dictionary mapping supybot.Author instances to lists of
# contributions.
__version__ = "2.1"
__author__ = supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net")
__contributors__ = {}
# This is a url where the most recent plugin package can be downloaded.
__url__ = '' # 'http://supybot.com/Members/yourname/Factoid plugin/download'
__url__ = 'https://bots.ubuntulinux.nl'
import config
reload(config)
import plugin_ng
reload(plugin_ng)
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin_ng.Class
Class = plugin.Class
configure = config.configure
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -1,7 +1,14 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
@ -9,32 +16,25 @@ import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
# This will be called by supybot 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 supybot.questions import expect, anything, something, yn
conf.registerPlugin('Encyclopedia', True)
Encyclopedia = conf.registerPlugin('Encyclopedia')
# This is where your configuration variables (if any) should go. For example:
# conf.registerGlobalValue(Factoid, 'someConfigVariableName',
# registry.Boolean(False, """Help for someConfigVariableName."""))
conf.registerChannelValue(Encyclopedia, 'database',
registry.String('', 'Name of database to use'))
conf.registerGlobalValue(Encyclopedia, 'packagelookup',
registry.Boolean(True, "Whether to look up packages"))
conf.registerChannelValue(Encyclopedia, 'fallbackdb',
registry.String('ubuntu', 'Fallback database'))
conf.registerGlobalValue(Encyclopedia, 'fallbackchannel',
registry.String('#ubuntu', 'Fallback channel'))
conf.registerGlobalValue(Encyclopedia, 'relaychannel',
registry.String('#ubuntu-ops', 'Relay channel for unauthorized edits'))
conf.registerGlobalValue(Encyclopedia, 'notfoundmsg',
registry.String('Factoid %s not found', 'Reply when factoid isn\'t found'))
conf.registerGlobalValue(Encyclopedia,'prefixchar',
registry.String('!','Prefix character for factoid display/editing'))
conf.registerChannelValue(Encyclopedia, 'searchorder',
registry.String('','Distro search order'))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
conf.registerGlobalValue(Encyclopedia, 'datadir',
registry.String('', 'Path to dir containing factoid databases'))
conf.registerGlobalValue(Encyclopedia, 'aptdir',
registry.String('', 'Path to apt cache directory'))

106
Encyclopedia/packages.py Normal file
View File

@ -0,0 +1,106 @@
###
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import commands, os, apt
from email import FeedParser
def component(arg):
if '/' in arg: return arg[:arg.find('/')]
return 'main'
class Apt:
def __init__(self, plugin):
self.aptdir = plugin.registryValue('aptdir')
self.distros = []
if self.aptdir:
self.distros = [x[:-5] for x in os.listdir(self.aptdir) if x.endswith('.list')]
self.aptcommand = """apt-cache\\
-o"Dir::State::Lists=%s/%%s"\\
-o"Dir::etc::sourcelist=%s/%%s.list"\\
-o"Dir::State::status=%s/%%s.status"\\
-o"Dir::Cache=%s/cache"\\
%%s %%s""" % tuple([self.aptdir]*4)
self.aptfilecommand = """apt-file -s %s/%%s.list -c %s/apt-file/%%s -l -F search %%s""" % tuple([self.aptdir]*2)
def find(self, pkg, checkdists, filelookup=True):
_pkg = ''.join([x for x in pkg.strip().split(None,1)[0] if x.isalnum or x in '.-_+'])
distro = checkdists[0]
if len(pkg.strip().split()) > 1:
distro = ''.join([x for x in pkg.strip().split(None,2)[1] if x.isalnum or x in '.-_+'])
if distro not in self.distros:
distro = checkdists[0]
pkg = _pkg
data = commands.getoutput(self.aptcommand % (distro, distro, distro, 'search -n', pkg))
if not data:
if filelookup:
data = commands.getoutput(self.aptfilecommand % (distro, distro, pkg)).split()
if data:
if len(data) > 5:
return "File %s found in %s (and %d others)" % (pkg, ', '.join(data[:5]), len(data)-5)
return "File %s found in %s" % (pkg, ', '.join(data))
return 'Package/file %s does not exist in %s' % (pkg, distro)
return "No packages matching '%s' could be found" % pkg
pkgs = [x.split()[0] for x in data.split('\n')]
if len(pkgs) > 5:
return"Found: %s (and %d others)" % (', '.join(pkgs[:5]), len(pkgs) -5)
else:
return "Found: %s" % ', '.join(pkgs[:5])
def info(self, pkg, checkdists):
_pkg = ''.join([x for x in pkg.strip().split(None,1)[0] if x.isalnum() or x in '.-_+'])
distro = None
if len(pkg.strip().split()) > 1:
distro = ''.join([x for x in pkg.strip().split(None,2)[1] if x.isalnum() or x in '-._+'])
if distro:
if distro not in self.distros:
checkdists = [checkdists[0]]
else:
checkdists = [distro]
pkg = _pkg
for distro in checkdists:
data = commands.getoutput(self.aptcommand % (distro, distro, distro, 'show', pkg))
data2 = commands.getoutput(self.aptcommand % (distro, distro, distro, 'showsrc', pkg))
if not data or 'E: No packages found' in data:
continue
maxp = {'Version': '0'}
packages = [x.strip() for x in data.split('\n\n')]
for p in packages:
if not p.strip():
continue
parser = FeedParser.FeedParser()
parser.feed(p)
p = parser.close()
if apt.VersionCompare(maxp['Version'], p['Version']) < 0:
maxp = p
del parser
maxp2 = {'Version': '0'}
packages2 = [x.strip() for x in data2.split('\n\n')]
for p in packages2:
if not p.strip():
continue
parser = FeedParser.FeedParser()
parser.feed(p)
p = parser.close()
if apt.VersionCompare(maxp2['Version'], p['Version']) < 0:
maxp2 = p
del parser
archs = ''
if maxp2['Architecture'] not in ('all','any'):
archs = ' (Only available for %s)' % maxp2['Architecture']
return("%s: %s. In component %s, is %s. Version %s (%s), package size %s kB, installed size %s kB%s" %
(maxp['Package'], maxp['Description'].split('\n')[0], component(maxp['Section']),
maxp['Priority'], maxp['Version'], distro, int(maxp['Size'])/1024, maxp['Installed-Size'], archs))
return 'Package %s does not exist in %s' % (pkg, ', '.join(checkdists))

493
Encyclopedia/plugin.py Normal file
View File

@ -0,0 +1,493 @@
###
# Copyright (c) 2006-2007 Dennis Kaarsemaker
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
from supybot.commands import *
import supybot.ircmsgs as ircmsgs
import supybot.callbacks as callbacks
import sqlite, datetime, time
import supybot.registry as registry
import supybot.ircdb as ircdb
import supybot.conf as conf
import re, os, time
import packages
reload(packages)
# Simple wrapper class for factoids
class Factoid:
def __init__(self, name, value, author, added, popularity):
self.name = name; self.value = value
self.author = author; self.added = added
self.popularity = popularity
class FactoidSet:
def __init__(self):
self.global_primary = self.global_secondary = \
self.channel_primary = self.channel_secondary = None
# Repeat filtering message queue
msgcache = {}
def queue(irc, to, msg):
now = time.time()
for m in msgcache.keys():
if msgcache[m] < now - 30:
msgcache.pop(m)
for m in msgcache:
if m[0] == irc and m[1] == to:
oldmsg = m[2]
if msg == oldmsg or oldmsg.endswith(msg):
break
if msg.endswith(oldmsg):
msg = msg[:-len(oldmsg)] + 'please see above'
else:
msgcache[(irc, to, msg)] = now
irc.queueMsg(ircmsgs.privmsg(to, msg))
def capab(prefix, capability):
try:
ircdb.users.getUser(prefix)
return ircdb.checkCapability(prefix, capability)
except:
return False
class Encyclopedia(callbacks.Plugin):
"""!factoid: show factoid"""
threaded = True
def __init__(self, irc):
callbacks.Plugin.__init__(self, irc)
self.databases = {}
self.times = {}
self.seens = {}
self.distros = []
self.Apt = packages.Apt(self)
self.edits = {}
def addeditor(self, irc, msg, args, name):
if not capab(msg.prefix, 'addeditors'):
return
try:
u = ircdb.users.getUser(name)
u.addCapability('editfactoids')
irc.replySuccess()
except:
irc.error('User %s is not registered' % name)
addeditor = wrap(addeditor, ['text'])
def removeeditor(self, irc, msg, args, name):
if not capab(msg.prefix, 'addeditors'):
return
try:
u = ircdb.users.getUser(name)
u.removeCapability('editfactoids')
irc.replySuccess()
except:
irc.error('User %s is not registered or not an editor' % name)
removeeditor = wrap(removeeditor, ['text'])
def editors(self, irc, msg, args):
irc.reply(', '.join([ircdb.users.getUser(u).name for u in ircdb.users.users \
if 'editfactoids' in ircdb.users.getUser(u).capabilities]))
editors = wrap(editors)
def moderators(self, irc, msg, args):
irc.reply(', '.join([ircdb.users.getUser(u).name for u in ircdb.users.users \
if 'addeditors' in ircdb.users.getUser(u).capabilities]))
moderators = wrap(moderators)
def get_target(self, nick, text, orig_target):
target = orig_target
retmsg = ''
if text.startswith('tell '):
text = ' ' + text
if '>' in text:
target = text[text.rfind('>')+1:].strip()
text = text[:text.rfind('>')].strip()
retmsg = "%s wants you to know: " % nick
elif ' tell ' in text and ' about ' in text:
target = text[text.find(' tell ')+6:].strip().split(None,1)[0]
text = text[text.find(' about ')+7:].strip()
retmsg = "%s wants you to know: " % nick
if '|' in text:
if not retmsg:
retmsg = text[text.find('|')+1:].strip() + ': '
text = text[:text.find('|')].strip()
if target == 'me':
target = nick
if target.lower() != orig_target.lower() and target.startswith('#'):
target = orig_target
retmsg = ''
if (target.lower() == nick.lower() or retmsg[:-2].lower() == nick.lower()) and nick.lower() != orig_target.lower():
target = nick
retmsg = '(In the future, please use a private message to investigate) '
return (text, target, retmsg)
def get_db(self, channel):
db = self.registryValue('database',channel)
if channel not in self.databases:
self.databases[channel] = sqlite.connect(os.path.join(self.registryValue('datadir'), '%s.db' % db))
self.databases[channel].name = db
return self.databases[channel]
def addressed(self, recipients, text, irc):
if recipients[0] == '#':
text = text.strip()
if text.lower() == self.registryValue('prefixchar') + irc.nick.lower():
return irc.nick.lower()
if text[0] == self.registryValue('prefixchar'):
text = text[1:]
if text.lower().startswith(irc.nick.lower()) and (len(text) < 5 or not text[5].isalnum()):
t2 = text[5:].strip()
if t2 and t2.find('>') != 0 and t2.find('|') != 0:
text = text[5:].strip()
return text
if text.lower().startswith(irc.nick) and not text[5].isalnum():
return text[5:]
return False
else: # Private
if text.strip()[0] in str(conf.supybot.reply.whenAddressedBy.chars):
return False
for c in irc.callbacks:
comm = text.split()[0]
if c.isCommandMethod(comm) and not c.isDisabled(comm):
return False
if text[0] == self.registryValue('prefixchar'):
return text[1:]
return text
def get_factoids(self, name, channel, resolve = True, info = False):
factoids = FactoidSet()
factoids.global_primary = self.get_single_factoid(channel, name)
factoids.global_secondary = self.get_single_factoid(channel, name + '-also')
factoids.channel_primary = self.get_single_factoid(channel, name + '-' + channel)
factoids.channel_secondary = self.get_single_factoid(channel, name + '-' + channel + '-also')
if resolve:
factoids.global_primary = self.resolve_alias(channel, factoids.global_primary)
factoids.global_secondary = self.resolve_alias(channel, factoids.global_secondary)
factoids.channel_primary = self.resolve_alias(channel, factoids.channel_primary)
factoids.channel_secondary = self.resolve_alias(channel, factoids.channel_secondary)
if info:
# Get aliases for factoids
factoids.global_primary = self.factoid_info(channel, factoids.global_primary)
factoids.global_secondary = self.factoid_info(channel, factoids.global_secondary)
factoids.channel_primary = self.factoid_info(channel, factoids.channel_primary)
factoids.channel_secondary = self.factoid_info(channel, factoids.channel_secondary)
return factoids
def get_single_factoid(self, channel, name, deleted=False):
db = self.get_db(channel)
cur = db.cursor()
if deleted:
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name = %s", name)
else:
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name = %s AND value NOT like '<deleted>%%'", name)
factoids = cur.fetchall()
if len(factoids):
f = factoids[0]
n = f[0]
if '-#' in n:
n = n[:n.find('-#')]
return Factoid(n,f[1],f[2],f[3],f[4])
def resolve_alias(self, channel, factoid, loop=0):
if loop >= 10:
return Factoid('','Error: infinite <alias> loop detected','','',0)
if factoid and factoid.value.lower().startswith('<alias>'):
new_factoids = self.get_factoids(factoid.value[7:].lower().strip(), channel, False)
for x in ['channel_primary', 'global_primary']:
if getattr(new_factoids, x):
return self.resolve_alias(channel, getattr(new_factoids, x), loop+1)
return Factoid('','Error: unresolvable <alias>','','',0)
else:
return factoid
def factoid_info(self, channel, factoid):
if not factoid:
return
if not factoid.value.startswith('<alias>'):
# Try and find aliases
db = self.get_db(channel)
cur = db.cursor()
cur.execute("SELECT name FROM facts WHERE value = %s", '<alias> ' + factoid.name)
data = cur.fetchall()
if data:
factoid.value = "<reply> %s aliases: %s" % (factoid.name, ', '.join([x[0] for x in data]))
else:
factoid.value = "<reply> %s has no aliases" % (factoid.name)
# Author info
factoid.value += " - added by %s on %s" % (factoid.author[:factoid.author.find('!')], factoid.added[:factoid.added.find('.')])
return factoid
def check_aliases(self, channel, factoid):
now = time.time()
for e in self.edits.keys():
if self.edits[e] + 10 < now:
self.edits.pop(e)
if not factoid.value.startswith('<alias>'):
return
# Was the old value an alias?
oldf = self.get_single_factoid(channel, factoid.name)
if oldf and oldf.value.startswith('<alias>'):
if factoid.name not in self.edits:
self.edits[factoid.name] = now
return "You are editing an alias. Please repeat the edit command within the next 10 seconds to confirm"
# Do some alias resolving
if factoid.value.startswith('<alias>'):
aliasname = factoid.value[7:].strip()
alias = self.get_single_factoid(channel, aliasname)
if not alias:
return "Factoid '%s' does not exist" % aliasname
alias = self.resolve_alias(channel, factoid)
if alias.value.lower().startswith('error'):
return alias.value.lower
factoid.value = '<alias> ' + alias.name
def doPrivmsg(self, irc, msg):
# Filter CTCP
if chr(1) in msg.args[1]:
return
# Are we being queried?
recipient, text = msg.args
text = self.addressed(recipient, text, irc)
if not text:
return
display_info = False
target = msg.args[0]
if target[0] != '#':
target = msg.nick
channel = msg.args[0]
# Strip leading nonalnums
while text and not text[0].isalnum():
if text[0] == '-':
display_info = True
text = text[1:]
if not text:
return
# Now switch between actions
orig_text = text
lower_text = text.lower()
ret = ''
retmsg = ''
if lower_text[:4] not in ('info','find'):
# Lookup, search or edit?
if lower_text.startswith('search '):
ret = self.search_factoid(lower_text[7:].strip(), channel)
elif (' is ' in lower_text and text[:3] in ('no ', 'no,')) or '<sed>' in lower_text or '=~' in lower_text \
or lower_text.startswith('forget') or lower_text.startswith('unforget'):
if not capab(msg.prefix, 'editfactoids'):
irc.reply("Your edit request has been forwarded to %s. Thank you for your attention to detail" %
self.registryValue('relaychannel'),private=True)
irc.queueMsg(ircmsgs.privmsg(self.registryValue('relaychannel'), "In %s, %s said: %s" %
(msg.args[0], msg.nick, msg.args[1])))
return
ret = self.factoid_edit(text, channel, msg.prefix)
elif ' is ' in text and ('|' not in text or text.find(' is ') > text.find('|')):
if not capab(msg.prefix, 'editfactoids'):
if len(text[:text.find('is')]) > 20:
irc.error("I am only a bot, please don't think I'm intelligent :)")
else:
irc.reply("Your edit request has been forwarded to %s. Thank you for your attention to detail" %
self.registryValue('relaychannel'),private=True)
irc.queueMsg(ircmsgs.privmsg(self.registryValue('relaychannel'), "In %s, %s said: %s" %
(msg.args[0], msg.nick, msg.args[1])))
return
ret = self.factoid_add(text, channel, msg.prefix)
else:
text, target, retmsg = self.get_target(msg.nick, orig_text, target)
ret = self.factoid_lookup(text, channel, display_info)
# Fall through to package lookup
if self.registryValue('packagelookup') and (not ret or not len(ret)):
text, target, retmsg = self.get_target(msg.nick, orig_text.lower(), target)
if text.startswith('info '):
ret = self.Apt.info(text[5:].strip(),self.registryValue('searchorder', channel).split())
elif text.startswith('find '):
ret = self.Apt.find(text[5:].strip(),self.registryValue('searchorder', channel).split())
else:
ret = self.Apt.info(text.strip(),self.registryValue('searchorder', channel).split())
if ret.startswith('Package'):
ret = None
if not ret:
retmsg = ''
ret = self.registryValue('notfoundmsg')
if ret.count('%') == ret.count('%s') == 1:
ret = ret % text
if type(ret) != list:
queue(irc, target, retmsg + ret)
else:
queue(irc, target, retmsg + ret[0])
for r in ret[1:]:
queue(irc, target, r)
def factoid_edit(self, text, channel, editor):
db = self.get_db(channel)
cs = db.cursor()
factoid = retmsg = None
def log_change(factoid):
cs.execute('''insert into log (author, name, added, oldvalue) values (%s, %s, %s, %s)''',
(editor, factoid.name, str(datetime.datetime.now()), factoid.value))
db.commit()
if text.lower().startswith('forget '):
factoid = self.get_single_factoid(channel, text[7:])
if not factoid:
return "I know nothing about %s yet, %s" % (text[7:], editor[:editor.find('!')])
else:
log_change(factoid)
factoid.value = '<deleted>' + factoid.value
retmsg = "I'll forget that, %s" % editor[:editor.find('!')]
if text.lower().startswith('unforget '):
factoid = self.get_single_factoid(channel, text[9:], deleted=True)
if not factoid:
return "I knew nothing about %s at all, %s" % (text[9:], editor[:editor.find('!')])
else:
if not factoid.value.startswith('<deleted>'):
return "Factoid %s wasn't deleted yet, %s" % (factoid.name, editor[:editor.find('!')])
log_change(factoid)
factoid.value = factoid.value[9:]
retmsg = "I suddenly remember %s again, %s" % (factoid.name, editor[:editor.find('!')])
if text.lower()[:3] in ('no ', 'no,'):
text = text[3:].strip()
p = text.lower().find(' is ')
name, value = text[:p].strip(), text[p+4:].strip()
if not name or not value:
return
name = name.lower()
factoid = self.get_single_factoid(channel, name)
if not factoid:
return "I know nothing about %s yet, %s" % (name, editor[:editor.find('!')])
log_change(factoid)
factoid.value = value
retmsg = "I'll remember that %s" % editor[:editor.find('!')]
if not retmsg:
if ' is<sed>' in text:
text = text.replace('is<sed>','=~',1)
if ' is <sed>' in text:
text = text.replace('is <sed>','=~',1)
# Split into name and regex
name = text[:text.find('=~')].strip()
regex = text[text.find('=~')+2:].strip()
# Edit factoid
factoid = self.get_single_factoid(channel, name)
if not factoid:
return "I know nothing about %s yet, %s" % (name, editor[:editor.find('!')])
# Grab the regex
if regex.startswith('s'):
regex = regex[1:]
if regex[-1] != regex[0]:
return "Missing end delimiter"
if regex.count(regex[0]) != 3:
return "Too many (or not enough) delimiters"
regex, replace = regex[1:-1].split(regex[0])
try:
regex = re.compile(regex)
except:
return "Malformed regex"
newval = regex.sub(replace, factoid.value, 1)
if newval == factoid.value:
return "Nothing changed there"
log_change(factoid)
factoid.value = newval
retmsg = "I'll remember that %s" % editor[:editor.find('!')]
ret = self.check_aliases(channel, factoid)
if ret:
return ret
cs.execute("UPDATE facts SET value=%s where name=%s", (factoid.value,factoid.name))
db.commit()
return retmsg
def factoid_add(self, text, channel, editor):
db = self.get_db(channel)
cs = db.cursor()
p = text.lower().find(' is ')
name, value = text[:p].strip(), text[p+4:].strip()
if not name or not value:
return
name = name.lower()
if value.startswith('also '):
name += '-also'
value = value[5:].strip()
if not value:
return
if self.get_single_factoid(channel, name, deleted=True):
return "But %s already means something else!" % name
factoid = Factoid(name,value,None,None,None)
ret = self.check_aliases(channel, factoid)
if ret:
return ret
cs.execute("""INSERT INTO facts (name, value, author, added) VALUES (%s, %s, %s, %s)""",
(name, value, editor, str(datetime.datetime.now())))
db.commit()
return "I'll remember that, %s" % editor[:editor.find('!')]
def factoid_lookup(self, text, channel, display_info):
db = self.get_db(channel)
factoids = self.get_factoids(text.lower(), channel, resolve = not display_info, info = display_info)
ret = []
for order in ('primary', 'secondary'):
for loc in ('channel', 'global'):
key = '%s_%s' % (loc, order)
if getattr(factoids, key):
factoid = getattr(factoids,key)
if not display_info:
cur = db.cursor()
cur.execute("UPDATE FACTS SET popularity = %d WHERE name = %s", factoid.popularity+1, factoid.name)
db.commit()
if factoid.value.startswith('<reply>'):
ret.append(factoid.value[7:].strip().replace('$chan',channel))
elif order == 'secondary':
ret.append(factoid.value.strip().replace('$chan',channel))
else:
ret.append('%s is %s' % (factoid.name, factoid.value.replace('$chan',channel)))
if not display_info:
break
return ret
def search_factoid(self, factoid, channel):
keys = factoid.split()[:5]
db = self.get_db(channel)
cur = db.cursor()
ret = {}
for k in keys:
k = k.replace("'","\'")
cur.execute("SELECT name,value FROM facts WHERE name LIKE '%%%s%%' OR VAlUE LIKE '%%%s%%'" % (k, k))
res = cur.fetchall()
for r in res:
d = r[1].startswith('<deleted>')
r = r[0]
if d:
r += '*'
try:
ret[r] += 1
except:
ret[r] = 1
return 'Found: %s' % ', '.join(sorted(ret.keys(), lambda x, y: cmp(ret[x], ret[y]))[:10])
Class = Encyclopedia

View File

@ -1,574 +0,0 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
#
###
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.ircmsgs as ircmsgs
import supybot.callbacks as callbacks
import sqlite, datetime, time, apt_pkg, commands
import supybot.registry as registry
import supybot.ircdb as ircdb
from email import FeedParser
import re, os, fcntl, time
apt_pkg.init()
datadir = '/home/dennis/ubugtu/data/facts'
aptdir = '/home/dennis/ubugtu/data/apt'
logdir = '/var/www/bots.ubuntulinux.nl/'
distros = ('dapper','edgy','feisty','breezy','edgy','dapper-commercial','dapper-seveas','breezy-seveas','dapper-imbrandon','edgy-imbrandon', 'dapper-backports','edgy-seveas')
# Simple wrapper class for factoids
class Factoid:
def __init__(self, name, value, author, added, popularity):
self.name = name; self.value = value
self.author = author; self.added = added
self.popularity = popularity
class FactoidSet:
def __init__(self):
self.global_primary = self.global_secondary = \
self.channel_primary = self.channel_secondary = None
msgcache = {}
def queue(irc, to, msg):
now = time.time()
for m in msgcache.keys():
if msgcache[m] < now - 30:
msgcache.pop(m)
if (irc, to, msg) not in msgcache:
msgcache[(irc, to, msg)] = now
irc.queueMsg(ircmsgs.privmsg(to, msg))
class Encyclopedia(callbacks.Plugin):
"""!factoid: show factoid"""
threaded = True
def __init__(self, irc):
callbacks.Plugin.__init__(self, irc)
self.databases = {}
self.times = {}
self.seens = {}
def addeditor(self, irc, msg, args, name):
if not capab(msg.prefix, 'addeditors'):
return
try:
u = ircdb.users.getUser(name)
u.addCapability('editfactoids')
irc.replySuccess()
except:
irc.error('User %s is not registered' % name)
addeditor = wrap(addeditor, ['text'])
def removeeditor(self, irc, msg, args, name):
if not capab(msg.prefix, 'addeditors'):
return
try:
u = ircdb.users.getUser(name)
u.removeCapability('editfactoids')
irc.replySuccess()
except:
irc.error('User %s is not registered or not an editor' % name)
removeeditor = wrap(removeeditor, ['text'])
def editors(self, irc, msg, args):
irc.reply(', '.join([ircdb.users.getUser(u).name for u in ircdb.users.users \
if 'editfactoids' in ircdb.users.getUser(u).capabilities]))
editors = wrap(editors)
def _checkdists(self, channel):
cd = self.registryValue('searchorder', channel=channel)
return cd.split()
def moderators(self, irc, msg, args):
irc.reply(', '.join([ircdb.users.getUser(u).name for u in ircdb.users.users \
if 'addeditors' in ircdb.users.getUser(u).capabilities]))
moderators = wrap(moderators)
# Parse seenservs replies
def doNotice(self, irc, msg):
if msg.nick.lower() != 'seenserv':
return
resp = msg.args[1]
for n in self.seens.keys():
if self.seens[n][1] < time.time() - 10:
self.seens.pop(n)
for n in self.seens.keys():
if n.lower() in resp.lower():
queue(irc, self.seens[n][0], resp)
self.seens.pop(n)
def doPrivmsg(self, irc, msg):
if chr(1) in msg.args[1]:
return
recipient, text = msg.args
text = addressed(recipient, text, irc)
if not text:
return
display_info = False
target = msg.args[0]
if target[0] != '#':
target = msg.nick
channel = msg.args[0]
# Strip leading nonalnums
while text and not text[0].isalnum():
if text[0] == '-':
display_info = True
text = text[1:]
if not text:
return
# Now switch between actions
# XXX these 3 belong in a different plugin, but hey
if text.lower()[:4] in ('info','seen','find'):
text = text.lower()
if self.registryValue('packagelookup'):
if text.startswith('info '):
queue(irc, target, pkginfo(text[5:].strip(),self._checkdists(msg.args[0])))
return
if text.startswith('find '):
queue(irc, target, findpkg(text[5:].strip(),self._checkdists(msg.args[0])))
return
if text.startswith('seen '):
self.seens[text[5:].strip()] = (target, time.time())
queue(irc, 'seenserv', "seen %s" % text[5:].strip())
return
# Factoid manipulation
db = self.registryValue('database',channel)
if not db:
db,channel = self.registryValue('fallbackdb'), self.registryValue('fallbackchannel')
if channel not in self.databases:
self.databases[channel] = sqlite.connect(os.path.join(datadir, '%s.db' % db))
self.databases[channel].name = db
db = self.databases[channel]
if text.lower().startswith('search '):
irc.reply(searchfactoid(db, text[7:].strip().lower()))
return
do_new = False
if text.lower().startswith('forget '):
if ' is ' in text.lower() or text.lower().endswith(' is'):
return # Bad hack attempt :)
text = '%s =~ s/^/<deleted>/' % text[7:]
if text.lower().startswith('unforget '):
if ' is ' in text.lower() or text.lower().endswith(' is'):
return # Bad hack attempt :)
text = '%s =~ s/^<deleted>//' % text[9:]
if ' is<sed>' in text:
text = text.replace('is<sed>','=~',1)
elif ' is <sed>' in text:
text = text.replace('is <sed>','=~',1)
if ' is ' in text and '=~' not in text and not ('|' in text and text.find(' is ') > text.find('|')):
do_new = True
if text.lower()[:3] in ('no ','no,'):
do_new = False
text = text[3:].strip()
if text.startswith('is '):
return
p = text.lower().find(' is ')
n, v = text[:p].strip(), text[p+4:].strip()
if not n or not v:
return
for c in '!#@$^*/':
if c not in text:
text = '%s =~ s%s.*%s%s%s' % (n, c, c, v, c)
break
else:
irc.error('Internal error, please report')
return
# Big action 1: editing factoids
if '=~' in text:
# Editing
# Find factoid
p = text.find('=~')
name, value = text[:p].strip(), text[p+2:].strip()
name = name.lower()
if value.startswith('also '):
name += '-also'
value = value[5:].strip()
if not capab(msg.prefix, 'editfactoids'):
if len(name) > 20:
irc.error("I am only a bot, please don't think I'm intelligent :)")
return
irc.reply("Your edit request has been forwarded to %s. Thank you for your attention to detail"%self.registryValue('relaychannel'),private=True)
irc.queueMsg(ircmsgs.privmsg(self.registryValue('relaychannel'), "In %s, %s said: %s" % (msg.args[0], msg.nick, msg.args[1])))
lfd = open(logdir + '/botlogs/lock','a')
fcntl.lockf(lfd, fcntl.LOCK_EX)
fd = open(logdir + '/botlogs/%s.log' % datetime.date.today().strftime('%Y-%m-%d'),'a')
fd.write("%s %-20s %-16s %s\n" % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), channel, msg.nick, msg.args[1]))
fd.close()
fcntl.lockf(lfd,fcntl.LOCK_UN)
lfd.close()
os.chmod(logdir + '/botlogs/%s.log' % datetime.date.today().strftime('%Y-%m-%d'),0644)
return
# All clear!
#irc.reply(str((name, value)))
####
# Find existing factoid
newtext = name
newchannel = channel
secondary = channel_specific = False
if newtext.endswith('-also'):
newtext = newtext[:-5]
secondary = True
if '-#' in newtext:
newchannel = newtext[newtext.find('-#')+1:]
newtext = newtext[:newtext.find('-#')]
channel_specific = True
existing = get_factoids(db, newtext, newchannel, resolve=True)
# If it is an alias/also and new, check whether it resolves
if secondary and not (existing.global_primary or existing.channel_primary):
irc.error("I know nothing about %s yet" % newtext)
return
cur = db.cursor()
# If it is new and exists, bail
if do_new:
if real_get_factoid(cur, name):
irc.reply("%s is already known" % name)
return
# If it is an edit, but doesn't exist: bail
else:
if not real_get_factoid(cur, name):
irc.reply("I know nothing about %s yet" % name)
return
# Edit factoid
f = real_get_factoid(cur, name, True)
if not f:
cur.execute("""INSERT INTO facts (name, value, author, added) VALUES
(%s, '<deleted>', %s, %s)""", (name, msg.prefix, str(datetime.datetime.now())))
db.commit()
f = real_get_factoid(cur, name, True)
if value.startswith('s'):
value = value[1:]
if value[-1] != value[0]:
irc.reply("Missing end delimiter")
return
if value.count(value[0]) != 3:
irc.reply("Too many (or not enough) delimiters")
return
regex, replace = value[1:-1].split(value[0])
try:
regex = re.compile(regex)
except:
irc.reply("Malformed regex")
return
newval = regex.sub(replace, f.value, 1)
if newval == f.value:
irc.reply("Nothing changed there")
return
f.value = newval
# Check resolving of aliases
if f.value.startswith('<alias>'):
alias = f.value[7:].strip()
if name == alias:
irc.error("Recursive <alias> detected. Bailing out!")
return
aliases = get_factoids(db, alias, newchannel, resolve=True)
if aliases.global_primary:
if name == aliases.global_primary.name:
irc.error("Recursive <alias> detected. Bailing out!")
return
f.value = '<alias> ' + aliases.global_primary.name
elif aliases.channel_primary:
f.name += '-%s' % newchannel
f.value = '<alias> ' + aliases.channel_primary.name
else:
irc.error("Unresolvable alias: %s" % alias)
return
# Finally, save
log("(%s) UPDATE facts SET value = %s WHERE name = %s" % (msg.prefix, f.value, f.name))
cur.execute("UPDATE facts SET value = %s WHERE name = %s", (f.value, f.name))
db.commit()
irc.reply("I'll remember that, %s" % msg.nick)
else:
# Display a factoid
# Find recipient
_target = None
retmsg = ''
if '>' in text:
_target = text[text.rfind('>')+1:].strip()
text = text[:text.rfind('>')].strip()
if text.startswith('tell '):
text = ' ' + text
if ' tell ' in text and ' about ' in text:
_target = text[text.find(' tell ')+6:].strip().split(None,1)[0]
text = text[text.find(' about ')+7:].strip()
if '|' in text:
retmsg = text[text.find('|')+1:].strip() + ': '
text = text[:text.find('|')].strip()
if _target:
# Validate
if _target == 'me':
_target = msg.nick
for chan in irc.state.channels:
if _target in irc.state.channels[chan].users and msg.nick in irc.state.channels[chan].users:
target = _target
retmsg = '%s wants you to know: ' % msg.nick
break
else:
irc.error("That person could not be found in any channel you're in")
return
factoids = get_factoids(db, text.lower(), channel, resolve = not display_info, info = display_info)
replied = False
if target.lower() == msg.nick.lower() and msg.args[0][0] == '#':
queue(irc, target, "To send answers to yourself, please use /msg instead of spamming the channel")
for key in ('channel_primary', 'global_primary'):
if getattr(factoids, key):
replied = True
factoid = getattr(factoids,key)
if not display_info:
cur = db.cursor()
cur.execute("UPDATE FACTS SET popularity = %d WHERE name = %s", factoid.popularity+1, factoid.name)
db.commit()
if factoid.value.startswith('<reply>'):
#irc.queueMsg(ircmsgs.privmsg(target, '%s%s' % (retmsg, factoid.value[7:].strip())))
queue(irc, target, '%s%s' % (retmsg, factoid.value[7:].strip().replace('$chan',channel)))
else:
#irc.queueMsg(ircmsgs.privmsg(target, '%s%s is %s' % (retmsg, factoid.name, factoid.value.strip())))
queue(irc, target, '%s%s is %s' % (retmsg, factoid.name, factoid.value.strip().replace('$chan',channel)))
if not display_info:
break
else:
if not replied:
if self.registryValue('packagelookup'):
i = pkginfo(text,self._checkdists(msg.args[0]))
if not i.startswith('Package'):
queue(irc, target, i)
else:
if len(text) > 16:
irc.error("I am only a bot, please don't think I'm intelligent :)")
return
irc.reply(self.registryValue('notfoundmsg') % text)
else:
if len(text) > 16:
irc.error("I am only a bot, please don't think I'm intelligent :)")
return
irc.reply(self.registryValue('notfoundmsg') % text)
for key in ('channel_secondary', 'global_secondary'):
if getattr(factoids, key):
factoid = getattr(factoids,key)
#irc.queueMsg(ircmsgs.privmsg(target, '%s%s' % (retmsg, factoid.value.strip())))
queue(irc, target, '%s%s' % (retmsg, factoid.value.strip()))
if not display_info:
break
msgcache = {}
def send(irc, to, msg):
now = time.time()
for k in msgcache:
if now - msgcache[k] > 10:
msgcache.pop(k)
k = (irc, to, msg)
if k not in msgcache:
msgcache[k] = time.time()
irc.queueMsg(ircmsgs.privmsg(to, msg))
def addressed(recipients, text, irc):
if recipients[0] == '#':
text = text.strip()
if text.lower() == '!ubotu':
return 'ubotu'
if text[0] == '!':
text = text[1:]
if text.lower().startswith('ubotu') and (len(text) < 5 or not text[5].isalnum()):
t2 = text[5:].strip()
if t2 and t2.find('>') != 0 and t2.find('|') != 0:
text = text[5:].strip()
return text
if text.lower().startswith('ubotu') and not text[5].isalnum(): # FIXME: use nickname variable
return text[5:]
return False
else: # Private messages
if text.strip()[0] == '%': # FIXME: replywhenaddressed.chars oslt
return False
for c in irc.callbacks:
comm = text.split()[0]
if c.isCommandMethod(comm) and not c.isDisabled(comm):
return False
if text[0] == '!':
return text[1:]
#if text.lower().startswith('ubotu'): # FIXME: use nickname variable
# return text[5:]
return text
aptcommand = """apt-cache\\
-o"Dir::State::Lists=%s/%%s"\\
-o"Dir::etc::sourcelist=%s/%%s.list"\\
-o"Dir::State::status=%s/%%s.status"\\
-o"Dir::Cache=%s/cache"\\
%%s %%s""" % tuple([aptdir]*4)
aptfilecommand = """apt-file -s %s/%%s.list -c %s/apt-file/%%s -l -F search %%s""" % tuple([aptdir]*2)
def findpkg(pkg,checkdists,filelookup=True):
_pkg = ''.join([x for x in pkg.strip().split(None,1)[0] if x.isalnum or x in '.-_+'])
distro = checkdists[0]
if len(pkg.strip().split()) > 1:
distro = ''.join([x for x in pkg.strip().split(None,2)[1] if x.isalnum or x in '.-_+'])
if distro not in distros:
distro = checkdists[0]
pkg = _pkg
data = commands.getoutput(aptcommand % (distro, distro, distro, 'search -n', pkg))
if not data:
if filelookup:
data = commands.getoutput(aptfilecommand % (distro, distro, pkg)).split()
if data:
if len(data) > 5:
return "File %s found in %s (and %d others)" % (pkg, ', '.join(data[:5]), len(data)-5)
return "File %s found in %s" % (pkg, ', '.join(data))
return 'Package/file %s does not exist in %s' % (pkg, distro)
return "No packages matching '%s' could be found" % pkg
pkgs = [x.split()[0] for x in data.split('\n')]
if len(pkgs) > 5:
return"Found: %s (and %d others)" % (', '.join(pkgs[:5]), len(pkgs) -5)
else:
return "Found: %s" % ', '.join(pkgs[:5])
def pkginfo(pkg,checkdists):
_pkg = ''.join([x for x in pkg.strip().split(None,1)[0] if x.isalnum() or x in '.-_+'])
distro = None
if len(pkg.strip().split()) > 1:
distro = ''.join([x for x in pkg.strip().split(None,2)[1] if x.isalnum() or x in '-._+'])
if distro:
if distro not in distros:
checkdists = [checkdists[0]]
else:
checkdists = [distro]
pkg = _pkg
for distro in checkdists:
data = commands.getoutput(aptcommand % (distro, distro, distro, 'show', pkg))
data2 = commands.getoutput(aptcommand % (distro, distro, distro, 'showsrc', pkg))
if not data or 'E: No packages found' in data:
continue
maxp = {'Version': '0'}
packages = [x.strip() for x in data.split('\n\n')]
for p in packages:
if not p.strip():
continue
parser = FeedParser.FeedParser()
parser.feed(p)
p = parser.close()
if apt_pkg.VersionCompare(maxp['Version'], p['Version']) < 0:
maxp = p
del parser
maxp2 = {'Version': '0'}
packages2 = [x.strip() for x in data2.split('\n\n')]
for p in packages2:
if not p.strip():
continue
parser = FeedParser.FeedParser()
parser.feed(p)
p = parser.close()
if apt_pkg.VersionCompare(maxp2['Version'], p['Version']) < 0:
maxp2 = p
del parser
archs = ''
if maxp2['Architecture'] not in ('all','any'):
archs = ' (Only available for %s)' % maxp2['Architecture']
return("%s: %s. In component %s, is %s. Version %s (%s), package size %s kB, installed size %s kB%s" %
(maxp['Package'], maxp['Description'].split('\n')[0], component(maxp['Section']),
maxp['Priority'], maxp['Version'], distro, int(maxp['Size'])/1024, maxp['Installed-Size'], archs))
if len(checkdists) > 1:
return 'Package %s does not exist in any distro I know' % pkg
return 'Package %s does not exist in %s' % (pkg, distro)
def component(arg):
if '/' in arg: return arg[:arg.find('/')]
return 'main'
def real_get_factoid(cur,name,deleted=False):
if deleted:
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name = %s", name)
else:
cur.execute("SELECT name, value, author, added, popularity FROM facts WHERE name = %s AND value NOT like '<deleted>%%'", name)
factoid = cur.fetchall()
if len(factoid):
f = factoid[0]
return Factoid(f[0],f[1],f[2],f[3],f[4])
def get_factoids(db, name, channel, resolve = True, info = False):
cur = db.cursor()
factoids = FactoidSet()
factoids.global_primary = real_get_factoid(cur, name)
factoids.global_secondary = real_get_factoid(cur, name + '-also')
factoids.channel_primary = real_get_factoid(cur, name + '-' + channel)
factoids.channel_secondary = real_get_factoid(cur, name + '-' + channel + '-also')
if resolve:
factoids.global_primary = resolve_alias(db, factoids.global_primary, channel)
factoids.global_secondary = resolve_alias(db, factoids.global_secondary, channel)
factoids.channel_primary = resolve_alias(db, factoids.channel_primary, channel)
factoids.channel_secondary = resolve_alias(db, factoids.channel_secondary, channel)
if info:
# Get aliases for factoids
factoids.global_primary = factoid_info(db, factoids.global_primary, channel)
factoids.global_secondary = factoid_info(db, factoids.global_secondary, channel)
factoids.channel_primary = factoid_info(db, factoids.channel_primary, channel)
factoids.channel_secondary = factoid_info(db, factoids.channel_secondary, channel)
return factoids
def searchfactoid(db, factoid):
keys = factoid.split()[:5]
cur = db.cursor()
ret = {}
for k in keys:
k = k.replace("'","\'")
cur.execute("SELECT name FROM facts WHERE name LIKE '%%%s%%' OR VAlUE LIKE '%%%s%%'" % (k, k))
res = cur.fetchall()
for r in res:
r = r[0]
try:
ret[r] += 1
except:
ret[r] = 1
return 'Found: %s' % ','.join(sorted(ret.keys(), lambda x, y: cmp(ret[x], ret[y]))[:10])
def factoid_info(db,factoid,channel):
if factoid:
if not factoid.value.startswith('<alias>'):
# Try and find aliases
cur = db.cursor()
cur.execute("SELECT name FROM facts WHERE value = %s", '<alias> ' + factoid.name)
data = cur.fetchall()
if data:
factoid.value = "<reply> %s aliases: %s" % (factoid.name, ', '.join([x[0] for x in data]))
else:
factoid.value = "<reply> %s has no aliases" % (factoid.name)
# Author info
factoid.value += " - added by %s on %s" % (factoid.author[:factoid.author.find('!')], factoid.added[:factoid.added.find('.')])
return factoid
def resolve_alias(db,factoid,channel,loop=0):
if loop >= 10:
return Factoid('','Error: infinite <alias> loop detected','','',0)
if factoid and factoid.value.lower().startswith('<alias>'):
new_factoids = get_factoids(db,factoid.value[7:].lower().strip(), channel, False)
for x in ['channel_primary', 'global_primary']:
if getattr(new_factoids, x):
return resolve_alias(db, getattr(new_factoids, x), channel)
return Factoid('','Error: unresolvable <alias>','','',0)
#return None
else:
return factoid
def capab(prefix, capability):
try:
_ = ircdb.users.getUser(prefix)
if ircdb.checkCapability(prefix, capability):
return True
except:
pass
return False
def log(msg):
fd = open('/home/dennis/editlog','a')
fd.write('%s\n' % msg)
fd.close()
Class = Encyclopedia

14
Encyclopedia/update_apt Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
DIR=/home/dennis/ubugtu/data/apt
for DIST in "$DIR"/*.list; do
DIST=${DIST:${#DIR}}
DIST=${DIST/.list}
touch "$DIR/$DIST.status"
mkdir -p "$DIR/$DIST"
apt-get -qq -o "Dir::State::Lists=$DIR/$DIST" \
-o "Dir::etc::sourcelist=$DIR/$DIST.list" \
-o "Dir::State::status=$DIR/$DIST.status" \
-o "Dir::Cache=$DIR/cache" update
done

14
Encyclopedia/update_apt_file Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
DIR=/home/dennis/ubugtu/data/apt
for DIST in "$DIR"/*.list; do
DIST=${DIST:${#DIR}}
DIST=${DIST/.list}
mkdir -p "$DIR/apt-file/$DIST"
apt-file -l -c "$DIR/apt-file/$DIST" -s "$DIR/$DIST.list" update >/dev/null 2>&1
RET=$?
if [ ! $RET ]; then
echo "apt-file failed for $DIST!"
fi
done

50
Mess/ferengi.txt Normal file
View File

@ -0,0 +1,50 @@
1: Once you have their money, you never give it back.
3: Never spend more for an acquisition than you have to.
6: Never let family stand in the way of opportunity.
7: Always keep your ears open.
9: Opportunity plus instinct equals profit.
10: Greed is eternal.
16: A deal is a deal.
17: A contract is a contract is a contract but only between Ferengi.
18: A Ferengi without profit is no Ferengi at all.
21: Never place friendship before profit.
22: Wise men can hear profit in the wind.
23: Nothing is more important than your health. Except for money.
31: Never make fun of a Ferengi's mother.
33: It never hurts to suck up to the boss.
34: War is good for business.
35: Peace is good for business.
47: Don't trust a man wearing a better suit than your own.
48: The bigger the smile, the sharper the knife.
57: Good customers are as rare as latinum — treasure them.
59: Free advice is seldom cheap.
62: The riskier the road, the greater the profit.
74: Knowledge equals profit.
75: Home is where the heart is but the stars are made of latinum.
76: Every once in a while, declare peace. It confuses the hell out of your enemies.
94: Females and finances don't mix.
95: Expand or die.
98: Every man has his price.
102: Nature decays, but latinum lasts forever.
103: Sleep can interfere with opportunity.
109: Dignity and an empty sack is worth the sack.
111: Treat people in your debt like family - exploit them.
112: Never have sex with the boss' sister.
119: Buy, sell, or get out of the way.*
125: You can't make a deal if you're dead.
139: Wives serve; brothers inherit.
141: Only fools pay retail.
168: Whisper your way to success.
190: Hear all, trust nothing.
194: It's always good business to know about new customers before they walk in your door.
203: New customers are like razor-toothed gree worms: they can be succulent but sometimes they can bite back.
208: Sometimes the only thing more dangerous than a question is an answer.
211: Employees are the rungs on the ladder of success - don't hesitate to step on them.
214: Never begin a business negotiation on an empty stomach.
217: You can't free a fish from water.
229: Latinum lasts longer than lust.
239: Never be afraid to mis-label a product.
242: More is good, all is better.
263: Never allow doubt to tarnish your lust for Latinum.
285: No good deed ever goes unpunished.
: When no rule applies . . . make one up

3
Restart/README.txt Normal file
View File

@ -0,0 +1,3 @@
This is a not-always useful plugin to crudely restart the bot using execlp. This
will mean destroying all state and simply running a new bot. The bot won't
disconnect nicely or whatever. Yes, it's horrible :)

36
Restart/__init__.py Normal file
View File

@ -0,0 +1,36 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
###
"""
Crudely restart supybot by exec'ing itself
"""
import supybot
import supybot.world as world
__version__ = "0.1"
__author__ = supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net")
__contributors__ = {}
__url__ = 'https://bots.ubuntulinux.nl'
import config
reload(config)
import plugin
reload(plugin)
if world.testing:
import test
Class = plugin.Class
configure = config.configure

25
Restart/config.py Normal file
View File

@ -0,0 +1,25 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
import supybot.conf as conf
import supybot.registry as registry
def configure(advanced):
from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Restart', True)
Restart = conf.registerPlugin('Restart')
conf.registerGlobalValue(Restart, 'configfile',
registry.String('', """Config filename to restart with"""))

39
Restart/plugin.py Normal file
View File

@ -0,0 +1,39 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# All rights reserved.
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###
from supybot.commands import *
import supybot.callbacks as callbacks
import os
class Restart(callbacks.Plugin):
"""Restart the bot with restart"""
@wrap
def restart(self,irc,msg,args):
try:
_ = ircdb.users.getUser(msg.prefix)
if not ircdb.checkCapability(msg.prefix, 'restart'):
raise KeyError, "Bogus error to trigger the exception"
except:
irc.error("You don't have permission to restart")
return
conf = self.registryValue('configfile')
if not conf:
irc.error("No configfile specified")
else:
os.execlp('supybot','supybot',conf)
Class = Restart

37
Restart/test.py Normal file
View File

@ -0,0 +1,37 @@
###
# Copyright (c) 2006, Dennis Kaarsemaker
# 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.
###
from supybot.test import *
class RestartTestCase(PluginTestCase):
plugins = ('Restart',)
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: