Encyclopedia made neat.
This commit is contained in:
parent
139d186c83
commit
f3ffcabca9
|
@ -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"""
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 :)
|
|
@ -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
|
|
@ -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"""))
|
|
@ -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
|
|
@ -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:
|
Loading…
Reference in New Issue